| #!/usr/bin/perl | |
| # Extract utility for SimH help text | |
| # Copyright (c) 2013, Timothe Litt | |
| # Permission is hereby granted, free of charge, to any person obtaining a | |
| # copy of this software and associated documentation files (the "Software"), | |
| # to deal in the Software without restriction, including without limitation | |
| # the rights to use, copy, modify, merge, publish, distribute, sublicense, | |
| # and/or sell copies of the Software, and to permit persons to whom the | |
| # Software is furnished to do so, subject to the following conditions: | |
| # The above copyright notice and this permission notice shall be included in | |
| # all copies or substantial portions of the Software. | |
| # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
| # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
| # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL | |
| # THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER | |
| # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN | |
| # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
| # Except as contained in this notice, the name of the author shall not be | |
| # used in advertising or otherwise to promote the sale, use or other dealings | |
| # in this Software without prior written authorization from the author. | |
| use warnings; | |
| use strict; | |
| # This utility attempts to read the C source of an emulator device and convert | |
| # its help function to the structured help format. Manual editing of the result | |
| # will be required, but the mechanical work is done by the tool. | |
| # | |
| # A template for organizing the help into standard topics/subtopics is inserted | |
| # along with the translation. At this writing, everything is experimental, so | |
| # the 'standard' format may change. Nonetheless, this is suitable for experimentation. | |
| use File::Basename; | |
| my $prg = basename $0; | |
| my $rtn; | |
| my $ifn = '-'; | |
| my $ofn = '-'; | |
| my $line; | |
| my $update; | |
| while (@ARGV) { | |
| if( $ARGV[0] eq '--' ) { | |
| last; | |
| } | |
| if( $ARGV[0] eq '-dev' ) { | |
| shift; | |
| $rtn = shift; | |
| $rtn .= "_help"; | |
| next; | |
| } | |
| if( $ARGV[0] eq '-u' ) { | |
| $update = 1; | |
| shift; | |
| next; | |
| } | |
| last if( $ARGV[0] !~ /^-/ ); | |
| printf STDERR << "USAGE_"; | |
| Usage: | |
| $prg -u -dev devname -func rtn infile outfile | |
| devname is used to look for the existing help function name. | |
| E.g. if the routine is cr_help, use -dev cr. | |
| Alternatively, use -func to specify the full function name. | |
| infile and outfile default to - (stdin and stdout) | |
| $prg will attempt to produce a sensible device help string, although you | |
| should expect that the result will require manual editing. Complex C | |
| constructs (preprocessor conditionals, if statements that generate strings) | |
| are not automatically translated, but the old code will be preserved. | |
| However, as it may have been partially translated, the result may not compile. | |
| A template is installed so that you can move your information into the standard | |
| sections. | |
| Source code in the help function is reformatted - not to any particular | |
| style, but as a consequence of how it is tokenized and parsed. Use your | |
| favorite pretty-printer if you don't like the results. | |
| Normally, just the help function is output. -u will output the entire file | |
| (-u = "update") | |
| USAGE_ | |
| exit (0); | |
| } | |
| unless( defined $rtn ) { | |
| die "The help function must be specified with -func or -dev; -help for usage\n"; | |
| } | |
| if( @ARGV ) { | |
| $ifn = shift; | |
| } | |
| if( @ARGV ) { | |
| $ofn = shift; | |
| } | |
| open( STDIN, "<$ifn" ) or | |
| die "Unable to open $ifn for input: $!\n"; | |
| open( STDOUT, ">$ofn" ) or | |
| die "Unable to open $ofn for output: $!\n"; | |
| $line = ""; | |
| while( <STDIN> ) { | |
| # Look for the help function | |
| if( /^(?:static\s+)?t_stat\s+$rtn\s*\(/ ) { | |
| $line = $_; | |
| while( $line !~ /\{/ && $line !~ /\)\s*;/ ) { | |
| my $cont = <STDIN>; | |
| if( !defined $cont ) { | |
| die "EOF in function definition\n"; | |
| } | |
| $line .= $cont; | |
| } | |
| if( $line =~ /\)\s*;/ ) { # Just a prototype | |
| if( $update ) { | |
| print $line; | |
| } | |
| $line = ""; | |
| next; | |
| } | |
| # Process the function body | |
| my $f = $line; | |
| my $b = ''; | |
| my $bl = 1; | |
| my( %vargs, @vargs ); | |
| my $help = ''; | |
| my $comments = ''; | |
| # Each statement in the body | |
| while (1) { | |
| my ($tok, $val) = gettok(); | |
| last if( !defined $tok ); | |
| if ($tok eq '{') { # Track brace level | |
| $bl++; | |
| $b .= $tok; | |
| } elsif ($tok eq '}') { | |
| die "Unmatched }\n" if ( --$bl < 0 ); | |
| $b .= $tok; | |
| last if (!$bl); # End of function | |
| } elsif ($tok eq 'word' && $val eq 'fprintf') { | |
| # fprintf ( st, "string" ,args ); | |
| # Save embedded comments, but don't confuse the parse. | |
| ($tok, $val) = gettok(\$comments); | |
| if( $tok ne '(' ) { | |
| $b .= " $val"; | |
| next; | |
| } | |
| ($tok, $val) = gettok(\$comments); | |
| if( $tok ne 'word' || $val ne 'st' ) { | |
| $b .= "fprintf ($val"; | |
| next; | |
| } | |
| ($tok, $val) = gettok(\$comments); | |
| if( $tok ne ',' ) { | |
| $b .= "fprintf (st$val"; | |
| next; | |
| } | |
| ($tok, $val) = gettok(\$comments); | |
| if( $tok ne 'QS' ) { | |
| $b .= "fprintf (st, $val"; | |
| next; | |
| } | |
| # Concatenate adjacent strings | |
| my $string = ''; | |
| while( $tok eq 'QS' ) { | |
| $string .= substr( $val, 1, length( $ val ) -2); | |
| ($tok, $val) = gettok(\$comments); | |
| } | |
| # Check for format codes. plain %s is all that can be automated | |
| if ($string =~ /(%[^%s])/) { | |
| print STDERR "Line $.: Unsupported format code $1 in help string. Please convert to %s\n"; | |
| } | |
| # Rework argument list | |
| my $arg = ''; | |
| my @vlist; | |
| my $pl = 1; # Paren level | |
| while( $tok eq ',' ) { | |
| ($tok, $val) = gettok(\$comments); | |
| while( $tok ne ',' ) { | |
| if( $tok eq '(' ) { | |
| $pl++; | |
| } elsif( $tok eq ')' ) { | |
| die "Unmatched )" if( --$pl < 0); | |
| last if( !$pl ); | |
| } | |
| $arg .= " $val"; | |
| ($tok, $val) = gettok(\$comments); | |
| } | |
| if( !length $arg ) { | |
| print STDERR "Line $.: null argument to fprintf in $rtn\n"; | |
| $string = "<<NULL>>"; | |
| } | |
| unless( exists $vargs{$arg} ) { # Assign each unique arg an index | |
| $vargs{$arg} = @vargs; | |
| push @vargs, $arg; | |
| } | |
| push @vlist, $vargs{$arg}; # Remember offset in this list | |
| $arg = ''; | |
| } | |
| die "Line $.: Missing ')' in fprintf\n" if( $tok ne ')' ); | |
| ($tok, $val) = gettok(\$comments); | |
| die "Line $.: Missing ';' in fprintf\n" if( $tok ne ';' ); | |
| # Replace each escape with positional %s in new list. | |
| my $n = 0; | |
| $string =~ s/%([.\dlhs# +Lqjzt-]*[diouxXeEfFgGaAcspnm%])/ | |
| sprintf "%%%us",$vlist[$n++]+1/eg; | |
| $help .= $string; | |
| next; | |
| } elsif ($tok eq 'word' && $val =~ /^fprint_(set|show|reg)_help(?:_ex)?$/) { | |
| my %alt = ( set => "\$Set commands", | |
| show => "\$Show commmands", | |
| reg => "\$Registers" ); | |
| $b .= "/* Use \"$alt{$1}\" topic instead:\n"; | |
| do { | |
| $b .= " $val"; | |
| ($tok, $val) = gettok (\$comments); | |
| } while ($tok ne ';'); | |
| $b .= ";\n*/\n"; | |
| next; | |
| } | |
| # Random function body content | |
| $b .= " $val"; | |
| } | |
| # End of function - output new one | |
| print $f; # Function header | |
| print "const char helpString[] =\n"; | |
| print << 'TEMPLATE_'; | |
| /* Template for re-arranging your help. | |
| * Lines marked with '+' in the translation seemed to be indented and will | |
| * indent 4 columns for each '+'. See scp_help.h for a worked-out example. | |
| * The '*'s in the next line represent the standard text width of a help line */ | |
| /****************************************************************************/ | |
| " Insert your device summary here. Keep it short. Be sure to put a leading\n" | |
| " space at the start of each line. Blank lines do appear in the output;\n" | |
| " don't add extras.\n" | |
| "1 Hardware Description\n" | |
| " The details of the hardware. Feeds & speeds are OK here.\n" | |
| "2 Models\n" | |
| " If the device was offered in distinct models, a subtopic for each\n" | |
| "3 Model A\n" | |
| " Description of model A\n" | |
| "3 Model B\n" | |
| " Description of model B\n" | |
| "2 $Registers\n" | |
| " The register list of the device will automagically display above this\n" | |
| " line. Add any special notes.\n" | |
| "1 Configuration\n" | |
| " How to configure the device under SimH. Use subtopics\n" | |
| " if there is a lot of detail.\n" | |
| "2 $Set commands\n" | |
| " The SET commands for the device will automagically display above\n" | |
| " this line. Add any special notes.\n" | |
| "2 OSNAME1\n" | |
| " Operating System-specif configuration details\n" | |
| " If the device needs special configuration for a particular OS, a subtopic\n" | |
| " for each such OS goes here.\n" | |
| "2 Files\n" | |
| " If the device uses external files (tapes, cards, disks, configuration)\n" | |
| " Create a subtopic for each here.\n" | |
| "3 Config file 1\n" | |
| " Description.\n" | |
| "2 Examples\n" | |
| " Provide usable examples for configuring complex devices.\n" | |
| " If the examples are more than a couple of lines, make a subtopic for each.\n" | |
| "1 Operation\n" | |
| " How to operate the device under SimH. Attach, runtime events\n" | |
| " (e.g. how to load cards or mount a tape)\n" | |
| "1 Monitoring\n" | |
| " How to obtain and interpret status\n" | |
| "2 $Show commands\n" | |
| " The SHOW commands for the device will automagically display above\n" | |
| " this line. Add any special notes.\n" | |
| "1 Restrictions\n" | |
| " If some aspects of the device aren't emulated or some host\n" | |
| " host environments that aren't (fully) supported, list them here.\n" | |
| "1 Debugging\n" | |
| " Debugging information - provided by the device. Tips for common problems.\n" | |
| "1 Related Devices\n" | |
| " If devices are configured or used together, list the other devices here.\n" | |
| " E.G. The DEC KMC/DUP are two hardware devices that are closely related;\n" | |
| " The KMC controlls the DUP on behalf of the OS.\n" | |
| /* **** Your converted help text starts hare **** */ | |
| TEMPLATE_ | |
| my @lines = split /(\\n|\n)/, $help; | |
| while( @lines ) { | |
| my $line = shift @lines; | |
| my $term = shift @lines; | |
| if ($term eq "\\n") { | |
| $line .= $term; | |
| $term = "\n"; | |
| } | |
| if( $line =~ s/^(\s+)// ) { | |
| $line = ('+' x ((length( $1 ) +3)/4)) . $line; | |
| } else { | |
| $line = ' ' . $line; | |
| } | |
| print " \"$line\"\n" ; | |
| } | |
| print " ;\n"; | |
| print $b; # Stuff from body of old function | |
| if( length $comments ) { | |
| print "\n$comments"; | |
| } | |
| # Call scp_help | |
| print "\nreturn scp_help (st, dptr, uptr, helpString, cptr"; | |
| %vargs = reverse %vargs; | |
| while( @vargs ) { | |
| print ",\n " . shift( @vargs ); | |
| } | |
| print ");\n}\n"; | |
| } else { | |
| if( $update ) { | |
| print $_; | |
| } | |
| next; | |
| } | |
| } | |
| exit (0); | |
| my @pending; | |
| sub nextc { | |
| if( @pending ) { | |
| my $c = shift @pending; | |
| return $c; | |
| } | |
| return getc; | |
| } | |
| sub gettoken { | |
| my $c; | |
| my $ql = 0; | |
| my $cl = 0; | |
| my $tok = ''; | |
| while( defined(($c = nextc())) ) { | |
| if( $cl ) { | |
| if( $c eq '*' ) { | |
| $c = nextc; | |
| die "EOF in comment\n" if( !defined $c ); | |
| if ($c eq '/') { | |
| $tok .= '*/'; | |
| return ('comment', $tok); | |
| } | |
| push @pending, $c; | |
| $c = '*'; | |
| } | |
| $tok .= $c; | |
| next; | |
| } elsif( $c eq '/' ) { | |
| $c = nextc; | |
| if( $c eq '*' ) { | |
| if (length $tok) { | |
| push @pending, '/', '*'; | |
| return ('word', $tok); | |
| } | |
| $cl = 1; | |
| $tok = '/*'; | |
| next; | |
| } | |
| push @pending, $c; | |
| $c = '/'; | |
| } | |
| if( $ql ) { | |
| if( $c eq '\\' ) { | |
| $c = nextc; | |
| die "EOF in string\n" if( !defined $c ); | |
| $tok .= "\\$c"; # eval "\"\\$c\""; | |
| next; | |
| } | |
| if( $c eq $ql ) { | |
| $tok .= $ql; | |
| return ("QS", $tok); | |
| } | |
| $tok .= $c; | |
| next; | |
| } | |
| if( $c eq '"' || $c eq "'" ) { | |
| $ql = $c; | |
| $tok = $c; | |
| next; | |
| } | |
| if ($c =~ /^\s$/) { | |
| if( length $tok ) { | |
| return ('word', $tok); | |
| } | |
| next; | |
| } | |
| if ($c =~ /^\w$/) { | |
| $tok .= $c; | |
| next; | |
| } | |
| if( length $tok ) { | |
| push @pending, $c; | |
| return ('word', $tok); | |
| } | |
| if ($c eq '-') { | |
| $c = nextc; | |
| if( $c =~ /^[>=-]$/ ) { | |
| return ('op', "-$c"); | |
| } | |
| push @pending, $c; | |
| return ('op', '-'); | |
| } | |
| if( $c eq '<' ) { | |
| $c = nextc; | |
| if( $c eq '=' ) { | |
| return ('op', "<$c"); | |
| } | |
| if( $c eq '<' ) { | |
| my $c2 = nextc; | |
| if( $c2 eq '=' ) { | |
| return ('op', "<<="); | |
| } | |
| push @pending, $c2; | |
| return ('op', '<<'); | |
| } | |
| push @pending, $c; | |
| return ('op', '<'); | |
| } | |
| if( $c eq '>' ) { | |
| $c = nextc; | |
| if( $c eq '=' ) { | |
| return ('op', ">$c"); | |
| } | |
| if( $c eq '>' ) { | |
| my $c2 = nextc; | |
| if( $c2 eq '=' ) { | |
| return ('op', ">>="); | |
| } | |
| push @pending, $c2; | |
| return ('op', '>>'); | |
| } | |
| push @pending, $c; | |
| return ('op', '>'); | |
| } | |
| if( $c eq '=' ) { | |
| $c = nextc; | |
| if( $c eq '=' ) { | |
| return ('op', '=='); | |
| } | |
| push @pending, $c; | |
| return ('op', '='); | |
| } | |
| if ($c =~ m,^[!*+/%&^|]$,) { | |
| my $c2 = nextc; | |
| if( $c2 eq '=' ) { | |
| return ('op', "$c$c2"); | |
| } | |
| push @pending, $c2; | |
| return ('op', $c); | |
| } | |
| if( $c =~ /^[&|]$/ ) { | |
| my $c2 = nextc; | |
| if( $c2 eq $c ) { | |
| return ('op', "$c$c"); | |
| } | |
| push @pending, $c2; | |
| return ('op', $c); | |
| } | |
| if ($c =~ /^[#}]$/ ) { | |
| return ($c, "\n$c"); | |
| } | |
| return ($c, ($c =~ /^[{;]$/? "$c\n" : $c)); | |
| } | |
| return (undef, '<<EOF>>'); | |
| } | |
| sub gettok { | |
| my $comments = $_[0]; | |
| while( 1 ) { | |
| my( $token, $value ) = gettoken(); | |
| return ($token, $value) if( !defined $token ); | |
| if( $token eq 'comment' && $comments ) { | |
| $$comments .= $value . "\n"; | |
| next; | |
| } | |
| return ($token, $value); | |
| } | |
| } |