From 00afa58bc41d4af2587a2a44042ed4ef6b635adf Mon Sep 17 00:00:00 2001 From: Mark Pizzolato Date: Fri, 22 Nov 2013 06:08:03 -0800 Subject: [PATCH] SCP: Added hierarchical help capability (from Timothe Litt) --- helpx | 514 ++++++++++++++++++++++++++++++ scp.c | 897 ++++++++++++++++++++++++++++++++++++++++++++++++++++- scp.h | 11 + scp_help.h | 256 +++++++++++++++ sim_defs.h | 2 + 5 files changed, 1666 insertions(+), 14 deletions(-) create mode 100644 helpx create mode 100644 scp_help.h diff --git a/helpx b/helpx new file mode 100644 index 00000000..d3b79680 --- /dev/null +++ b/helpx @@ -0,0 +1,514 @@ +#!/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( ) { + + # Look for the help function + if( /^(?:static\s+)?t_stat\s+$rtn\s*\(/ ) { + $line = $_; + while( $line !~ /\{/ && $line !~ /\)\s*;/ ) { + my $cont = ; + 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 = "<>"; + } + 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, '<>'); +} + +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); + } +} diff --git a/scp.c b/scp.c index edc96ba0..4fd9f768 100644 --- a/scp.c +++ b/scp.c @@ -228,10 +228,13 @@ #include #if defined(_WIN32) #include +#include +#include #else #include #endif #include +#include #if defined(HAVE_DLOPEN) /* Dynamic Readline support */ #include @@ -472,6 +475,7 @@ static double sim_time; static uint32 sim_rtime; static int32 noqueue_time; volatile int32 stop_cpu = 0; +static char **sim_argv; t_value *sim_eval = NULL; FILE *sim_log = NULL; /* log file */ FILEREF *sim_log_ref = NULL; /* log file file reference */ @@ -899,6 +903,7 @@ if (!sim_quiet) { if (sim_dflt_dev == NULL) /* if no default */ sim_dflt_dev = sim_devices[0]; +sim_argv = argv; cptr = getenv("HOME"); if (cptr == NULL) { cptr = getenv("HOMEPATH"); @@ -1259,17 +1264,17 @@ void fprint_show_help (FILE *st, DEVICE *dptr) fprint_show_help_ex (st, dptr, TRUE); } -t_stat help_dev_help (FILE *st, DEVICE *dptr, UNIT *uptr, char *cptr) +t_stat help_dev_help (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr) { char gbuf[CBUFSIZE]; CTAB *cmdp; if (*cptr) { - cptr = get_glyph (cptr, gbuf, 0); + char *gptr = get_glyph (cptr, gbuf, 0); if ((cmdp = find_cmd (gbuf))) { if (cmdp->action == &exdep_cmd) { - if (dptr->help) - dptr->help (st, dptr, uptr, 0, cptr); + if (dptr->help) /* Shouldn't this pass cptr so the device knows which command invoked? */ + return dptr->help (st, dptr, uptr, flag, gptr); else fprintf (st, "No help available for the %s %s command\n", cmdp->name, sim_dname(dptr)); return SCPE_OK; @@ -1286,23 +1291,22 @@ if (*cptr) { fprint_attach_help_ex (st, dptr, FALSE); return SCPE_OK; } - fprintf (st, "No %s help is available for the %s device\n", cmdp->name, dptr->name); if (dptr->help) - dptr->help (st, dptr, uptr, 0, cptr); + return dptr->help (st, dptr, uptr, flag, cptr); + fprintf (st, "No %s help is available for the %s device\n", cmdp->name, dptr->name); return SCPE_OK; } if (MATCH_CMD (gbuf, "REGISTERS") == 0) { fprint_reg_help_ex (st, dptr, FALSE); return SCPE_OK; } - fprintf (st, "No %s help is available for the %s device\n", gbuf, dptr->name); if (dptr->help) - dptr->help (st, dptr, uptr, 0, cptr); + return dptr->help (st, dptr, uptr, flag, cptr); + fprintf (st, "No %s help is available for the %s device\n", gbuf, dptr->name); return SCPE_OK; } if (dptr->help) { - dptr->help (st, dptr, uptr, 0, cptr); - return SCPE_OK; + return dptr->help (st, dptr, uptr, flag, cptr); } if (dptr->description) fprintf (st, "%s %s help\n", dptr->description (dptr), dptr->name); @@ -1321,6 +1325,8 @@ char gbuf[CBUFSIZE]; CTAB *cmdp; GET_SWITCHES (cptr); +if (sim_switches & SWMASK ('F')) + flag = flag | SCP_HELP_FLAT; if (*cptr) { cptr = get_glyph (cptr, gbuf, 0); if ((cmdp = find_cmd (gbuf))) { @@ -1337,9 +1343,9 @@ if (*cptr) { if (dptr == NULL) return SCPE_2MARG; } - r = help_dev_help (stdout, dptr, uptr, (cmdp->action == &set_cmd) ? "SET" : "SHOW"); + r = help_dev_help (stdout, dptr, uptr, flag, (cmdp->action == &set_cmd) ? "SET" : "SHOW"); if (sim_log) - help_dev_help (sim_log, dptr, uptr, (cmdp->action == &set_cmd) ? "SET" : "SHOW"); + help_dev_help (sim_log, dptr, uptr, flag | SCP_HELP_FLAT, (cmdp->action == &set_cmd) ? "SET" : "SHOW"); return r; } else @@ -1419,6 +1425,7 @@ if (*cptr) { else { DEVICE *dptr; UNIT *uptr; + t_stat r; dptr = find_unit (gbuf, &uptr); if (dptr == NULL) { @@ -1431,9 +1438,10 @@ if (*cptr) { fprintf (sim_log, "Device %s is currently disabled\n", dptr->name); } } - help_dev_help (stdout, dptr, uptr, cptr); + r = help_dev_help (stdout, dptr, uptr, flag, cptr); if (sim_log) - help_dev_help (stdout, dptr, uptr, cptr); + help_dev_help (sim_log, dptr, uptr, flag | SCP_HELP_FLAT, cptr); + return r; } } else { @@ -7127,3 +7135,864 @@ if (sim_deb && (dptr->dctrl & dbits)) { } return; } + +/* Hierarchical help presentation + * + * Device help can be presented hierarchically by calling + * + * t_stat scp_help (FILE *st, struct sim_device *dptr, + * struct sim_unit *uptr, int flag, const char *help, char *cptr) + * + * or one of its three cousins from the device HELP routine. + * + * *help is the pointer to the structured help text to be displayed. + * + * The format and usage, and some helper macros can be found in scp_help.h + * If you don't use the macros, it is not necessary to #include "scp_help.h". + * + * Actually, if you don't specify a DEVICE pointer and don't include + * other device references, it can be used for non-device help. + */ + +#define blankch(x) ((x) == ' ' || (x) == '\t') + +typedef struct topic { + uint32 level; + char *title; + char *label; + struct topic *parent; + struct topic **children; + uint32 kids; + char *text; + size_t len; + uint32 flags; + uint32 kidwid; +#define HLP_MAGIC_TOPIC 1 +} TOPIC; + +static volatile struct { + const char *error; + size_t block; + size_t line; +} help_where = { "", 0, 0 }; +jmp_buf (help_env); +#define FAIL(why,text) { help_where.error = #text; longjmp (help_env, (why)); } + +/* Add to topic text. + * Expands text buffer as necessary. + */ + +static void appendText (TOPIC *topic, const char *text, size_t len) { + char *newt; + if (!len) { + return; + } + + newt = (char *)realloc (topic->text, topic->len + len +1); + if (!newt) { + FAIL (SCPE_MEM, No memory); + } + topic->text = newt; + memcpy (newt + topic->len, text, len); + topic->len +=len; + newt[topic->len] = '\0'; + return; +} + +/* Release memory held by a topic and its children. + */ +static void cleanHelp (TOPIC *topic) { + TOPIC *child; + size_t i; + + free (topic->title); + free (topic->text); + free (topic->label); + for (i = 0; i < topic->kids; i++) { + child = topic->children[i]; + cleanHelp (child); + free (child); + } + free (topic->children); + return; +} + +/* Build a help tree from a string. + * Handles substitutions, formatting. + */ +static TOPIC *buildHelp (TOPIC *topic, struct sim_device *dptr, + struct sim_unit *uptr, const char *htext, va_list ap) { + char *end; + size_t n, ilvl; +#define VSMAX 100 + char *vstrings[VSMAX]; + size_t vsnum = 0; + char *astrings[VSMAX+1]; + size_t asnum = 0; + char *const *hblock; + const char *ep; + t_bool excluded = FALSE; + + /* variable arguments consumed table. + * The scheme used allows arguments to be accessed in random + * order, but for portability, all arguments must be char *. + * If you try to violate this, there ARE machines that WILL break. + */ + + memset (vstrings, 0, VSMAX * sizeof (char *)); + memset (astrings, 0, VSMAX * sizeof (char *)); + astrings[asnum++] = (char *) htext; + + for (hblock = astrings; (htext = *hblock) != NULL; hblock++) { + help_where.block = hblock - astrings; + help_where.line = 0; + while (*htext) { + const char *start; + + help_where.line++; + if (isspace (*htext) || *htext == '+') {/* Topic text, indented topic text */ + if (excluded) { /* Excluded topic text */ + while (*htext && *htext != '\n') { + htext++; + } + if (*htext) + ++htext; + continue; + } + ilvl = 1; + appendText (topic, " ", 4); /* Basic indentation */ + if (*htext == '+') { /* More for each + */ + while (*htext == '+') { + ilvl++; + appendText (topic, " ", 4); + htext++; + } + } + while (*htext && *htext != '\n' && isspace (*htext)) { + htext++; + } + if (!*htext) { /* Empty after removing leading spaces */ + break; + } + start = htext; + while (*htext) { /* Process line for substitutions */ + if (*htext == '%') { + appendText (topic, start, htext - start); /* Flush up to escape */ + switch (*++htext) { /* Evaluate escape */ + case 'U': + if (dptr) { + char buf[129]; + n = uptr? uptr - dptr->units: 0; + sprintf (buf, "%s%u", dptr->name, (int)n); + appendText (topic, buf, strlen (buf)); + } + break; + case 'D': + if (dptr) { + appendText (topic, dptr->name, strlen (dptr->name)); + break; + } + case 'S': + appendText (topic, sim_name, strlen (sim_name)); + break; + case '%': + appendText (topic, "%", 1); + break; + case '+': + appendText (topic, "+", 1); + break; + default: /* Check for vararg # */ + if (isdigit (*htext)) { + n = 0; + while (isdigit (*htext)) { + n += (n * 10) + (*htext++ - '0'); + } + if (( *htext != 'H' && *htext != 's') || + n == 0 || n >= VSMAX) + FAIL (SCPE_ARG, Invalid escape); + while (n > vsnum) { /* Get arg pointer if not cached */ + vstrings[vsnum++] = va_arg (ap, char *); + } + start = vstrings[n-1]; /* Insert selected string */ + if (*htext == 'H') { /* Append as more input */ + if (asnum >= VSMAX) { + FAIL (SCPE_ARG, Too many blocks); + } + astrings[asnum++] = (char *)start; + break; + } + ep = start; + while (*ep) { + if (*ep == '\n') { + ep++; /* Segment to \n */ + appendText (topic, start, ep - start); + if (*ep) { /* More past \n, indent */ + size_t i; + for (i = 0; i < ilvl; i++) { + appendText (topic, " ", 4); + } + } + start = ep; + } else { + ep++; + } + } + appendText (topic, start, ep-start); + break; + } + FAIL (SCPE_ARG, Invalid escape); + } /* switch (escape) */ + start = ++htext; + continue; /* Current line */ + } /* if (escape) */ + if (*htext == '\n') { /* End of line, append last segment */ + htext++; + appendText (topic, start, htext - start); + break; /* To next line */ + } + htext++; /* Regular character */ + } + continue; + } /* topic text line */ + if (isdigit (*htext)) { /* Topic heading */ + TOPIC **children; + TOPIC *newt; + char nbuf[100]; + + n = 0; + start = htext; + while (isdigit (*htext)) { + n += (n * 10) + (*htext++ - '0'); + } + if ((htext == start) || !n) { + FAIL (SCPE_ARG, Invalid topic heading); + } + if (n <= topic->level) { /* Find level for new topic */ + while (n <= topic->level) { + topic = topic->parent; + } + } else { + if (n > topic->level +1) { /* Skipping down more than 1 */ + FAIL (SCPE_ARG, Level not contiguous); /* E.g. 1 3, not reasonable */ + } + } + while (*htext && (*htext != '\n') && isspace (*htext)) { + htext++; + } + if (!*htext || (*htext == '\n')) { /* Name missing */ + FAIL (SCPE_ARG, Missing topic name); + } + start = htext; + while (*htext && (*htext != '\n')) { + htext++; + } + if (start == htext) { /* Name NULL */ + FAIL (SCPE_ARG, Null topic name); + } + excluded = FALSE; + if (*start == '?') { /* Conditional topic? */ + size_t n = 0; + start++; + while (isdigit (*start)) { /* Get param # */ + n += (n * 10) + (*start++ - '0'); + } + if (!*start || *start == '\n'|| n == 0 || n >= VSMAX) + FAIL (SCPE_ARG, Invalid parameter number); + while (n > vsnum) { /* Get arg pointer if not cached */ + vstrings[vsnum++] = va_arg (ap, char *); + } + end = vstrings[n-1]; /* Check for True */ + if (!end || !(toupper (*end) == 'T' || *end == '1')) { + excluded = TRUE; /* False, skip topic this time */ + if (*htext) + htext++; + continue; + } + } + newt = (TOPIC *) calloc (sizeof (TOPIC), 1); + if (!newt) { + FAIL (SCPE_MEM, No memory); + } + newt->title = (char *) malloc ((htext - start)+1); + if (!newt->title) { + free (newt); + FAIL (SCPE_MEM, No memory); + } + memcpy (newt->title, start, htext - start); + newt->title[htext - start] = '\0'; + if (*htext) + htext++; + + if (newt->title[0] == '$') { + newt->flags |= HLP_MAGIC_TOPIC; + } + + children = (TOPIC **) realloc (topic->children, + (topic->kids +1) * sizeof (TOPIC *)); + if (!children) { + free (newt->title); + free (newt); + FAIL (SCPE_MEM, No memory); + } + topic->children = children; + topic->children[topic->kids++] = newt; + newt->level = n; + newt->parent = topic; + n = strlen (newt->title); + if (n > topic->kidwid) { + topic->kidwid = n; + } + sprintf (nbuf, ".%u", topic->kids); + n = strlen (topic->label) + strlen (nbuf) + 1; + newt->label = (char *) malloc (n); + if (!newt->label) { + free (newt->title); + topic->children[topic->kids -1] = NULL; + free (newt); + FAIL (SCPE_MEM, No memory); + } + sprintf (newt->label, "%s%s", topic->label, nbuf); + topic = newt; + continue; + } /* digits introducing a topic */ + if (*htext == ';') { /* Comment */ + while (*htext && *htext != '\n') + htext++; + continue; + } + FAIL (SCPE_ARG, Unknown line type); /* Unknown line */ + } /* htext not at end */ + memset (vstrings, 0, VSMAX * sizeof (char *)); + vsnum = 0; + } /* all strings */ + + return topic; +} + +/* Create prompt string - top thru current topic + * Add prompt at end. + */ +static char *helpPrompt ( TOPIC *topic, const char *pstring, t_bool oneword ) { + char *prefix; + char *newp, *newt; + + if (topic->level == 0) { + prefix = (char *) calloc (2,1); + if (!prefix) { + FAIL (SCPE_MEM, No memory); + } + prefix[0] = '\n'; + } else { + prefix = helpPrompt (topic->parent, "", oneword); + } + + newp = (char *) malloc (strlen (prefix) + 1 + strlen (topic->title) + 1 + + strlen (pstring) +1); + if (!newp) { + free (prefix); + FAIL (SCPE_MEM, No memory); + } + strcpy (newp, prefix); + if (topic->level != 0) + strcat (newp, " "); + newt = (topic->flags & HLP_MAGIC_TOPIC)? + topic->title+1: topic->title; + if (oneword) { + char *np = newp + strlen (newp); + while (*newt) { + *np++ = blankch (*newt)? '_' : *newt; + newt++; + } + *np = '\0'; + } else { + strcat (newp, newt); + } + if (*pstring && *pstring != '?') + strcat (newp, " "); + strcat (newp, pstring); + free (prefix); + return newp; +} + +static void displayMagicTopic (FILE *st, struct sim_device *dptr, TOPIC *topic) { + char tbuf[CBUFSIZE]; + size_t i, skiplines; +#ifdef _WIN32 + FILE *tmp; + char *tmpnam; + do { + int fd; + tmpnam = _tempnam (NULL, "simh"); + fd = _open (tmpnam, _O_CREAT | _O_RDWR | _O_EXCL, _S_IREAD | _S_IWRITE); + if (fd != -1) { + tmp = _fdopen (fd, "w+"); + break; + } + } while (1); +#else + FILE *tmp = tmpfile(); +#endif + + if (!tmp) { + fprintf (st, "Unable to create temporary file: %s\n", strerror (errno)); + return; + } + + if (topic->title) + fprintf (st, "%s\n", topic->title+1); + + skiplines = 0; + if (!strcmp (topic->title+1, "Registers")) { + fprint_reg_help (tmp, dptr) ; + skiplines = 1; + } else if (!strcmp (topic->title+1, "Set commands")) { + fprint_set_help (tmp, dptr); + skiplines = 3; + } else if (!strcmp (topic->title+1, "Show commands")) { + fprint_show_help (tmp, dptr); + skiplines = 3; + } + rewind (tmp); + + /* Discard leading blank lines/redundant titles */ + + for (i =0; i < skiplines; i++) { + fgets (tbuf, sizeof (tbuf), tmp); + } + + while (fgets (tbuf, sizeof (tbuf), tmp)) { + if (tbuf[0] != '\n') { + fputs (" ", st); + } + fputs (tbuf, st); + } + fclose (tmp); +#ifdef _WIN32 + remove (tmpnam); + free (tmpnam); +#endif + return; +} +/* Flatten and display help for those who say they prefer it. + */ + +static t_stat displayFlatHelp (FILE *st, struct sim_device *dptr, + struct sim_unit *uptr, int32 flag, + TOPIC *topic, va_list ap ) { + size_t i; + + if (topic->flags & HLP_MAGIC_TOPIC) { + fprintf (st, "\n%s ", topic->label); + displayMagicTopic (st, dptr, topic); + } else { + fprintf (st, "\n%s %s\n", topic->label, topic->title); + } + + /* Topic text (for magic topics, follows for explanations) + * It's possible/reasonable for a magic topic to have no text. + */ + + if (topic->text) + fputs (topic->text, st); + + for (i = 0; i < topic->kids; i++) { + displayFlatHelp (st, dptr, uptr, flag, topic->children[i], ap); + } + + return SCPE_OK; +} + +#define HLP_MATCH_AMBIGUOUS (~0u) +#define HLP_MATCH_WILDCARD (~1U) +#define HLP_MATCH_NONE 0 +static int matchHelpTopicName (TOPIC *topic, const char *token) { + size_t i, match; + char cbuf[CBUFSIZE], *cptr; + + if (!strcmp (token, "*")) { + return HLP_MATCH_WILDCARD; + } + match = 0; + for (i = 0; i < topic->kids; i++) { + strcpy (cbuf,topic->children[i]->title + + ((topic->children[i]->flags & HLP_MAGIC_TOPIC)? 1 : 0)); + cptr = cbuf; + while (*cptr) { + if (blankch (*cptr)) { + *cptr++ = '_'; + } else { + *cptr = toupper (*cptr); + cptr++; + } + } + if (!strncmp (cbuf, token, strlen (token))) { + if (match) { + return HLP_MATCH_AMBIGUOUS; + } + match = i+1; + } + } + return match; +} + +/* Main help routine + * Takes a va_list + */ + +t_stat scp_vhelp (FILE *st, struct sim_device *dptr, + struct sim_unit *uptr, int32 flag, + const char *help, char *cptr, va_list ap) { + + TOPIC top = { 0, NULL, NULL, &top, NULL, 0, NULL, 0, 0}; + TOPIC *topic = ⊤ + int failed; + size_t match; + size_t i; + char *p; + t_bool flat_help = FALSE; + char cbuf [CBUFSIZE], gbuf[CBUFSIZE]; + + static const char attach_help[] = { " ATTACH" }; + static const char brief_help[] = { "%s help. Type to exit, HELP for navigation help" }; + static const char onecmd_help[] = { "%s help." }; + static const char help_help[] = { + /****|***********************80 column width guide********************************/ + " This help command provides hierarchical help. To see more information, type\n" + " an offered subtopic name. To move back a level, just type .\n" + " To review the current topic/subtopic, type \"?\".\n" + " To view all subtopics, type \"*\".\n" + " To exit help at any time, type EXIT.\n" + }; + + if ((failed = setjmp (help_env)) != 0) { + fprintf (stderr, "\nHelp was unable to process the help for this device.\n" + "Error in block %u line %u: %s\n" + "Please contact the device maintainer.\n", + (int)help_where.block, (int)help_where.line, help_where.error); + cleanHelp (&top); + return failed; + } + + /* Compile string into navigation tree */ + + /* Root */ + + if (dptr) { + p = dptr->name; + flat_help = (dptr->flags & DEV_FLATHELP) != 0; + } else { + p = sim_name; + } + top.title = (char *) malloc (strlen (p) + ((flag & SCP_HELP_ATTACH)? sizeof (attach_help)-1: 0) +1); + for (i = 0; p[i]; i++ ) { + top.title[i] = toupper (p[i]); + } + top.title[i] = '\0'; + if (flag & SCP_HELP_ATTACH) { + strcpy (top.title+i, attach_help); + } + + top.label = (char *) malloc (sizeof ("1")); + strcpy (top.label, "1"); + + flat_help = flat_help || !sim_ttisatty() || (flag & SCP_HELP_FLAT); + + if (flat_help) { + flag |= SCP_HELP_FLAT; + if (sim_ttisatty()) { + fprintf (st, "%s help.\nThis help is also available in hierarchical form.\n", top.title); + } else { + fprintf (st, "%s help.\n", top.title); + } + } else { + fprintf (st, ((flag & SCP_HELP_ONECMD)? onecmd_help: brief_help), top.title); + } + + /* Add text and subtopics */ + + (void) buildHelp (&top, dptr, uptr, help, ap); + + /* Go to initial topic if provided */ + + while (cptr && *cptr) { + cptr = get_glyph (cptr, gbuf, 0); + if (!gbuf[0]) { + break; + } + if (!strcmp (gbuf, "HELP")) { /* HELP (about help) */ + fputs (help_help, st); + break; + } + match = matchHelpTopicName (topic, gbuf); + if (match == HLP_MATCH_WILDCARD) { + displayFlatHelp (st, dptr, uptr, flag, topic, ap); + cleanHelp (&top); + return SCPE_OK; + } + if (match == HLP_MATCH_AMBIGUOUS) { + fprintf (st, "\n%s is ambiguous in %s\n", gbuf, topic->title); + break; + } + if (match == HLP_MATCH_NONE) { + fprintf (st, "\n%s is not available in %s\n", gbuf, topic->title); + break; + } + topic = topic->children[match-1]; + } + cptr = NULL; + + if (flat_help) { + displayFlatHelp (st, dptr, uptr, flag, topic, ap); + cleanHelp (&top); + return SCPE_OK; + } + + /* Interactive loop displaying help */ + + while (TRUE) { + char *pstring; + const char *prompt[2] = {"? ", "Subtopic? "}; + + /* Some magic topic names for help from data structures */ + + if (topic->flags & HLP_MAGIC_TOPIC) { + fputc ('\n', st); + displayMagicTopic (st, dptr, topic); + } else { + fprintf (st, "\n%s\n", topic->title); + } + + /* Topic text (for magic topics, follows for explanations) + * It's possible/reasonable for a magic topic to have no text. + */ + + if (topic->text) + fputs (topic->text, st); + + if (topic->kids) { + size_t w = 0; + char *p; + char tbuf[CBUFSIZE]; + + fprintf (st, "\n Additional information available:\n\n"); + for (i = 0; i < topic->kids; i++) { + strcpy (tbuf, topic->children[i]->title + + ((topic->children[i]->flags & HLP_MAGIC_TOPIC)? 1 : 0)); + for (p = tbuf; *p; p++) { + if (blankch (*p)) + *p = '_'; + } + w += 4 + topic->kidwid; + if (w > 80) { + w = 4 + topic->kidwid; + fputc ('\n', st); + } + fprintf (st, " %-*s", topic->kidwid, tbuf); + } + fprintf (st, "\n\n"); + if (flag & SCP_HELP_ONECMD) { + pstring = helpPrompt (topic, "", TRUE); + fprintf (st, "To view additional topics, type HELP %s topicname\n", pstring+1); + free (pstring); + break; + } + } + + if (!sim_ttisatty() || (flag & SCP_HELP_ONECMD)) + break; + + reprompt: + if (!cptr || !*cptr) { + pstring = helpPrompt (topic, prompt[topic->kids != 0], FALSE); + + cptr = read_line_p (pstring, cbuf, sizeof (cbuf), stdin); + free (pstring); + } + + if (!cptr) /* EOF, exit help */ + break; + + cptr = get_glyph (cptr, gbuf, 0); + if (!strcmp (gbuf, "*")) { /* Wildcard */ + displayFlatHelp (st, dptr, uptr, flag, topic, ap); + gbuf[0] = '\0'; /* Displayed all subtopics, go up */ + } + if (!gbuf[0]) { /* Blank, up a level */ + if (topic->level == 0) + break; + topic = topic->parent; + continue; + } + if (!strcmp (gbuf, "?")) { /* ?, repaint current topic */ + continue; + } + if (!strcmp (gbuf, "HELP")) { /* HELP (about help) */ + fputs (help_help, st); + goto reprompt; + } + if (!strcmp (gbuf, "EXIT") || !strcmp (gbuf, "QUIT")) { /* EXIT (help) */ + break; + } + + /* String - look for that topic */ + + if (!topic->kids) { + fprintf (st, "No additional help at this level.\n"); + cptr = NULL; + goto reprompt; + } + match = matchHelpTopicName (topic, gbuf); + if (match == HLP_MATCH_AMBIGUOUS) { + fprintf (st, "%s is ambiguous, please type more of the topic name\n", gbuf); + cptr = NULL; + goto reprompt; + } + + if (match == HLP_MATCH_NONE) { + fprintf (st, "Help for %s is not available\n", gbuf); + cptr = NULL; + goto reprompt; + } + /* Found, display subtopic */ + + topic = topic->children[match-1]; + } + + /* Free structures and return */ + + cleanHelp (&top); + + return SCPE_OK; +} + +/* variable argument list shell - most commonly used + */ + +t_stat scp_help (FILE *st, struct sim_device *dptr, + struct sim_unit *uptr, int32 flag, + const char *help, char *cptr, ...) { + t_stat r; + va_list ap; + + va_start (ap, cptr); + r = scp_vhelp (st, dptr, uptr, flag, help, cptr, ap); + va_end (ap); + + return r; +} + +#if 01 +/* Read help from a file + * + * Not recommended due to OS-dependent issues finding the file, + maintenance. + * Don't hardcode any path - just name.hlp - so there's a chance the file can + * be found. + */ + +t_stat scp_vhelpFromFile (FILE *st, struct sim_device *dptr, + struct sim_unit *uptr, int32 flag, + const char *helpfile, + char *cptr, va_list ap) { + FILE *fp; + char *help, *p; + t_offset size, n; + int c; + t_stat r; + + fp = sim_fopen (helpfile, "r"); + if (fp == NULL) { + if (sim_argv && *sim_argv[0]) { + char fbuf[(4*PATH_MAX)+1]; /* PATH_MAX is ridiculously small on some platforms */ + char *d = NULL; + + /* Try to find a path from argv[0]. This won't always + * work (one reason files are probably not a good idea), + * but we might as well try. Some OSs won't include a + * path. Having failed in the CWD, try to find the location + * of the executable. Failing that, try the 'help' subdirectory + * of the executable. Failing that, we're out of luck. + */ + strncpy (fbuf, sim_argv[0], sizeof (fbuf)); + if ((p = match_ext (fbuf, "EXE"))) { + *p = '\0'; + } + if ((p = strrchr (fbuf, '\\'))) { + p[1] = '\0'; + d = "%s\\"; + } else { + if ((p = strrchr (fbuf, '/'))) { + p[1] = '\0'; + d = "%s/"; +#ifdef VMS + } else { + if ((p = strrchr (fbuf, ']'))) { + p[1] = '\0'; + d = "[%s]"; + } +#endif + } + } + if (p && (strlen (fbuf) + strlen (helpfile) +1) <= sizeof (fbuf)) { + strcat (fbuf, helpfile); + fp = sim_fopen (fbuf, "r"); + } + if (!fp && p && (strlen (fbuf) + strlen (d) + sizeof ("help") + + strlen (helpfile) +1) <= sizeof (fbuf)) { + sprintf (p+1, d, "help"); + strcat (p+1, helpfile); + fp = sim_fopen (fbuf, "r"); + } + } + } + if (fp == NULL) { + fprintf (stderr, "Unable to open %s\n", helpfile); + return SCPE_UNATT; + } + + size = sim_fsize_ex (fp); /* Estimate; line endings will vary */ + + help = (char *) malloc ((size_t) size +1); + if (!help) { + fclose (fp); + return SCPE_MEM; + } + p = help; + n = 0; + + while ((c = fgetc (fp)) != EOF) { + if (++n > size) { +#define XPANDQ 512 + p = (char *) realloc (help, ((size_t)size) + XPANDQ +1); + if (!p) { + fclose (fp); + return SCPE_MEM; + } + help = p; + size += XPANDQ; + p += n; + } + *p++ = c; + } + *p++ = '\0'; + + fclose (fp); + + r = scp_vhelp (st, dptr, uptr, flag, help, cptr, ap); + free (help); + + return r; +} + +t_stat scp_helpFromFile (FILE *st, struct sim_device *dptr, + struct sim_unit *uptr, int32 flag, + const char *helpfile, char *cptr, ...) { + t_stat r; + va_list ap; + + va_start (ap, cptr); + r = scp_vhelpFromFile (st, dptr, uptr, flag, helpfile, cptr, ap); + va_end (ap); + + return r; +} +#endif diff --git a/scp.h b/scp.h index 5d4afad3..7e6fe4d9 100644 --- a/scp.h +++ b/scp.h @@ -155,6 +155,17 @@ void _sim_debug (uint32 dbits, DEVICE* dptr, const char* fmt, ...); #define sim_debug(dbits, dptr, ...) if (sim_deb && ((dptr)->dctrl & dbits)) _sim_debug (dbits, dptr, __VA_ARGS__); else (void)0 #endif void fprint_stopped_gen (FILE *st, t_stat v, REG *pc, DEVICE *dptr); +#define SCP_HELP_FLAT (1u << 31) /* Force flat help when prompting is not possible */ +#define SCP_HELP_ONECMD (1u << 30) /* Display one topic, do not prompt */ +#define SCP_HELP_ATTACH (1u << 29) /* Top level topic is ATTACH help */ +t_stat scp_help (FILE *st, struct sim_device *dptr, + struct sim_unit *uptr, int32 flag, const char *help, char *cptr, ...); +t_stat scp_vhelp (FILE *st, struct sim_device *dptr, + struct sim_unit *uptr, int32 flag, const char *help, char *cptr, va_list ap); +t_stat scp_helpFromFile (FILE *st, struct sim_device *dptr, + struct sim_unit *uptr, int32 flag, const char *help, char *cptr, ...); +t_stat scp_vhelpFromFile (FILE *st, struct sim_device *dptr, + struct sim_unit *uptr, int32 flag, const char *help, char *cptr, va_list ap); /* Global data */ diff --git a/scp_help.h b/scp_help.h new file mode 100644 index 00000000..f5c92e7b --- /dev/null +++ b/scp_help.h @@ -0,0 +1,256 @@ +/* scp_help.h: hierarchical help definitions + + 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. +*/ + +#ifndef SCP_HELP_H_ +#define SCP_HELP_H_ 0 + +/* The SCP structured help uses help text that defines a hierarchy of information + * organized into topics and subtopics. This is modelled on the VMS help command. + * + * This arrangement allows the long help messages being used in many devices to be + * organized to be easier to approach and navigate. + * + * The helpx utility (a Perl script) will read a device source file + * that conforms to the previous conventions, and produce a template and + * initial translation into the format described here. + * + * The structure of the help text is: + * + * Lines beginning with whitespace are displayed as part of the current topic, except: + * * The leading white space is replaced by a standard indentation of 4 spaces. + * Additional indentation, where appropriate, can be obtained with '+', 4 spaces each. + * + * * The following % escapes are recognized: + * * %D - Inserts the name of the device (e.g. "DTA"). + * * %U - Inserts the name of the unit (e.g. "DTA0"). + * * %S - Inserts the current simulator name (e.g. "PDP-10") + * * %#s - Inserts the string suppled in the "#"th optional argument to the help + * routine. # starts with 1. Any embedded newlines will cause following + * text to be indented. + * * %#H - Appends the #th optional argument to the help text. Use to add common + * help to device specific help. The text is placed AFTER the current help + * string, and after any previous %H inclusions. Parameter numbers restart + * with the new string, following the last parameter used by the previous tree. + * * %% - Inserts a literal %. + * * %+ - Inserts a literal +. + * - Any other escape is reserved and will cause an exception. However, the goal + * is to provide help, not a general formatting facility. Use sprintf to a + * local buffer, and pass that as a string if more general formatting is required. + * + * Lines beginning with a number introduce a subtopic of the device. The number indicates + * the subtopic's place in the help hierarchy. Topics offered as Additional Information + * under the device's main topic are at level 1. Their sub-topics are at level 2, and + * so on. Following the number is a string that names the sub-topic. This is displayed, + * and what the user types to access the information. Whitespace in the topic name is + * typed as an underscore (_). Topic names beginning with '$' invoke other kinds of help, + * These are: + * $Registers - Displays the device register help + * $Set commands - Displays the standard SET command help. + * $Show commands - Displays the standard SHOW command help. + * + * For these special topics, any text that you provide will be added after + * the output from the system routines. This allows you to add more information, or + * an introduction to subtopics with more detail. + * + * Topic names that begin with '?' are conditional topics. + * Some devices adopt radically different personalities at runtime, + * e.g. when attached to a processor with different bus. + * In rare cases, it's better not to include help that doesn't apply. + * For these cases, ?#, where # is a 1-based parameter number, can be used + * to selectively include a topic. If the specified parameter is TRUE + * (a string with the value "T", "t" or '1'), the topic will be visible. + * If the parameter is FALSE (NULL, or a string with any other value), + * the topic will not be visible. + * + * If it can be determined at compile time whether the topic in question + * is needed, #ifdef around those lines of the help is a better choice. + * + * If both $ and ? are used, ? comes first. + * + * Guidelines: + * Help should be concise and easy to understand. + * + * The main topic should be short - less than a sceenful when presented with the + * subtopic list. + * + * Keep line lengths to 76 columns or less. + * + * Follow the subtopic naming conventions (under development) for a consistent style: + * + * At the top level, the device should be summarized in a few sentences. + * The subtopics for detail should be: + * Hardware Description - The details of the hardware. Feeds & speeds are OK here. + * Models - If the device was offered in distinct models, a subtopic for each. + * Registers - Register descriptions + * + * Configuration - How to configure the device under SimH. SET commands. + * Operating System - If the device needs special configuration for a particular + * OS, a subtopic for each such OS goes here. + * Files - If the device uses external files (tapes, cards, disks, configuration) + * A subtopic for each here. + * Examples - Provide usable examples for configuring complex devices. + * + * Operation - How to operate the device under SimH. Attach, runtime events + * (e.g. how to load cards or mount a tape) + * + * Monitoring - How to obtain status (SHOW commands) + * + * Restrictions - If some aspects of the device aren't emulated, list them here. + * + * Debugging - Debugging information + * + * Related Devices - If devices are configured or used together, list the other devices here. + * E.G. The DEC KMC/DUP are two hardware devices that are closely related; + * The KMC controlls the DUP on behalf of the OS. + * + * This text can be created by any convenient means. It can be mechanically extracted from the device + * source, read from a file, or simply entered as a string in the help routine. To facilitate the latter, + * this file defines two convenience macros: + * + * L(text) - provides a string with a leading space and a trailing \n. Enter a line of topic text. + * T(n, NAME) - provides a string with the topic level n and the topic name NAME, and a trailing \n. + * + * These are concatenated normally, e.g. + const char *const help = + L (The %D device is interesting) + L (It has lots of help options) + T (1, TOPIC 1) + L (And this is topic 1) + ; + * + * API: + * To make use of this type of help in your device, create (or replace) a help routine with one that + * calls scp_help. Most of the arguments are the same as those of the device help routine. + * + * t_stat scp_help (FILE *st, struct sim_device *dptr, + * struct sim_unit *uptr, int flag, const char *help, char *cptr, ...) + * + * If you need to pass the variable argument list from another routine, use: + * + * t_stat scp_vhelp (FILE *st, struct sim_device *dptr, + * struct sim_unit *uptr, int flag, const char *help, char *cptr, va_list ap) + * + * To obtain the help from an external file (Note this reads the entire file into memory): + * t_stat scp_helpFromFile (FILE *st, struct sim_device *dptr, + * struct sim_unit *uptr, int flag, const char *helpfile, char *cptr, ...) + * and for va_list: + * t_stat scp_vhelpFromFile (FILE *st, struct sim_device *dptr, + * struct sim_unit *uptr, int flag, const char *helpfile, char *cptr, va_list ap) { + * + * dptr and uptr are only used if the %D and/or %U escapes are encountered. + * help is the help text; helpfile is the help file name. + * + * flag is usually the flag from the help command dispatch. SCP_HELP_FLAT is set in non-interactive + * environments. When this flag, or DEV_FLATHELP in DEVICE.flags is set, the entire help text + * will be flattened and displayed in outline form. + * + * Help files are easier to edit, but can become separated from the SimH executable. Finding them + * at runtime can also be a challenge. SimH tries...but the project standard is to embed help + * as strings in the device. (It may be easier to develop help as a file before converting it + * to a string.) + * + * Lines beginning with ';' will be ignored. + * + * Here is a worked-out example: + * +;**************************************************************************** + The Whizbang 100 is a DMA line printer controller used on the Whizbang 1000 + and Gurgle 1200 processor familes of the Obsolete Hardware Corporation. +1 Hardware Description + The Whizbang 100 is specified to operate "any printer you and a friend can + lift", and speeds up to 0.5 C. + + The controller requires a refrigerator-sized box, consumes 5.5KW, and is + liquid cooled. It uses GBL (Granite Baked Logic). + + Painted a cool blue, it consistently won industrial design awards, even + as mechanically, it was less than perfect. Plumbers had full employment. +2 Models + The Whizbang 100 model G was commissioned by a government agency, which + insisted on dull gray paint, and speeds limited to 11 MPH. + + The Whizbang 100 Model X is powered by the improbability drive, and is + rarely seen once installed. +2 $Registers + The two main registers are the Print Control register and the Print Data + register. The Print Maintenance register is usually broken. +3 Print Control register + Bit 0 turns the attached printer on when set, off when clear. + Bit 1 ejects the current page + Bit 2 ejects the operator + Bit 3 enables interrupts +3 Print data register + The print data register is thiry-seven bits wide, and accepts data in + elephantcode, the precursor to Unicode. Paper advance is accomplished + with the Rocket Return and Page Trampoline characters. +1 Configuration + The Whizbang 100 requires 4 address slots on the LooneyBus. ++ SET WHIZBANG LUNA 11 + will assign the controller to its default bus address. +2 $Set commands + The output codeset can be ASCII or elephantcode ++ SET WHIZBANG CODESET ASCII ++ SET WHIZBANG CODESET ELEPHANTCODE + + The VFU (carriage control tape) is specifed with ++ SET WHIZBANG TAPE vfufile +2 WOS + Under WOS, the device will only work at LooneyBus slot 9 +2 RTG + The RTG driver has been lost. It is not known if the + Whizbang will operate correctly. +2 Files + The VFU is programmed with an ASCII text file. Each line of the + file corresponds to a line of the form. Enter the channel numbers + as base 33 roman numerals. +2 Examples + TBS +1 Operation + Specify the host file to receive output using the ++ATTACH WHIZBANG filespec + command. +1 Monitoring + The Whizbang has no lights or switches. The model X may be located + with the ++SHOW WHIZBANG LOCATION + simulator command. +2 $Show commands +1 Restrictions + The emulator is limited to a single Whizbang controller. +1 Debugging + The only implemented debugging command is ++ SET WHIZBANG DEBUG=PRAY + To stop: ++ SET WHIZBANG NODEBUG=PRAY +1 Related Devices + See also the Whizbang paper shredder (SHRED). + * + */ + +#define T(level, text) #level " " #text "\n" +#define L(text) " " #text "\n" + +#endif diff --git a/sim_defs.h b/sim_defs.h index 13c67b88..e60eb18e 100644 --- a/sim_defs.h +++ b/sim_defs.h @@ -391,6 +391,7 @@ struct sim_device { #define DEV_S_TYPE 3 /* Width of Type Field */ #define DEV_V_SECTORS 7 /* Unit Capacity is in 512byte sectors */ #define DEV_V_DONTAUTO 8 /* Do not auto detach already attached units */ +#define DEV_V_FLATHELP 9 /* Use traditional (unstructured) help */ #define DEV_V_UF_31 12 /* user flags, V3.1 */ #define DEV_V_UF 16 /* user flags */ #define DEV_V_RSV 31 /* reserved */ @@ -401,6 +402,7 @@ struct sim_device { #define DEV_DEBUG (1 << DEV_V_DEBUG) /* device supports SET DEBUG command */ #define DEV_SECTORS (1 << DEV_V_SECTORS) /* capacity is 512 byte sectors */ #define DEV_DONTAUTO (1 << DEV_V_DONTAUTO) /* Do not auto detach already attached units */ +#define DEV_FLATHELP (1 << DEV_V_FLATHELP) /* Use traditional (unstructured) help */ #define DEV_NET 0 /* Deprecated - meaningless */