         Q L   H A C K E R ' S   J O U R N A L
      ===========================================
           Supporting  All  QL  Programmers
      ===========================================
         #8                        March 1992
      
    The QL Hacker's Journal (QHJ) is published by Tim
Swenson as a service to the QL Community.  The QHJ is
freely distributable.  Past issues are available on disk,
via e-mail, or via the Anon-FTP server, garbo.uwasa.fi. 
The QHJ is always on the look out for article submissions.

        QL Hacker's Journal
     c/o Tim Swenson
     5615 Botkins Rd 
     Huber Heights, OH 45424 USA
     (513) 233-2178
     swensontc@mail.serve.com
     http://www.serve.com/swensont/

From The Editor
   By Tim Swenson

   THE QHJ GOES ELECTRIC
   
   With this issue, the QHJ is now also being distributed
via electronic mail.  With the recent advent of a QL
Internet mailing list, the QHJ can be distributed to QL
users all around the world in a matter of minutes.  Even QL
users on CompuServe can recieve the QHJ via e-mail.  The QHJ
will still be published in print and mailed, but I can now
expand the QHJ's readership with little effort and no cost. 
Let's hear it for e-mail!!

   Guiseppe Zanetti and Mauricio Tavares are both collecting
e-mail addresses of QL users on the Internet.  So far the
list has users from the US, England, Germany, the
Netherlands, Italy, Sweden, Denmark, Norway, and New
Zealand.

   If you are interested in getting on the list you need to
have a connectioin to the Internet.  Most colleges have
connections to the Internet, some computer companies have an
Internet connection, and CompuServe has a mail gateway with
the Internet.  There are a number of Public Access Unix
systems scattered around the US that provide Internet mail
access from little or no cost.  If interested, send me a
note and I'll send you the latest listing of these systems.

   Recently the QHJ has been getting some pretty good
coverage from two QL / Sinclair publications.  The widest
coverage comes from QL World.  In their December 1991 issue
there is a article on the QL in North America in which the
QHJ and I are mentioned.  Gee, it's kind of neat to see
someone else put your name in print.

   A while back Peter Hale of the New England QL Group
passed a letter mailed to him from Robin Stevenson, the
writer of the QL World article, asking for info on QL groups
in North America.  As the Editor of the CATS User Group, I
got a copy of the letter.  Being the type to bend any body's
ear when it comes to talking about the QL, I sent Mr.
Stevenson a note about CATS and the QHJ.  See, it pays to
take the time to respond to these kind of queries.

   The other publication that mentioned the QHJ is Update,
the last North American Sinclair magazine.  Eliad P. Wannum
wrote a short review of Sinclair Publications and the QHJ
got a nice write up.  Update covers all Sinclair computers,
but seems to focus on the T/S 2068.  This is mostly because
the editor is a dedicated T/S 2068 user from way back.  In
fact the best looking pages in the magazine were produced on
a T/S 2068 using Word Master.


Strip_c
   By Tim Swenson
   
   A few months ago I picked up a inexpensive pen-based
MS-DOS laptop.  It came with a Word Processor that handles
handwritten input from the screen.  I've found it usefull
for taking notes at meetings and writing short letters while
watching TV.  The word processor stores it's files in its
own format and not ascii.

   I've thought about the ways that I could get the file to
ascii so I can transfer it to the QL and into Quill or
MicroEmacs.  The word processor will output an ascii version
of the file, but I did not want to have to go throught the
process for every file.  Plus this means that every file
would have an ascii version on disk that I would have to go
back and delete.  I wanted to convert the original file somehow.

  Not knowing how the word processor stored its files, I
decided a program could be written that would strip out all
non-printable characters from a file, there by making it a pure
text file.

  Below is strip_c that does this.  This program ignores any
non-ascii characters and non-printing ascii characters (like
null, etc).  It does translate all Carriage Returns into
Line Feeds that the QL prefers.  It's short, simple and to
the point.

/* strip_c  */
/* Will compile under Small-C */

#include <stdio_h>

main() {
   char c, file1[30], file2[30];

   int
         fd1,
         fd2;

   printf("Enter Input File Name : \n");
   gets(file1);

   printf("Enter Output File Name: \n");
   gets(file2);

   fd1 = fopen(file1,"r");
   if (fd1 == NULL)  {
      printf("Did not open file: %s",file1);
      abort(1);
   }

   fd2 = fopen(file2,"w");
   if (fd2 == NULL) {
      printf("Did not open file: %s",file2);
      abort(1);
   }

   while (( c = getc(fd1)) != EOF) {
      if ( c >= 32 && c <= 126 )
         { putc(c,fd2); }
      if ( c == 10 || c ==13 )
         { c = 10; putc(c,fd2); }
   }
   fclose(fd1);
   fclose(fd2);

}


Ascii Dump
   By Tim Swenson
   
   In writing strip_c and converting the file to an ascii
file, I needed to know what ascii characters were in the
file that I would need to strip out.  A number of ascii
characters are not used in text files, ones like End of
Transmission, Acknowlegement, and so on.

   On a Unix system, there is a program called od, for Octal
Dump.  Od will take a file and output it in Octal,
Hexidecimal, or Ascii.  It's usefull to see all non-printing
codes.  I only used od with the -a (for Ascii) option.  I'm
not so good at reading Octal or Hex.

   Instead of going to a Unix system to check this file out,
I decided to write a version of od for the QL.  Since I only
use od with ascii output, I limited my program to just the
ascii option.  Of course, I could no longer call my version
od, so it is now called ad for Ascii Dump.

   The constant WIDTH is the number of ascii characters is
printed on a single line.  It is set for 10 characters, but
you can set it as wide as you want, limited only by your
screen width.

/* ad_c
   Ascii Dump
     Will compile under Small-C
*/

#include <stdio_h>
#define WIDTH   10

main() {
   char c, file[30];

   int   fd, count;

   printf("Enter Input File Name : \n");
   gets(file);

   fd = fopen(file,"r");
   if (fd == NULL)  {
      printf("Did not open file: %s",file);
      abort(1);
   }

   count = 0;

   while (( c = getc(fd)) != EOF) {
      if ( c == 0 )  printf("nul ");
      if ( c == 1 )  printf("soh ");
      if ( c == 2 )  printf("stx ");
      if ( c == 3 )  printf("etx ");
      if ( c == 4 )  printf("eot ");
      if ( c == 5 )  printf("enq ");
      if ( c == 6 )  printf("ack ");
      if ( c == 7 )  printf("bel ");
      if ( c == 8 )  printf("bs  ");
      if ( c == 9 )  printf("ht  ");
      if ( c == 10 )  printf("lf  ");
      if ( c == 11 )  printf("vt  ");
      if ( c == 12 )  printf("ff  ");
      if ( c == 13 )  printf("cr  ");
      if ( c == 14 )  printf("so  ");
      if ( c == 15 )  printf("si  ");
      if ( c == 16 )  printf("dle ");
      if ( c == 17 )  printf("dc1 ");
      if ( c == 18 )  printf("dc2 ");
      if ( c == 19 )  printf("dc3 ");
      if ( c == 20 )  printf("dc4 ");
      if ( c == 21 )  printf("nak ");
      if ( c == 22 )  printf("syn ");
      if ( c == 23 )  printf("etb ");
      if ( c == 24 )  printf("can ");
      if ( c == 25 )  printf("em  ");
      if ( c == 26 )  printf("sub ");
      if ( c == 27 )  printf("esc ");
      if ( c == 28 )  printf("fs  ");
      if ( c == 29 )  printf("gs  ");
      if ( c == 30 )  printf("rs  ");
      if ( c == 31 )  printf("us  ");
      if ( c == 32 )  printf("sp  ");
      if ( c >= 33 && c <=126)
      {
          putchar(c);
          printf("   ");
      }
      if ( c == 127 ) printf("del ");
      if ( c >= 128 )
      {
          putchar(c);
          printf("   ");
      }

      count++;
      if ( count > WIDTH ) {
         printf("\n");
         count = 0;
      }

   }
   fclose(fd);
}


Check Bits for Ascii Files
   By Tim Swenson
   
   I've been scanning throught some old issues of Dr Dobb's
Journal trying to set the spark to a new programming idea. 
I was reading one article on architecture ( and / or gates,
etc) and the word parity leaped from the page.

   I had a sudden flash back to my college days where I was
introduced to the concept of parity and check bits.  My mind
was mulling on what applications I could put parity and
check bits to use.

   It then dawned me that ascii characters are 7 bits, but
stored in 8 bit bytes.  In a true ascii file, the last bit
is always set to 0 and wasted.  Hey, I could use the 8th bit
as a check bit.  If a the 7 other bits total up to an even
number the parity bit can be set to 1, else it would be set
to 0.  Neat idea, but what use would it have?

   How about error checking when sending files bewteen
computers via the serial port.  After the transfer the file
can be checked for parity errors.  With disk systems there
is always the chance that the drive will not read the disk
properly and return a bad bit ( albeit a VERY small chance).
Parity checking can keep you from suffering from the horror :-).
 
   Anyhow, the program was a good mental and programming
excercise.  I prefer to write short programs that can be
concieved, written, tested, and made running in a day.

   The program has three functions:  Add the check bits (A),
Remove the check bits (R), and Check the check bits (C).  The
C option is used to check the file to make sure no errors
have creeped in.

/* Check Bit for Ascii Files
      This program takes a 7-bit ascii file and
   converts the 8th bit to a check bit.
      Even characters have the check bit set to
   one and odd characters have it set to zero.

      This program has three modes:
         A : add the check bits to the file
         R : remove the check bits and return file
             to pure ascii
         C : check file for errors using the check bits

*/

#include <stdio_h>

main()
{

char  c, file1[30],file2[30];
int   arg, check, errors, fd1, fd2;

   check = 0;
   errors = 0;

   printf("Enter A, R, or C : ");
   arg = getchar();
   putchar(arg);

   if ( arg == 'A' || arg == 'a' )
      {  check = 'A'; }
   if ( arg == 'R' || arg == 'r' )
      {  check = 'R'; }
   if ( arg == 'C' || arg == 'c' )
      {  check = 'C'; }
   if ( check == 0 )
      {  printf("\nNot a Valid Character\n");
         abort(-1);
      }

   printf("\nEnter Input File: ");
   gets(file1);

   fd1 = fopen(file1,"r");
   if ( fd1 == NULL)
      {  printf("\nCould Not Open File\n");
         abort(-1);
      }

   if ( check == 'A' || check == 'R' )
      {  printf("\nEnter Output File: ");
         gets(file2);

         fd2 = fopen(file2,"w");
         if ( fd2 == NULL)
            {  printf("\nCould Not Open File\n");
               abort(-1);
            }
      }

   while (( c = getc(fd1)) != EOF )
   {   if ( check == 'A' )
       {  if ( c >= 128 )
              printf("\nIgnoring non-ascii character
#%d",c);
          if ( c % 2 == 0) c = c+128;
       }

       if ( check == 'R' || check == 'C' )
       {  if ( c > 128 )
          {  c = c - 128;
             if ( c % 2 != 0 ) errors++;
          }
          else if ( c % 2 == 0) errors++;
       }

       if ( check == 'A' || check == 'R' )
          putc(c,fd2);
   }

   fclose(fd1);
   if ( check == 'A' || check == 'R' )
      fclose(fd2);

   if ( check == 'C' || check == 'R' )
       printf("\nTotal Errors = %d",errors);

}


Ansi C To K&R C
   By Tim Swenson
   
   Guiseppe Zanetti mailed me a copy of an Ansi C to K&R C
convert program that came from the GNU project.  I have
played with it and it seems to work.  It is limited in
dealing with just the function definitions, translating
these to the old K&R syntax.  It does not convert any
special ANSI syntax that K&R can not handle.  It will not
take any ANSI code and convert it so that it is guaranteed
to compile under K&R.  It only tackles the function definitions.

   I have noticed that you do have to have the function name
as the first text on the line.  Having void, int, or char in
front of a function will not work.  This can be a serious
limitation, making you go through the code and delete them
yourself.  This program could be the start for a better ANSI
to K&R system.

   Franz Herrmann is working on making C68 better at
handling ANSI code.  He has patched the CPP program so that
it's predefined macros fully conform to ANSI.  C68 2.0 has
an "unproto" option that changes ANSI function definitions
to K&R.

   Here is a sample ANSI program and the results after it
goes throught the convertor: 

/* getline: get line into s, return length */
/* from K&R 2nd Ed page 69  */

getline(char s[], int lim)
{
   int c,i;

   i = 0;
   while (--line > 0 && (c=getchar()) != EOF && c != '\n')
      s[i++] = c;
   if ( c == '\n')
      s[i++] = c;
   s[i] = '\0';
   return i;
}

After the Convertor:

#line 1 "flp2_ansi2_c"
/* getline: get line into s, return length */
/* from K&R 2nd Ed page 69  */

getline(s, lim)  char s[]; int lim;
{
   int c,i;

   i = 0;
   while (--line > 0 && (c=getchar()) != EOF && c != '\n')
      s[i++] = c;
   if ( c == '\n')
      s[i++] = c;
   s[i] = '\0';
   return i;
}

The Program:

/* Compiled with C68 1.15 on the QL */
/* ansi2knr.c */
/* Convert ANSI function declarations to K&R syntax */

#include <stdio_h>
#include <ctype_h>

#ifdef BSD
#include <strings.h>
#define strchr index
#else
#ifdef VMS
    extern char *strcat(), *strchr(), *strcpy(), *strupr();
    extern int strcmp(), strlen(), strncmp();
#else
#include <string_h>
#endif
#endif

#ifdef MSDOS
#include <malloc.h>
#else
#ifdef VMS
     extern char *malloc();
     extern void free();
#else
     extern char *malloc();
     extern int free();
#endif
#endif

/* Usage:
    ansi2knr input_file output_file
 * If no output_file is supplied, output goes to stdout.
 * There are no error messages.
 *
 * ansi2knr recognizes functions by seeing a non-keyword
identifier
 * at the left margin, followed by a left parenthesis,
 * with a right parenthesis as the last character on the
line.
 * It will recognize a multi-line header if the last
character
 * on each line but the last is a left parenthesis or
comma.
 * These algorithms ignore whitespace and comments, except
that
 * the function name must be the first thing on the line.
 * The following constructs will confuse it:
 - Any other construct that starts at the left margin
and follows the above syntax (such as a macro or function
call).
 - Macros that tinker with the syntax of the function header.
 */

/* Scanning macros */
#define isidchar(ch) (isalnum(ch) || (ch) == '_')
#define isidfirstchar(ch) (isalpha(ch) || (ch) == '_')

int
main(argc, argv)
    int argc;
    char *argv[];
{   FILE *in, *out;
#define bufsize 500         /* arbitrary size */
    char buf[bufsize+1];
    char *line;
    switch ( argc )
       {
    default:
        printf("Usage: ansi2knr input_file
[output_file]\n");
        exit(0);
    case 2:
        out = stdout; break;
    case 3:
        out = fopen(argv[2], "w");
        if ( out == NULL )
           {    fprintf(stderr, "Cannot open %s\n",
argv[2]);
            exit(1);
           }
       }
    in = fopen(argv[1], "r");
    if ( in == NULL )
       {    fprintf(stderr, "Cannot open %s\n", argv[1]);
        exit(1);
       }
    fprintf(out, "#line 1 \"%s\"\n", argv[1]);
    line = buf;
    while ( fgets(line, (unsigned)(buf + bufsize - line),
in) != NULL )
       {    switch ( test1(buf) )
           {
        case 1:         /* a function */
            convert1(buf, out);
            break;
        case -1:        /* maybe the start of a function */
            line = buf + strlen(buf);
            continue;
        default:        /* not a function */
            fputs(buf, out);
            break;
           }
        line = buf;
       }
    if ( line != buf ) fputs(buf, out);
    fclose(out);
    fclose(in);
    return 0;
}

/* Skip over space and comments, in either direction. */
char *
skipspace(p, dir)
    register char *p;
    register int dir;  /* 1 for forward, -1 for backward */
{   for ( ; ; )
       {    while ( isspace(*p) ) p += dir;
        if ( !(*p == '/' && p[dir] == '*') ) break;
        p += dir;  p += dir;
        while ( !(*p == '*' && p[dir] == '/') )
           {    if ( *p == 0 ) return p;    /* multi-line
comment?? */
            p += dir;
           }
        p += dir;  p += dir;
       }
    return p;
}

/*
 * Write blanks over part of a string.
 */
int
writeblanks(start, end)
    char *start;
    char *end;
{   char *p;
    for ( p = start; p < end; p++ ) *p = ' ';
    return 0;
}

/*
 * Test whether the string in buf is a function definition.
 * The string may contain and/or end with a newline.
 * Return as follows:
 *  0 - definitely not a function definition;
 *  1 - definitely a function definition;
 *  -1 - may be the beginning of a function definition,
 *      append another line and look again.
 */
int
test1(buf)
    char *buf;
{   register char *p = buf;
    char *bend;
    char *endfn;
    int contin;
    if ( !isidfirstchar(*p) )
        return 0;       /* no name at left margin */
    bend = skipspace(buf + strlen(buf) - 1, -1);
    switch ( *bend )
       {
    case ')': contin = 1; break;
    case '(':
    case ',': contin = -1; break;
    default: return 0;      /* not a function */
       }
    while ( isidchar(*p) ) p++;
    endfn = p;
    p = skipspace(p, 1);
    if ( *p++ != '(' )
        return 0;       /* not a function */
    p = skipspace(p, 1);
    if ( *p == ')' )
        return 0;       /* no parameters */
	 /* Check that the apparent function name isn't a
keyword. */
 /* We only need to check for keywords that could be
followed */
 /* by a left parenthesis (which, unfortunately, is most of
them).*/
       {    static char *words[] =
    {    "asm", "auto", "case", "char", "const","double",
         "extern", "float", "for", "if", "int", "long",
         "register", "return", "short", "signed", "sizeof",
         "static", "switch", "typedef", "unsigned",
         "void", "volatile", "while", 0
           };
        char **key = words;
        char *kp;
        int len = endfn - buf;
        while ( (kp = *key) != 0 )
           {    if ( strlen(kp) == len && !strncmp(kp, buf,
len) )
                return 0;   /* name is a keyword */
            key++;
           }
       }
    return contin;
}

int
convert1(buf, out)
    char *buf;
    FILE *out;
{   char *endfn = strchr(buf, '(') + 1;
    register char *p;
    char **breaks;
    unsigned num_breaks = 2;    /* for testing */
    char **btop;
    char **bp;
    char **ap;
top:    p = endfn;
    breaks = (char **)malloc(sizeof(char *) * num_breaks *
2);
    if ( breaks == 0 )
       {    /* Couldn't allocate break table, give up */
        fprintf(stderr, "Unable to allocate break table!\n");
        fputs(buf, out);
        return -1;
       }
    btop = breaks + num_breaks * 2 - 2;
    bp = breaks;
    /* Parse the argument list */
    do
       {    int level = 0;
        char *end = NULL;
        if ( bp >= btop )
           {    /* Filled up break table. */
            /* Allocate a bigger one and start over. */
            free((char *)breaks);
            num_breaks <<= 1;
            goto top;
           }
        *bp++ = p;
        /* Find the end of the argument */
        for ( ; end == NULL; p++ )
           {    switch(*p)
               {
            case ',': if ( !level ) end = p; break;
            case '(': level++; break;
            case ')': if ( --level < 0 ) end = p; break;
            case '/': p = skipspace(p, 1) - 1; break;
            default: ;
               }
           }
        p--;            /* back up over terminator */
        /* Find the name being declared. */
        /* This is complicated because of procedure and */
        /* array modifiers. */
        for ( ; ; )
           {    p = skipspace(p - 1, -1);
            switch ( *p )
               {
            case ']':   /* skip array dimension(s) */
            case ')':   /* skip procedure args OR name */
               {    int level = 1;
                while ( level )
                 switch ( *--p )
                   {
                case ']': case ')': level++; break;
                case '[': case '(': level--; break;
                case '/': p = skipspace(p, -1) + 1; break;
                default: ;
                   }
               }
                if ( *p == '(' && *skipspace(p + 1, 1) ==
'*' )
                   {    /* We found the name being declared
*/
                    while ( !isidfirstchar(*p) )
                        p = skipspace(p, 1) + 1;
                    goto found;
                   }
                break;
            default: goto found;
               }
           }
found:      if ( *p == '.' && p[-1] == '.' && p[-2] == '.'
)
           {    p++;
            if ( bp == breaks + 1 ) /* sole argument */
                writeblanks(breaks[0], p);
            else
                writeblanks(bp[-1] - 1, p);
            bp--;
           }
        else
           {    while ( isidchar(*p) ) p--;
            *bp++ = p+1;
           }
        p = end;
       }
    while ( *p++ == ',' );
    *bp = p;
    /* Make a special check for 'void' arglist */
    if ( bp == breaks+2 )
       {    p = skipspace(breaks[0], 1);
        if ( !strncmp(p, "void", 4) )
           {    p = skipspace(p+4, 1);
            if ( p == breaks[2] - 1 )
               {    bp = breaks;    /* yup, pretend arglist
is empty */
                writeblanks(breaks[0], p + 1);
               }
           }
       }
    /* Put out the function name */
    p = buf;
    while ( p != endfn ) putc(*p, out), p++;
    /* Put out the declaration */
    for ( ap = breaks+1; ap < bp; ap += 2 )
       {    p = *ap;
        while ( isidchar(*p) ) putc(*p, out), p++;
        if ( ap < bp - 1 ) fputs(", ", out);
       }
    fputs(")  ", out);
    /* Put out the argument declarations */
    for ( ap = breaks+2; ap <= bp; ap += 2 ) (*ap)[-1] =
';';
    fputs(breaks[0], out);
    free((char *)breaks);
    return 0;
}

