.dw .bd From The Editor .bo .do 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. .dw .bd Strip_c .bo .do 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 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); } .dw .bd Ascii Dump .bo .dw 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 #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); } .dw .bd Check Bits for Ascii Files .do .bo 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 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); } .dw .bd Ansi C To K&R C .do .bo 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 #include #ifdef BSD #include #define strchr index #else #ifdef VMS extern char *strcat(), *strchr(), *strcpy(), *strupr(); extern int strcmp(), strlen(), strncmp(); #else #include #endif #endif #ifdef MSDOS #include #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; }