Appendix C. Listing

Editor's note. These listings have been scanned from a paper copy. I've proofed the text, but typos may remain.

This appendix contains listings of the functions that have been written for PAL, the functions that were developed but not used (analystr.fun and showparm.fun), tidy.bat, and the Prolog prototype spelling assistant (proto.pro), in that order.

The order of the PAL function listings is that of the include files under function definitions, in palfun.c. The filename is shown in block letters at the start of each file, and function names are in bold lowercase. No changes have been made to the code, although there has been some reformatting of text.


/* PALFUN.C */
#include <ctype.h>
#include <string.h>
#include <stdio.h>
#include <graph.h>
#include <stdarg.h>

#define AND &&
#define HELPKEY 59 /* 0;59 = Fl */
#define MAXANSLEN 80
/* WARNING: if this maximum length of answer is changed, corresponding 
   initialisation of ANSWER in hidden.inc must be changed */

#define NOT !
#define OR ||

/* ----------------------- WINDOW PARAMETERS ------------ */
#define HEAD_R1 1                  /* first row of header */
#define HEAD_Cl 1                  /* first column of header */
#define HEAD_R2 1                  /* last row of header */
#define HEAD_C2 vc.numtextcols     /* last column of header */
#define MAIN_R1 2                  /* first row of main screen */
#define MAIN_Cl 1                  /* first column of main screen */
#define MAIN_R2 vc.numtextrows-1   /* last row of main screen */
#define MAIN_C2 vc.numtextcols     /* last column of main screen */
#define MESS_R1 vc.numtextrows     /* first row of message area */
#define MESS_Cl 1                  /* first column of message area */
#define MESS_R2 vc.numtextrows     /* last row of message area */
#define MESS_C2 vc.numtextcols     /* last column of message area */

/* screen window coordinates defined using above values */
#define HEADCOORD HEAD_R1,HEAD_Cl,HEAD_R2,HEAD_C2
#define MAINCOORD MAIN_R1,MAIN_Cl,MAIN_R2,MAIN_C2
#define MESSCOORD MESS_RI,MESS_Cl,MESS_R2,MESS_C2

enum boolean { FALSE, TRUE };
enum mres
{ HELPREQ, MATCH, NOANS, NOMATCH, PM1, PM2, PM3, PM4, PM5,
  PM6, PM7, PM8, OPT1, OPT2, OPT3, OPT4, OPT5, OPT6, AE};

struct matchparams
{
  enum boolean blanktest;
  enum boolean casetest;
  enum boolean puncttest;
  enum boolean spelltest;
};

struct matchparams defaultmp = { FALSE, FALSE, FALSE, FALSE };
struct matchparams currentmp = { FALSE, FALSE, FALSE, FALSE };
struct videoconfig vc;
struct rccoord oldpos;


/* -------------------  FUNCTION DECLARATIONS  --------------- */

enum mres ( char *model, char *answer, char *mps );
extern void authprog( void );
void cleanstr( char *raw, char *conv );
void clearall( void );
void clearbetween( int rowl, int row2 );
void cleardisplay( void );
enum mres findword( char *model, char *answer, char *mps );
void getanswer( char *answer, int length );
void header( char *sometext );
enum boolean ispunctchar( char ch );
enum boolean legalsmp( char *mpstring );
enum mres match_any(int reqmatches, char *model, char *answer, char *mps );
enum mres mc(int numb_of_optns, char *stem, char *option, 
     [editor's note: code is missing from the paper copy]

/* Can't use eg #define mc2 mc(2, hence these functions to call mc */
enum mres mc2( char *st, char *ol, char *o2
enum mres mc3( char *st, char *ol, char *o2, char *o3
enum mres mc4( char *st, char *ol, char *o2, char *o3, char *o4 );
enum mres mc5( char *st, char *ol, char *o2, char *o3, char *o4, char *o5 );
enum mres mc6( char *st, char *ol, char *o2, char *o3, char *o4, char *o5, char *o6 );

void message( char *sometext);
void pause( void );
void putcursor( int rowpos, int colpos );
void setcurrentmp(char *matchstring);
void set_match_params( char *matchstring);
enum mres test( char *modelstr, char *answerstr );
void text( char *sometext );


/*  ---------------------    FUNCTION DEFINITIONS   -------------- */

#include "alphamat.fun"   /* alpha match */
#include "cleanstr.fun"   /* for alphamatch, findword, matchany */
#include "clear.fun"      /* all clear functions */
#include "findword.fun"   /* findword */
#include "get.fun"        /* getanswer */
#include "header.fun"     /* header */
#include "ispunctn.fun"   /* ispunctchar */
#include "matchprm.fun"   /* set_match_params, setcurrentmp */
#include "matchany.fun"   /* matchany */
#include "mc.fun"         /* multiple choice function */
#include "message.fun"
#include "pause.fun"      /* pause */
#include "putcurs.fun"    /* putcursor */
#include "test_vl.fun"    /* test for match */

void text(char *sometext)
{ _outtext(sometext); }
/* This is defined as 'write' in hidden.inc. It could not be called by this 
   name due to interference with write.asm The function should be made to 
   accept control characters, so that variables can be printed */

main()
{
  _getvideoconfig( &vc );
  /* suitable message if can't find one */
  _clearscreen( _GCLEARSCREEN );
  _settextwindow( MAINCOORD );
  authprog();      /* this will contain the author's code */
  /* when authprog has finished, all author errors could be 
  displayed - simply define an errorbuffer globally, and 
  AE function could write to it */
}


/* HIDDEN.INC */
void authprog()
{
#define ALL "B C S P"    /* match parameter string */
#define AND &&
#define ANSWER char      /* use in conjunction with SET */
#define DEF "default"    /* match parameter string */
#define END }
#define IS ==
#define ISNT  !=
#define NONE " "         /* use with message, header, s_m_params */
#define OR ||
#define MATCHRES int     /* results of the enum type mres */
#define SET [80] = " "   /* 80 = MAXANSLEN */

/* The following correspond to the enum type mres, which is used in the
   matching functions. If the order of the results in the enum type is 
   changed, so must the values here, otherwise inconsistencies will arise. */

#define HELPREQ 0
#define MATCH 1
#define NOANS 2
#define NOMATCH 3
#define PM1 4
#define PM2 5
#define PM3 6
#define PM4 7
#define PM5 8
#define PM6 9
#define PM7 10
#define PM8 11
#define OPT1 12
#define OPT2 13
#define OPT3 14
#define OPT4 15
#define OPT5 16
#define OPT6 17

/* so author doesn't type () at end of these functions */
#define clearall clearall()
#define cleardisplay cleardisplay()
#define pause pause()

#define write text
/* couldn't make a function called write because of write.asm, 
   so have to rename */


/* ALPHAMAT.FUN */
enum mres alphamatch(char *model, char *answer, char *mps)
{
  char tidymod[MAXANSLEN], tidyans[MAXANSLEN];
  strcpy(tidymod, "+");   /* initialisation */
  strcpy(tidyans, "+");

  /* No attempt to answer */
  { if( strlen( answer ) == 0 ) return NOANS; }

  if( strcmp(answer, "?") == 0) return HELPREQ;
    /* help requested, no need to test */
  else /* analyse answer */
  {
    setcurrentmp( mps); /* set current match params */
    cleanstr( model, tidymod );
    cleanstr( answer, tidyans );
    
    return( test( tidymod, tidyans ));
    /* ie MATCH or NOMATCH */
  }
}


/* CLEANSTR.FUN */
void cleanstr( char *raw , char *conv )
{
/* Takes a string (raw), and manipulates it according to the current 
   match parameters. Options are to convert to upper case, strip 
   punctuation, and remove unnecessary blanks. */

  int convcounter;
  char temp[MAXANSLEN];
  char *ptr;
  strcpy( conv, raw );

  if( NOT currentmp.casetest)   /* convert to upper case */
    { strcpy(temp, conv); strcpy(conv, strupr(temp) ); }

  if( NOT currentmp.puncttest)
  /* punctuation unimportant, strip it from string */
  {
    strcpy(temp, conv); ptr = temp; convcounter = 0;
    while( *ptr )
    {
      if( ! ispunctchar( *ptr ))
        { conv[convcounter] = *ptr; convcounter++; }
      ptr++;
    }
    conv[convcounter] = '\0';
  }

  /* Blanks unimportant, remove from string, but leave one blank 
     between words. */
  if( NOT currentmp.blanktest )
  {
    strcpy(temp, conv); ptr = temp;
    conv[0] = ' '; convcounter = 1;
    while( *ptr)
    {
      if( ( *ptr !=  ' ' || (conv[convcounter-1] != ' ') )
        { conv[convcounter] = *ptr; convcounter++; }
      ptr++;
    }
    if( conv[convcounter-1) == ' ')
      conv[convcounter] = '\01';
    else
      { conv[convcounter] ' '; conv[convcounter+l] = '\0'; }
  } /* endif not blanktest */
}


/* CLEAR.FUN */
void clearbetween(int rowl, int row2)
{
  #define bottom MAIN_R2 - HEAD_R2
  /* check row coordinates are legal */
  if( rowl >= 1 AND row2 <= bottom AND rowl <= row2 )
  {
    oldpos = _gettextposition();

    if( oldpos.row >= rowl AND oldpos.row <= row2 )
    {
      /* if cursor in range to clear, reset start of first cleared row */
      oldpos.row = rowl;
      oldpos.col = 1;
    }
    _settextwindow( rowl+MAIN_R1-1, MAIN_C1, row2+MAIN_R1-1, MAIN_C2 );
    /* add row numbers to main window top row because settextwindow 
       uses absolute screen values */
    _clearscreen( _GWINDOW );
    _settextwindow( MAINCOORD
    _settextposition(oldpos.row, oldpos.col);
  }

  else /* illegal row parameters */
  {
    char tb[250]; /*text buffer */
    int c; /* counter for buffer */
    _clearscreen( _GWINDOW );
    c = sprintf(tb, "Author error in clearbetween: illegal row parameters.");
    c+= sprintf(tb+c,"\n row values must be between 1 and %d inclusive, 
and rowl <= row2", bottom);
    c+= sprintf(tb+c,"\n You set: rowl = %d, row2 = %d",rowl,row2);
    c+= sprintf(tb+c,"\n Press any key to continue... ");
    _outtext(tb);
    getch();
    _clearscreen( _GWINDOW );
  }
}

void clearall(void)
{
  _clearscreen( _GCLEARSCREEN );
  _settextwindow( MAINCOORD );
}

void cleardisplay(void)
{
  _clearscreen( _GWINDOW );
}


/* FINDWORD.FUN */<
enum mres findword( char *model, char *answer, char *mps )
{
  /* Tokenise answer, test each token for match with model, 
     return MATCH if a match is found, NOMATCH if no match found,
     NOANS if no attempt was made to answer (ie empty string), 
     HELPREQ if Fl was pressed. */
  #define maxwords 9
  char tidymod[MAXANSLEN], tidyans[MAXANSLEN], seps[]
  char toks[maxwords][MAXANSLEN]; /* max nine tokens */
  char *p;
  int count, numb;
  
  /* No attempt to answer */
  { if( strlen( answer == 0 ) return NOANS; }
  
  if( strcmp(answer, "?") == 0) return HELPREQ;
    /* help requested, no need to test */
  else /* analyse answer */
  {
    setcurrentmp( mps );   /* set current match params */
    strcpy(tidymod," ");
    /* initialise, so tidymod points somewhere */
    strcpy(tidyans, " ");
    cleanstr( model, tidymod );
    cleanstr( answer, tidyans );
  
    /* break into tokens */
    count = 0;
    p = strtok( tidyans, seps );
    while( (p != NULL) AND ( count < maxwords ) )
    {
      strcpy( toks[count], " ");    /* leading space */
      strcat( toks[count], p );
      strcat( toks[count], " ");    /* trailing space */
      p = strtok( NULL, seps);
      count++;
      numb = count;
    }
  
    for( count = 0; count < numb; count++)
    /* find first match */
    if( test( tidymod, toks[count] ) == MATCH )
      return MATCH;
    return NOMATCH; /* no match found */
  }  /* endelse analyse answer */
}


/* GET.FUN */
void getanswer( char *answer, int length)
{
  char currchar[2];      /* to convert char to string  */
  char prnbuf[170];
  int pos = 0, key,  counter;
  struct rccoord textpos;
  
  oldpos = _gettextposition();
  
  if( (length+oldpos.col <= MAIN_C2) AND (length > 0) AND (length < MAXANSLEN) )
  { /* length of allowed answer < right extremity of window */
  for( counter = 1; counter <= length; counter += 1)
    _outtext("_");
    _settextposition(oldpos.row, oldpos.col);
    message("Type answer, press Enter. Back arrow to delete. Fl = Help");

  /* Can't use a textwindow to constrain the length of an input string 
     therefore can't use gets(). Can't use ungetch(), as it may only be 
     used once. Therefore, code in while loop below is necessary */
  while(l)
  {
    key = getch();             /* don't echo yet */
    if( key == 13 ) break;     /* Enter key pressed */
    if( key == 0 )             /* extended key pressed */
    {
      if( getch() == HELPKEY)
      /* help requested, return "?" */
      { answer[0] = '?'; pos = 1; break; }
    }
    if( isprint(key) AND (pos < length ))
    { /* It's a printable character, display, and store */
      answer[pos] = (char)key;
      currchar[0] = (char)key;
      currchar[l] = '\0'; /* key to string */
      _outtext( currchar );
      pos += 1;
      /* Alternative is to use printf and keep track of cursor location. 
         This is necessary if the arrow keys are used, so that the cursor 
         location and answer position are always synchronised. The use of
         switch is probably better in this case, since a lot of redundant
         if clauses would not be tested */
    }
    if( (pos > 0) AND (key == 8) )  /* backspace key == 8 */
    {
      pos -= 1;          /* overwrite answer position */
      textpos = _gettextposition();
      _settextposition(textpos.row, textpos.col-1);
      /* go back one column */
      _outtext("_"); /* remove last char from screen */
      _settextposition(textpos.row, textpos.col-1);
      /* don't want line */
    }
  } /* endwhile */
  answer[pos] = '\0'; /* strings terminate with null */
  } /* endif */
  else
  {
    sprintf(prnbuf,"\nAuthor error in getanswer:\n length 
less than 1 or length+current position > border.\nor length > maximum 
allowed length %d\n Press any key to continue...", MAXANSLEN -1);
    _outtext(prnbuf);
    getch();
    _clearscreen( GWINDOW );
    answer[0] = '\0';
  }
}


/* HEADER.FUN */
void header( char *sometext)
{
  char printbuff[200];
  if( strlen( sometext ) > ( HEAD_C2 - HEAD_C1 +1 ) )
  {
    _clearscreen( _GWINDOW );
    sprintf( printbuff, "Author error in header: length of message too long.\n
maximum %d characters allowed. Your message is %d characters\npress any key to 
continue..." HEAD_C2 - HEAD_C1 +1 ), strlen( sometext ) );
    _outtext( printbuff );
    getch();
    _clearscreen( _GWINDOW );
  }

  else
  {
    oldpos = _gettextposition();
    _settextwindow( HEADCOORD );
    _clearscreen( _GWINDOW );
    _outtext( sometext);
    _settextwindow( MAINCOORD );
    settextposition(oldpos.row, oldpos.col);
  }
}


/* ISPUNCTN.FUN */
enum boolean ispunctchar( char ch )
{
/* NOTE WELL the format for defining punctuation characters.
   It can be changed at will, but note that wild characters used in 
   matching functions are ? and *. If they are defined as punctuation, 
   then they should not be used in model answers, as they will be 
   removed. Note the backslash preceding double and single quotes. 
   The double backslash represents a single ASCII backslash. */
  char punctuation[] = "!\"$^&{}[]:;@_\',./~#|\\";
  char *ptr = punctuation;
  while( *ptr )         /* go through punctuation array */
    if( *ptr == ch) return TRUE; /* match found */
    else *ptr++;
  return FALSE;   /* no match, ch is not punctuation */
}


/* MATCHANY.FUN */
enum mres match_any(int reqmatches, char *model, char *answer, char *mps )
{
  /* Tokenise model and answer strings, test each token for match with model, 
     return MATCH if required number of matches found, NOMATCH if no matches 
     at all, and PM1 to PM8 if only some of the required matches found. */
  
  #define maxwords 9
  #define minwords 2
  
  char tidymod[MAXANSLEN], tidyans[MAXANSLEN], seps[] = " ";
  char anstoks[maxwords)[MAXANSLEN];
  char modtoks[maxwords][MAXANSLEN];
  char printbuff[150];
  char *p;
  int wordsinmodel, wordsinans, count, modcount, wordsfound = 0;
  
  if( reqmatches < minwords OR reqmatches > maxwords)
  {
    sprintf( printbuff, "\n Author error in match any: required 
number of matches is out of range (%d to %d).", minwords, maxwords );
    goto error;
  }
  if( strlen( answer ) == 0 ) return NOANS;
  /* No attempt to answer */
  
  if( strcmp(answer, "?") == 0) return HELPREQ;
  /* help requested, no need to test */
  
  else /* analyse answer */
  {
    setcurrentmp( mps );
    strcpy( tidymod, " ");   /* initialise string */
    strcpy( tidyans, " ");
    cleanstr( model, tidymod );
    cleanstr( answer, tidyans );
    
    /* break model into tokens */
    count = 0;
    p = strtok( tidymod, seps );
    while( p != NULL )
    {
      if( count > maxwords)
      {
        sprintf( printbuff, "\n Author error in match any: number of 
words in model is greater\n than the maximum allowed (%d).", maxwords );
        goto error;
      }
      strcpy( modtoks[count], " " );    /* leading space */
      strcat( modtoks[count], p );
      strcat( modtoks(count], " " );    /* trailing space */
      p = strtok( NULL, seps);
      count++;
    }
    wordsinmodel = count;
    if( wordsinmodel < reqmatches)
    {
      sprintf( printbuff, "\n Author error in match_any: required 
number of matches (%d) is greater\n than the number of words (%d) in 
the model answer.", reqmatches, wordsinmodel );
      goto error;
    }
    /* break answer into tokens */
    count = 0;
    p = strtok( tidyans, seps );
    while( (p != NULL) AND ( count < maxwords ) )
    {
      strcpy( anstoks[count],          /* leading space */
      strcat( anstoks[count), p
      strcat( anstoks[count),          /* trailing space */
      p = strtok( NULL, seps);
      count++;
    }
    wordsinans = count;

    for( modcount = 0; modcount < wordsinmodel; modcount++ )
    /* for each word in model, try to find match in answer */

      for( count = 0; count < wordsinans; count++)
      /* find first match */
        if( test( modtoks[modcount], anstoks[count) == MATCH)
        {
          wordsfound++; break;
          /* break to prevent repetitions in answer being analysed as correct */
        }

    if( wordsfound >= reqmatches ) return MATCH;
    if( wordsfound == 0 ) return NOMATCH;
    switch( wordsfound )
    {
      case 1:  return PM1;
      case 2:  return PM2;
      case 3:  return PM3;
      case 4:  return PM4;
      case 5:  return PM5;
      case 6:  return PM6;
      case 7:  return PM7;
      case 8:  return PM8;
    }
  } /* endelse analyse answer */
  
  error:
    _outtext( printbuff );
    _outtext( "\npress any key to continue... ");
    getch();
    return AE;
}


/* MATCHPRM.FUN */
void set match_params(char *matchstring)
{
  /* if B C or P specified in matchstring, then S is implied. */
  if(legalsmp( matchstring ) )
  {
    if( strchr( matchstring, 'S') == NULL)
      defaultmp.spelltest = FALSE;
    /* WARNING: this IF/ELSE must be at the top, otherwise def.stest 
       may be reset */
    else
      defaultmp.spelltest = TRUE;

    if( strchr( matchstring, 'B') == NULL) 
      defaultmp.blanktest = FALSE;
    else
    { defaultmp.blanktest = TRUE;
      defaultmp.spelltest = TRUE; }

    if( strchr( matchstring, 'C') == NULL) 
      defaultmp.casetest = FALSE;
    else
    { defaultmp.casetest = TRUE;
      defaultmp.spelltest = TRUE; }

    if( strchr( matchstring, 'P') == NULL) 
      defaultmp.puncttest = FALSE;
    else
      {
        defaultmp.puncttest = TRUE;
        defaultmp.spelltest = TRUE;
      }
  }
  else /* not legal smp string */
  {
    _outtext("\nauthor error in set match arams: illegal character(s).\n
Press any key to continue..."); 
    getch();
  }
}

void setcurrentmp(char *matchstring)
{
  /* if B C or P specified in matchstring, then S is implied. */
  if( strcmp( matchstring, "default") == 0)
    currentmp = defaultmp;
  else /*set new current mps */
  {
    if(legalsmp( matchstring ) )
    {
      if( strchr( matchstring, 'S') == NULL) currentmp.spelltest = FALSE;
      /* WARNING: this IF/ELSE must be at the top, otherwise curr.stest 
         may be reset */
      else
        currentmp.spelltest = TRUE;
    
      if( strchr( matchstring, 'B') == NULL) currentmp.blanktest = FALSE;
      else
        { currentmp.blanktest = TRUE;
          currentmp.spelltest = TRUE; }
    
      if( strchr( matchstring, 'C') == NULL) currentmp.casetest = FALSE;
      else
        { currentmp.casetest = TRUE;
          currentmp.spelltest = TRUE; }
    
      if( strchr( matchstring, 'P') == NULL) currentmp.puncttest = FALSE;
      else
        { currentmp.puncttest = TRUE;
          currentmp.spelltest = TRUE; }
    }
    else /* not legal smp string */
    {
      _outtext("\nAuthor error in match parameter list of a matching 
function: \nillegal character (s) . \nPress any key to continue...");
      getch();
    }
  } /* endelse set new mps */
}

enum boolean legalsmp( char* rnpstring)
{
  /* NOTE: doesn't check for duplicate characters */
  char *ptr = mpstring;

  while( *ptr )
  {
    /* if the character pointed to is in the list, continue */
    if( strchr( " ,BCPS", *ptr) != NULL) *ptr++;
    else return FALSE; /* not in list */
  }
  return TRUE;
}


/* MC.FUN */
enum mres mc2( char *st, char *ol, char *o2 )
{  return mc(2, st, ol, o2 ); }

enum mres mc3( char *st, char *ol, char *o2, char *o3 )
{  return mc(3, st, ol, o2, o3 ); }
enum mres mc4( char *st, char *ol, char *o2, char *o3, char *o4 )
{  return mc(4, st, ol, o2, o3, o4 ); }

enum mres mc5( char *st, char *ol, char *o2, char *o3, char *o4, char *o5 )
{  return mc(5, st, ol, o2, o3, o4, o5 ); }

enum mres mc6( char  *st, char *ol, char *o2, char *o3, char *o4, char *o5, char *o6 )
{  return mc(6, st, ol, o2, o3, o4, o5, o6 ); }


enum mres mc(int numb_of_optns, char *stem, char *option, ...)
{
  #define spacing 2      /* one line between options */
  #define minoptns 2     /* minimum number of options allowed */
  #define maxoptns 6     /* maximum number of options allowed */
  #define maxcols 80     /* need constant, can't use vc.numtextcols */
  #define reverse _settextcolor( (short)bgd ); _setbkcolor( (long)fore );
  #define normal _settextcolor( fore ); _setbkcolor( bgd );
  /* global variable MAIN_R2 = last line of main window */
  struct rccoord oldpos;
  int curr_row, count, longest, key, startrow, endrow;
  short fore;
  long bgd;
  va_list marker;
  char buffer[maxoptns][maxcols];
  char printbuff[200];
  char *temp;
  enum boolean errorfound = FALSE;
  enum boolean helpflag = FALSE;
  
  oldpos = _gettextposition();     /* start on new line */
  _settextposition(oldpos.row+l, 0);
  oldpos = _gettextposition();
  
  if( (oldpos.row + (numb_of_optns * spacing) ) > MAIN_R2
  {
    errorfound = TRUE;
    _outtext( "\nauthor error in mc: not enough screen lines to display 
  all options\nUse one of the clear functions to remove previous text.\n
  Press any key...");
    getch();
  }
  if( NOT errorfound) /* no author errors, continue (1) */
  {
    fore = _gettextcolor(); /* for reverse video highlight bar */
    bgd = _getbkcolor();
    _settextposition(oldpos.row, 1);
    _outtext( stem );
    va_start( marker, option);
    /* initialise variable arguments */
    strcpy( buffer[0], option);
    longest = strlen( option);
    count = 1;
    while( count < numb_of_optns ) /* collect rest of options */
    {
      temp = va_arg( marker, char );
      strcpy( buffer[count), temp );
      if( strlen( buffer[count)) > longest)
        longest = strlen( buffer[count] );
      count++;
    }
    va_end( marker );
    
    if( longest > maxcols )
    {
      errorfound = TRUE;
      sprintf( printbuff, "\nauthor error in mc:\nlength of an option 
is greater than maximum allowed (%d).\nPress any key...", maxcols);
      _outtext( printbuff )
      getch();
    }
    if( NOT errorfound ) /* continue (2) */
    {
      /* Fill strings with blanks, to length of longest, 
         so all cursors same length */
      for( count = 0; count < numb_of_optns; count++)
      {
        while( strlen( buffer[count]) < longest )
          strcat( buffer[count], " ");
        strcat( buffer[count], "\0");
      }
      /* Get row position for first option, and display it */
      oldpos = _gettextposition();
      startrow = oldpos.row +spacing;
      curr row = startrow;
      _settextposition( curr_row, 1);
      reverse;
      _outtext( buffer[0] );
      normal;
      
      /* display remainder of options */
      for( count = 1; count < numb_of_optns; count ++)
      {
        curr_row += spacing;
        _settextposition( curr_row, 1 );
        _outtext( buffer[count] );
      }
      endrow = curr_row;    /* save this last row, and */
      curr_row = startrow;  /* move cursor to first option */
      _settextposition( startrow, 1);
      count = 0;            /* current buffer index */
      
      message("Up/down arrows to move bar, Enter to select, Fl = Help.");
      _displaycursor( _GCURSOROFF );
      while( 1 )
      /* move cursor through options until Enter */
      {
        key = getch();
        if(key == 13) break;
        if(key == 0)
        {
          key = getch();
          /* extended key was pressed, get second value, test up/down/help */
          if(key == HELPKEY )      /* HELPKEY = 59 = Fl */
          {
            helpflag = TRUE; break;
          }
          if(key == 72) /* up arrow */
          {
            normal;
            settextposition( curr_row, 1);
            /* reset cursor to start of line */
            _outtext( buffer(count] );
            reverse;       /* for next outtext */
            if( curr_row = startrow )
              { curr_row == endrow; count = numb_of_optns-1; }
            else { curr_row -= spacing; count--; }
            _settextposition( curr_row, 1);
            _outtext( buffer[count] );
          }
          if(key == 80) /* down arrow */
          {
            normal;
            _settextposition( curr_row, 1 );
            _outtext( buffer[count] );
            reverse;
            if( curr_row == endrow )
              { curr_row = startrow; count = 0; }
            else { curr_row += spacing; count++; }
            _settextposition( curr_row, 1 );
            _outtext( buffer[count] );
          }
        } /* endif key == 0 */
      } /* endwhile */
      _settextposition( endrow+l, 0 );
      normal;
      _displaycursor( _GCURSORON );
      message( " " );    /* clear message area */
      
      if( helpflag ) return HELPREQ;
      else
      switch( count )
      {
        case  0: return  OPTI;
        case  1: return  OPT2;
        case  2: return  OPT3;
        case  3: return  OPT4;
        case  4: return  OPT5;
        case  5: return  OPT6;
      }
    } /* endif not errorfound (2) */
  } /* endif not errorfound (1) */
  return AE; /* error has been found */
}


/* MESSAGE.FUN */
void message( char* sometext)
{
  char printbuff(200];

  if( strlen( sometext ) > ( MESS_C2 - MESS_C1 +1 ) )
  {
    _clearscreen( _GWINDOW );
    sprintf( printbuff, "Author error in message: length of message too 
long.\nmaximum %d characters allowed. Your message is %d characters\n
Press any key to continue...", ( MESS_C2 - MESS_Cl +1 ), strlen( sometext ) );
    _outtext( printbuff );
    getch();
    clearscreen( _GWINDOW );
  }
  else
  {
    oldpos = _gettextposition();
    _settextwindow( MESSCOORD );
    _clearscreen( _GWINDOW );
    _outtext( sometext);
    _settextwindow( MAINCOORD );
    settextposition(oldpos.row, oldpos.col);
  }
}


/* PAUSE.FUN */
void pause( void )
{
  oldpos = _gettextposition();
  _settextwindow( MESSCOORD );
  _clearscreen( _GWINDOW );
  _outtext(" Press any key to continue...");
  getch();
  _clearscreen( _GWINDOW );
  _settextwindow( MAINCOORD );
  _settextposition( oldpos.row, oldpos.col );
}


/* PUTCURS.FUN */
void putcursor( int rowpos, int colpos )
{
  char printbuff[180];
  if( (rowpos < 1 ) OR ( rowpos > (MAIN_R2 - MAIN_R1 +1) ) OR 
      (colpos < 1 ) OR ( colpos > (MAIN-C2 - MAIN-C1 +1) ) )
  {
    _clearscreen( _GWINDOW );
    sprintf( Printbuff, "Author error in putcursor; values out of range.\n
\nRow must be between 1 and %d inclusive\nCol must be between 1 and
%d inclusive.\nYou set row = %d, col %d", (MAIN_R2 - MAIN_R1 + 1), 
(MAIN_C2 - MAIN_C1 +1), rowpos, colpos);
    _outtext( printbuff);
    _outtext("\nPress any key to continue...");
    getch();
    clearscreen( _GWINDOW );
  }
  else
    _settextposition( rowpos, colpos);
}


/* TEST_VI.FUN */
enum mres test( char* modelstr, char* answerstr )
{
/* This is the matcher (version 1). It does not deal withwildcards or 
   wild characters. It returns either MATCH or NOMATCH. MATCH indicates 
   an acceptable match, NOMATCH indicates failure to match. Only one
   error is allowed in the answer string.

   If the length of the model and answer strings are the same, then one 
   of three possibilities exists for an acceptable match; a perfect match, 
   a substitution error, or a transposition error. If the length of the 
   model is less than the length of the answer, then a possible Insertion 
   error has been made. If the length of the model is greater than the 
   length of the answer, then a possible omission error has been made. 
   If none of these situations is detected, the match fails. */

int result, length, modelcount, answercount;
enum boolean errorfound = FALSE;
if( ! currentmp.spelltest )
{
  if( strlen( answerstr ) == strlen( modelstr ))
  /* test for Perfect match, Substitution, Transposition */
  {
    length = strlen( modelstr ); modelcount = 0;
    while( (! errorfound) AND (modelcount < length) )
    {
      if(answerstr[modelcount] == modelstr[modelcount]) modelcount++;
      else /* error found */
      {
        errorfound = TRUE;
        if((answerstr[modelcount] == modelstr[modelcount+l]) AND
           (answerstr[modelcount+l] == modelstr[modelcountl])) modelcount += 2;
           /* transposition error */
        else
          modelcount++; /* substitution or other error */
      } /* endelse error found */
    } /* endwhile not error found */
    
    while(modelcount < length) /* test rest of string */
      if(answerstr[modelcount] == modelstr[modelcount])
        modelcount++;
      else
        return NOMATCH; /* another error, abandon check */
      return MATCH;       /* no more errors found */
  } /* endif (test for P, S, T) */
  
  /* test for single Omission */
  if( strlen( answerstr ) == ( strlen( modelstr ) -1 ) )
  {
    modelcount = 0;
    length = strlen( answerstr );
    for( answercount = 0; answercount < length; answercount++)
    {
      if( answerstr[answercount] == modelstr[modelcount] ) modelcount++; 
      /* OK so far */
      else /* error found */
      {
        if( (! errorfound   AND /* first error */
            ( answerstr[answercount] == modelstr[modelcount+l]) )
            /* and if following char is not error, continue */
            {
              errorfound = TRUE;
              modelcount += 2; /* ie, skip one char */
            }
        else
          /* This is not the first error, or the following character is 
             also an error - abandon checking */
          return NOMATCH;
      } /* end else error found */
    } /* end for */
    return MATCH;
    /* if we've got this far, only one omission found */
  } /* end if (test for omission */
  
  /* test for single Insertion */
  if( strlen(answerstr) == (strlen(modelstr)+l) )
  {
    answercount = 0;
    length = strlen( modelstr );
    for( modelcount = 0; modelcount < length; modelcount++)
    {
      if( answerstr[answercount) == modelstr[modelcount] ) answercount++;
      else /* error found */
      { /* if this is the first error */
        if( ( ! errorfound ) AND
            ( answerstr[answercount+l] == modelstr[modelcount] ) )
            /* and if following char is not error, continue */
          {
            errorfound = TRUE;
            answercount += 2; /* ie, skip one char */
          }
      
        else
        /* This is not the first error, or the following character is 
           also an error - abandon checking */
        return NOMATCH;
      } /* end else error found */
    } /* end for */
    return MATCH;
    /* if we've got this far, only one insertion found */
  } /* end if (test for insertion ) */
  return NOMATCH; /* not P, S, T, 0, or I , therefore fail */
} /* endif not spelltest */
else
/* no spell check required, compare strings, and return result */
  {
    result = strcmp(modelstr, answerstr);
    if( result == 0) return MATCH;
    else return NOMATCH;
  }
}


/* ANALYSTR.FUN */
void analyse_string( char* model, char matchres )
{
  /* This function is redundant, and is not included in the
  PAL authoring package. It is replaced by branching functions (if and goto) */
  
  char prnbuff[200];
  _outtext("\n This function (analyse_string) is for prototyping purposes 
only.\n In practice it would be replaced by branching functions.\n");

  switch( matchres) /* build output message */
  {
    case 'T' :
      /* perfect match, or everything required found */
      _outtext("\nYour answer is acceptable.");
      break;
  
    case 'F' :      /* total failure */
      sprintf( prnbuff, "\nYour answer is not acceptable, the correct 
answer is:\n %s", model);
      _outtext( prnbuff );
      break;
  
    case 'N' :      /* no attempt */
      sprintf( prnbuff, "\nYou did not attempt to answer, the correct 
answer is:\n %s", model);
      outtext( prnbuff );
      break;
  
    case '?' :      /* help requested */
      _outtext("\n help requested, none available. ");
      break;
  
    /* partially correct answer */
    case '1' : case '2' : case '3' : case '4' : 
    case '5' : case '6' : case '7' : case '8' :
      sprintf( prnbuff, "\n Your answer is partially correct; you 
found %c item(s). The full list is:\n %s", matchres, model);
      _outtext( prnbuff );
      break;
    
    case 'X' : /* author error in a previous function */
      _outtext("\n no response - author error in previous function.");
      break;
  
    default :
    /* error - but should never happen, but if it did, the program 
       should terminate */
    _outtext("\n Error - illegal matchresult");
  }
  
  _outtext("\n Press any key to continue...");
  getch();
  clearscreen( _GWINDOW );
}


/* SHOWPARM.FUN */
void showparms()
{
  /* This function was used during testing to check the operation of 
     setcurrentmp when it is called by the matching functions. */
  
  char prnbuff[80] = " ";
  if(currentmp.blanktest) strcat( prnbuff, "blanktest ON ");
    else strcat( prnbuff, "blanktest OFF "); 
  if(currentmp.casetest) strcat( prnbuff, "casetest ON ");
    else strcat( prnbuff, "casetest OFF ");
  if(currentmp.puncttest) strcat( prnbuff, "puncttest ON ");
    else strcat( prnbuff, "puncttest OFF "); 
  if(currentmp.spelltest) strcat( prnbuff, "spelltest ON ");
    else strcat( prnbuff, "spelltest OFF ");
  
  { /* this is equivalent to header, without AE test */
    oldpos = _gettextposition();
    _settextwindow( HEADCOORD );
    _clearscreen( _GWINDOW );
    _outtext( prnbuff);
    _settextwindow( MAINCOORD );
    settextposition(oldpos.row, oldpos.col);
  }
}

/* TIDY.BAT */
echo off
rem                    TIDY.BAT
rem  this batch file deletes unwanted sym, ilk, mdt, and 
rem  obj files.

echo
echo Removing ILK SYM MTD and OBJ files. Please wait...

rem replaceable parameters are converted to uppercase.

for %%x in (*.obj) do if not %%x == PALFUN.OBJ del %%x
for %%x in (*.ilk) do del %%x 
for %%x in (*.sym) do del %%x 
for %%x in (*.mdt) do del %%x

echo
echo Done - all unwanted files removed.


/* PROTO.PRO */
/* This is the prototype for the spelling assistant. It deals with the 
   four major types of keyboard error. Spaces, commas, and full stops 
   are removed from the model and answer. Case is important. Wildcards 
   and wild characters cannot be used.

   The user is prompted to input a model, and then an answer.

   The implementation version is Turbo Prolog, version 1.0, which 
   purists do not consider to be true prolog. */

domains
    charlist=char*

predicates
    string_chlist(string,charlist)
    perfect(charlist,charlist)
    omission(charlist,charlist)
    insertion(charlist,charlist)
    substitution(charlist,charlist)
    transposition(charlist,charlist)
    go
    test(charlist,charlist)
    charmatch(charlist,charlist)
    /* charmatch is fix for transposition pred */
    clean(charlist,charlist)
    remove(char)

goal
    go.

clauses

string_chlist("",[]).
string_chlist(S,[H|T]):-
    frontchar(S,H,S1), string_chlist(S1,T).

go:-
    write(" input model: "),
    readln(Model),
    write("input answer: "),
    readln(Answer),
    string_chlist(Model, Mod_chlist),
    string_chlist(Answer,Ans_chlist),
    clean(Mod_chlist,Clean_mod),
    clean(Ans_chlist,Clean_ans),!,
    Write("\nclean model  = ",Clean_mod),
    write("\nclean answer = ",Clean_ans),
    test(Clean_mod,Clean_ans).

test(Model,Answer):- perfect(Model,Answer).
test(Model,Answer):- omission(Model,Answer).
test(Model,Answer):- insertion(Model,Answer).
test(Model,Answer):- substitution(Model,Answer).
test(Model,Answer):- transposition(Model,Answer).

perfect(Mod_ans,Mod_ans):- nl, write("perfect match ").

omission([H|T1],[H|T2)):- omission(Tl,T2).
omission([_|T1,T):- nl, write("omission ").

insertion([H|TI],[H|T2]):- insertion(TI,T2).
insertion(T,[_|T]):- nl, write("insertion ").

substitution([H|T1],[H|T2]):- substitution(TI,T2).
substitution([_|T],[_|T]):- nl, write("substitution error ").

transposition([H|T1],[H|T2]):- transposition(TI,T2).
transposition([_|T],[_|T2]):- charmatch(T,T2), nl, write("tranposition ").

charmatch([_|H],[_|H]).

clean([],[]).
clean([C|Restl],[C|Rest2]):- remove(C), clean(Restl,Rest2).
clean([_|Rest], Rest2):- clean(Rest,Rest2).

remove(C):- C <> ' ', C <> '.', C <> ','.



Preface | Contents | 1 Introduction | 2 Review | 3 Req. analysis | 4 Req. documents | 5 Specification | 6 Design | 7 Verification | 8 Discussion | 9 PAL manual | Appendix A | Appendix B | Appendix C | Glossary | References | Index