Q L H A C K E R ' S J O U R N A L =========================================== Supporting All QL Programmers =========================================== #18 August 1994 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 *** swensotc@ss2.sews.wpafb.af.mil tswenson@dgis.dtic.dla.mil *** Note new E-Mail address EDITOR'S FORUMN This is the second issue that is far later that I would like. Let me explain the reason why. Back in March I was told that I was being matrixed to do computer support for the B-2 Bomber Systems Program Office. What this job entails is managing a computer and telecommunications branch. I have three government civilians and nine contractors. I have a total budget of about 7 million dollars (most of that is already obligated to fund telephone circuits and the contractors). I am the only Captain branch chief, so I get lots of visibility. I don't deal with anything about the plane itself, my branch supports the office automation for the Air Force office that manages the B-2 program. Needless to say I have been busy figuring out my new job and what I am doing. I only had a 1 week overlap with the person I replaced. Nine hour days are getting to be the norm for me. I will still keep working on this newsletter. I hope that some people will send in some articles and take the pressure off of me (hint, hint). Thanks to all that are still out there waiting for an issue. Now on to other things: This past May I made the trek to Rhode Island. Paul Holmgren was good enough to give me a ride. We left Friday night and drove straight through to Rhode Island (NoDoz - don't drive 12 hrs without it). Paul has this nice little black box that sets off radar detectors. I did a little behavior modification on some speeders on I-95. The show in Rhode Island was pretty good. Saw a few faces that I have not seen for a while. I won't go into much detail since other QL sources cover it better than I could. There was not much new on the programming front. C68 version 3.14c was released just before the show. Al Boehm had a nice cloud simulator running on the QXL. I'm looking forward to it's public release. I did pick up some copies of two older compilers; Computer One Pascal and ProFortran 77. ProFortran 77 had a bad microdrive, but I found someone that has a good copy. I should get it soon. Computer One Pascal reminds me of TurboPascal. It's got everything in one package, editor, compiler, linker. I'll have to give it a test drive soon. While in Rhode Island, I decided to actually buy some new software for my QL. I've been interested in the Pointer Environment for a while, so I picked up QPAC II. Now I need a mouse, so I bought SERMouse. But, SERMouse has a problem with the standard QL serial port (as buggy as it is). So I bought the HERMES chip. Don Walterman had a $10 2400 baud modem, so I bought it and doubled my modem capability. I've moved my Hayes 1200 Personal modem (a modem that looks like a wall transformer) to Z88 duty. With a nice convertor cable, it works fine. Now I have enough software to require a boot disk. I put all of the software listed above, plus a few other utilities on one disk and created a boot program. I saw one that John Impellizzeri had and copied it. When software is loaded on the QL, it's done in silence. On an MS-DOS system, each package usually prints something to the screen letting the user know it's loading. This is fairly handy when trying to debug stuff, so I did the same on the QL. I open a little window and print a line stating what application is loading. This shows a progression of software being loaded. Then when it's all done, I just do a new. Now the QL is loaded, SuperBasic is empty, and ready to go. The only problem I have is QEM clashing with SERMouse. Both are trying to get the SER2 port. Since I have SERMouse running as a job, I just RJOB it before running QEM. The only hard part is getting behind the QL to switch the modem cable with the serial mouse. As for QL Internet news, I recently FTPed a copy of ELVIS from maya.dei.unipd.it. ELVIS is a clone of the Unix editor, VI. I've seen it listed by some QL PD distributers, but it took a while for it to hit the net. I've unzipped the executable and it looks and feels like VI. Not that I'm a big fan of VI, it just nice to have another editor that is fairly portable. I also downloaded MINEFIELD, a Freeware Pointer Environment program, by Philipe Trion that is a clone of the MS-Windows game, Mine Sweeper. Besides QPAC II, MINEFIELD is the first PE program that I ran. It was a great demo of the PE. Maybe it should be distributed with other PE starter packages like QPAC II. My wife got hooked on it and stayed up to 1 AM playing it. Now if someone would do a PENTE program for the PE. And the last thing. The last weekend in August is ComputerFest, here in Dayton. I plan on helping man the Computer Museum booth with Gary Ganger. But the important thing is that I'll be hosting the 2nd Annual QHJ Bar-B-Q at my house. Like last year, I'll provide burgers, hot dogs, plates, etc. People will probably need to bring drinks (unless someone brings a few 2 litre bottles). I'll have directions at the Fest. Just drop by the Sinclair booths. Until next time, Happy Hacking. COMPLEX ASCII ROTATION While reading one of the many computer magazines that I read in a month (got to have something to do while I eat my lunch), I read an article in which the columnist talked about security and mail. He was commenting about how open Internet mail (and e-mail in general) is fairly open and easy to tap into. Simple Mail Transfer Protocol (SMTP), the Internet's mail protocol, only handles ASCII mail. Most e-mail is sent in the clear, no encryption. Since a mail message can pass through many different sites, it can be copied and read at almost any point. Having been a Unix system administrator, I know how mail can bounce and be sent to the "Postmaster" for resolution. As "Postmaster" I read other persons mail to figure out where it was supposed to go® I like to tell people that e-mail is about as private as a post card. You don't write very private stuff on a post card, so don't do it with e-mail. Another general rule of e-mail is not to write anything in a mail message that you would not like to see on the front page of your local newspaper. I got to thinking about using encryption for e-mail ( encryption is a hot topic these days). But, I did not want to go the the extremes of using Public-Key encryption or DES. Since the whole idea is to make your mail unreadable to the general perusal, a fairly simple algorithm would work. I also wanted something that could be very easy to port and was not dependent on any computer platform. So, the whole scheme had to rely heavily on the password the user uses. Ok, now to the details. What this program does is more than just simple rotation of the characters. Simple rotation is just adding a constant number to all characters. If you rotated by 2, A becomes C, B becomes D, etc. Way too simple. So to mix it up, a rotation array is made out of the password. For each letter of the password, a MOD 7 is done and the results put into the array. The longer the password the longer the array. You could make the program do simple rotation with a one character password. Once the array is created, the program goes through the input file, using the rotation array to rotate a number of characters. The program will cycle through the rotation array many times (like a circular queue) until it reaches the end of the input file. The output file has the general look of the input file (newlines are not touched), but the words are now meaningless. This encryption is not unbreakable, but to 99% of the population it is unreadable. Someone would have to beŠpretty determined to try to unencrypt it. Since this was a fairly simple program to write, I wrote a version in SuperBasic and C. When I get my Fortran compiler up and running, I'll try to port it to Fortran. Then I'll try Pascal to try out Computer One Pascal. 100 OPEN #3,con_250x150a50x50 110 PAPER #3,0 : INK #3,4 : BORDER #3,4 : CLS #3 120 INPUT #3,"Name of Input File : ";in_file$ 130 INPUT #3,"Name of Output File : ";out_file$ 140 INPUT #3,"Password : ";password$ 150 INPUT #3,"Rotate or Unrotate (U/R) : ";rot$ 160 IF rot$="r" THEN rot$="R" 170 IF rot$="u" THEN rot$="U" 180 IF rot$<>"U" AND rot$<>"R" THEN GO TO 150 190 DIM rot(30) 200 REMark Create Rotation Array 210 pass_len = LEN(password$) 220 FOR x = 1 TO pass_len 230 rot(x) = CODE(password$(x)) MOD 7 240 NEXT x 250 OPEN_IN #4,in_file$ 260 OPEN_NEW #5,out_file$ 270 rot_mark = 1 280 REPeat loop 290 IF NOT EOF(#4) THEN 300 in$ = INKEY$(#4,-1) 310 ELSE 320 EXIT loop 330 END IF 340 IF CODE(in$) < 32 THEN 350 PRINT #5,in$; 360 ELSE 370 LET temp = CODE(in$) 380 IF rot$="R" THEN temp = temp+rot(rot_mark) 390 IF rot$="U" THEN temp = temp-rot(rot_mark) 400 PRINT #5,CHR$(temp); 410 END IF 420 rot_mark = rot_mark + 1 430 IF rot_mark > pass_len THEN rot_mark = 1 440 END REPeat loop 450 CLOSE #4 : CLOSE #5 460 PRINT #3," Done " 470 CLOSE #3 /* Complex ASCII Rotation This program takes as input a password and an ASCII file. From the password a rotation queue is derived. Then the incomming file is processed using the rotation queue to rotate each character differently then those to it's left and right. The end result is an output file with the rotated text. The program also reverses the process and will produce the original text out of the rotated file. */ #define ROTATE 1 #define UNROTATE 0 #include /* Global Array for holding Rotation Queue */ int rot_array[30]; main () { int c, i, fd1, fd2, rot_mark, pass_len, rot; char *password; printf("Enter the Input File : "); fd1 = open_file("r"); printf("Enter the Output File : "); fd2 = open_file("w"); printf("Enter a Password : "); gets(password); pass_len = strlen(password); /* generate the rotation array from the password */ for ( i=1; i<=pass_len; i++) rot_array[i] = password[i] % 7; printf("Rotate or Unrotate (U/R) : "); c = getchar(); putchar(c); printf("\n"); if ( c == 'R' || c == 'r' ) rot = ROTATE; else rot = UNROTATE; /* Start of the main part of the program */ rot_mark = 1; while (( c=getc(fd1)) != EOF) { if ( !isprint(c) ) putc(c,fd2); else { if ( rot == ROTATE ) c = c + rot_array[rot_mark]; else c = c - rot_array[rot_mark]; putc(c,fd2); rot_mark++; if ( rot_mark > pass_len ) rot_mark = 1; } } printf("\n Done!\n\n"); } /* This procedure gets a file name and opens it. if it fails, it aborts the program. It takes three values "r", "w", "a" for Read, Write, Append. Usage: file_pointer = open_file("r"); */ open_file(rwa) char *rwa; { char filename[30]; int fd; gets(filename); fd = fopen(filename, rwa); if ( fd == NULL) { printf("\n Error Opening File! \n"); abort(-1); } return fd; } APPROXIMATE STRING MATCHING In past issues, string matching has been a reaccuring theme. In the April 1994 issue of "C Users Journal" there was an article on string matching that uses a different algorithm for guessing how close two strings are; Frequency Distribution. Frequency Distribution counts how many occurances of each letter appear in the word. For "test", t=2, e=s=1. For "shasta", s=a=2, h=t=1. To guess how close two words are the frequency distribution is compared. If a word has about the same frequency distribution, then it is assumed to be equal. There lies the weakness of this algorithm. The author states that anagrams, like "BAT" and "TAB", would appear to be the same, since they have the same frequency distribution, where they are not. I think this can be extended to all words that are spelled differently but have the same letters in them. A simple example would be "TEA" and "ATE". They are not an anagram, but they have the same frequency distribution. I would expect that only a small percentage of words would fail in this algorithm. This algorithm would be usefull for a rough comparison. Adding another algorithm to check the cases that might cause this algorithm to fail would be usefull. #include int freq_match(mask, test) char *mask; char *test; { static int freq_count[256]; /* the frequency */ /* distribution */ int divergence; int i; /* freq_match("maskwork",""); */ if (test[0] == '\0) { /* initialize the distribution array */ for (i=0; i<256; i++) freq_count[i++] = 0; /* compute the distribution */ for ( i=0; mask[i] != '\0'; i++) freq_count[mask[i]] += 1; /* return a zero for initialization */ return 0; } /* freq_match("don't care","testword"); */ else { /* subtract the freq. dist. of the test word */ for (i=0; test[i] != '\0'; i++) freq_count[test[i]] -= 1; /* compute the divergence */ for (divergence=0, i=0; i<256; i++) divergence += abs(freq_count[i]); /* this code is to reset the freq. dist. */ /* back to the settings for the mask word */ for ( i=0; test[i] != '\0'; i++ ) freq_count[test[i]] += 1; return divergence; } } void main() { char mask[80], test[80]; printf("Mask:"); gets(mask); printf("Test:"); gets(test); freq_match(mask,""); printf("The divergence is %d.\n", freq_match("",test)); } HELLO, WORLD In a number of recent postings to alt.folklore.computers, the answer to the question "What's the shortest 'Hello, World' program for a specific language? For those that don't know, 'Hello, World' is the first program used in K&R's book on C and is synonomus with the simplest program to write as a beginner. The whole scope of the program is to print the string 'Hello, World'. Here is a summary of the results of the postings: BASIC: ------ 10 PRINT "Hello, World" FORTRAN IV: ----------- WRITE(6,100) 100 FORMAT(13H Hello World!) ST OP EN *D COBOL: ------ IDENTIFICATION DIVISION. PROGRAM-ID. WORLD. ENVIRONMENT DIVISION. DATA DIVISION. PROCEDURE DIVISION. BOO. DISPLAY "Hello, world!" UPON CONSOLE. STOP RUN. ADA: ---- With STANDARD_IO; Use STANDARD_IO; Procedure HELLO_WORLD is begin Put("Hello world!"); end HELLO_WORLD; C: -- #include main() { printf('Hello, World!"); } perl: ----- #/usr/local/bin/perl print "Hello, World!\n"; Postscript: ----------- 100 100 moveto /Times-Roman findfont 24 scalefont (Hello, World!) show showpage FORTH: ------ ."Hello, World!" Lisp: ----- (print "Hello, World!") Smalltalk: ---------- 'Hello, World!' PrintN1 ! PILOT: ------ 10 T:Hello, World! REXX: ----- SAY "Hello, World!" Modula-2: --------- MODULE HelloWorld; IMPORT InOut; BEGIN WriteString ("Hello, World!"); WriteLn; END HelloWorld. Here is an interesting one in C. I have not checked this one to see if it runs under C68. #define _ ¨int) putchar main() { int c = 0110 ;_(c);c+=29;_(c);c+=7 ;_(c);_(c);c+=3;_(c) ;c=0x20;_(c);c=0127 ;_(c);c+=24;_(c); c+=0x3;_(c);c-= 6;_(c);c-=010; _(c);c=0x21; _(c);_(10) ;c+=66;} Nokolisp: (which had only symbols - no character strings) --------- (compress (append (explode 'Hello) (cons 32 (explode 'world.)))) NATURAL LANGUAGE A while back I asked for some help in porting a program listed in the C Users Journal. Emiliano Barbaini of Italy volunteered to help. I sent him the code and in return he sent me back the changes he made to get the program to run on the QL and on an BULL DPX/2. I took the changes, made them, and then compiled the program. It compiled fine, but I did not notice any changes to the output (still did not work). Emiliano used C68 v 3.05 where as I used v 3.03 (Hey, I know 3.14c is the latest, just shows how often I use C68). This might have made the difference. I want not too sure about presenting the code here, but Emiliano was able to get it running on a QL. So, I'm sure it's just something that I am doing (or not doing). Give it a try and see how it works for you. There were some follow on articles in the C Users Journal that expanded this Natural Language program. If you are interested, I can photocopy the articles for you. The source code for recent issues of the CUJ are available on the Internet. /* natural_c */ #include #include #include #include #define ING 73 /* Restriction for ING word */ void initialize(void); void reset_sentence(void); void get_record(char *); char *extract_word(void); int match_record(char *, int); char *extract_root(void); void check_underlying(void); int check_type(char *,int); void check_subject(void); void check_action(void); void check_place(void); void make_response(void); void make_answer(int); void get_verb_ing(void); int match_verb_ing(void); FILE *infile; char dic_record[80]; int sentence; int word_ct; char word_array[10][15]; char root_array[10][15]; char prime_types[10][11]; char phrases[10][11]; char type_array[10][5][11]; char subjects[20][15]; char actions[20][15]; char places[20][31]; char response[80]; void main() { char *cur_word; char in_sentence[80]; initialize(); if ((infile = fopen("diction", "r+")) == NULL) { printf ("\nError opening dictionary\n"); exit(0); } printf("\nSentence: "); while(gets(in_sentence)) { if (in_sentence[0] == '\0') break; reset_sentence(); cur_word = strtok(in_sentence, " "); while(cur_word != NULL) { get_record(cur_word); cur_word = strtok(NULL, " "); if (++word_ct > 9) break; } check_underlying(); check_subject(); check_action(); check_place(); make_response(); printf("Response: %s\n\nSentence: ", response); if (++sentence > 19) break; } /* end while */ fclose(infile); return; } /*****************************************************/ /* Initialize variables (subjects, actions and */ /* places arrays contain entries for 20 sentences). */ /*****************************************************/ void initialize() { int i; for (i=0; i<20; i++) { subjects[i][0] = '\0'; actions[i][0] = '\0'; places[i][0] = '\0'; } sentence = 0; return; } /*****************************************************/ /* These variables are initialized for each new */ /* input sentence (each of the 10 word entries for */ /* the input sentence has 5 type_array entries). */ /*****************************************************/ void reset_sentence() { int i,j; word_ct = 0; for (i=0; i<10; i++) { word_array[i][0] = '\0'; root_array[i][0] = '\0'; prime_types[i][0] = '\0'; phrases[i][0] = '\0'; for (j=0; j<5; j++) type_array[i][j][0] = '\0'; } return; } /*****************************************************/ /* Get all the records from the dictionary. If the */ /* passed word is not in the dictionary, then the */ /* word could be a name. */ /*****************************************************/ void get_record(char *pass_word) { int types = 0; rewind (infile); fgets(dic_record, 80, infile); while (! feof(infile)) { if (match_record(pass_word, types) == 0) types++; fgets(dic_record, 80, infile); /***** Deleted Line puts(dic_record); *********/ } if (types == 0) { /*** Old Line ***/ if (isupper( (int) pass_word[0])) /**** ***/ if (isupper(pass_word[0]) != 0) strcpy(type_array[word_ct][types], "NAME"); else strcpy(type_array[word_ct][types], "NOTFOUND"); } strcpy(word_array[word_ct], pass_word); return; } /*****************************************************/ /* Compare the passed word with the word in the */ /* current dictionary record. If they are the same, */ /* then extract the type (NOUN, VERB, etc.). If the */ /* type is a VERB, then also extract the root and */ /* and copy it to the root array. */ /*****************************************************/ int match_record(char *pass_word, int types) { int i, j; char *root; char *dic_word; dic_word = extract_word(); /* Check if passed word equals dictionary word */ if (strcmpi(pass_word, dic_word) != 0) return(1); /* Word found, get the type */ for (i=14,j=0; i<20; i++) { if (isspace(dic_record[i])) break; type_array[word_ct][types][j++] = dic_record[i]; } /* Trim the type */ type_array[word_ct][types][j] = '\0'; if (strcmp(type_array[word_ct][types], "VERB") == 0) { root = extract_root(); strcpy(root_array[word_ct], root); } return(0); } /*****************************************************/ /* Extract the word from the dictionary. The word is */ /* 14 characters in length and starts in column 1. */ /*****************************************************/ char *extract_word() { int i, j; char dic_word[15]; for (i=0,j=0; i<14; i++) { if (isspace(dic_record[i])) break; dic_word[j++] = dic_record[i]; } /* Trim the dictionary word */ dic_word[j] = '\0'; return(dic_word); } /*****************************************************/ /* Extract the root from the dictionary. It */ /* identifies a group of similar words (the root for */ /* run, ran, runs and running is run). It is 14 */ /* characters in length and starts in column 35. */ /*****************************************************/ char *extract_root() { int i, j; char root[15]; for (i=34,j=0; i<48; i++) { if (isspace(dic_record[i])) break; root[j++] = dic_record[i]; } /* Trim the root */ root[j] = '\0'; return(root); } /*****************************************************/ /* Determine if the input sentence contains a known, */ /* underlying structure. If it does, then assign the */ /* correct types and phrases for the words. */ /*****************************************************/ void check_underlying() { int i; /* Structure WH-AUX-NAME-VERB */ i = 0; if ( (check_type("WH", i) == 0) && (check_type("AUX", i+1) == 0) && (check_type("NAME", i+2) == 0) && (check_type("VERB", i+3) == 0) ) { strcpy(prime_types[i], "WH"); strcpy(prime_types[i+1], "AUX"); strcpy(prime_types[i+2], "NAME"); strcpy(prime_types[i+3], "VERB"); strcpy(phrases[i], "WHQUESTION"); strcpy(phrases[i+1], "VERBPHRASE"); strcpy(phrases[i+2], "NOUNPHRASE"); strcpy(phrases[i+3], "VERBPHRASE"); return; } /* Structure WH-AUX-VERB-NAME *** NEW **** */ i = 0; if ( (check_type("WH", i) == 0) && (check_type("AUX", i+1) == 0) && (check_type("VERB", i+2) == 0) && (check_type("NAME", i+3) == 0) ) { strcpy(prime_types[i], "WH"); strcpy(prime_types[i+1], "AUX"); strcpy(prime_types[i+2], "VERB"); strcpy(prime_types[i+3], "NAME"); strcpy(phrases[i], "WHQUESTION"); strcpy(phrases[i+1], "VERBPHRASE"); strcpy(phrases[i+2], "VERBPHRASE"); strcpy(phrases[i+3], "NOUNPHRASE"); return; } /* Structure NAME-AUX-VERB-PREP-DET-NOUN */ if ( (check_type("NAME", i) == 0) && (check_type("AUX", i+1) == 0) && (check_type("VERB", i+2) == 0) && (check_type("PREP", i+3) == 0) && (check_type("DET", i+4) == 0) && (check_type("NOUN", i+5) == 0) ) { strcpy(prime_types[i], "NAME"); strcpy(prime_types[i+1], "AUX"); strcpy(prime_types[i+2], "VERB"); strcpy(prime_types[i+3], "PREP"); strcpy(prime_types[i+4], "DET"); strcpy(prime_types[i+5], "NOUN"); strcpy(phrases[i], "NOUNPHRASE"); strcpy(phrases[i+1], "VERBPHRASE"); strcpy(phrases[i+2], "VERBPHRASE"); strcpy(phrases[i+3], "PREPPHRASE"); strcpy(phrases[i+4], "PREPPHRASE"); strcpy(phrases[i+5], "PREPPHRASE"); return; } return; } /*****************************************************/ /* Compare the passed type with all the types for */ /* this word in the type_array. If the type is */ /* found, then return 0. The pass_number parameter */ /* identifies the word in the input sentence. */ /*****************************************************/ int check_type(char *pass_type, int pass_number) { int i; for (i=0; type_array[pass_number][i][0]; i++) { if (strcmp(type_array[pass_number][i], pass_type) == 0) /* Passed type is found in array */ return(0); } /* Passed type is not found in array */ return(1); } /*****************************************************/ /* If the correct type is "NAME", then the word */ /* refers to a subject so copy the word to the */ /* subjects array. */ /*****************************************************/ void check_subject() { int i; for (i=0; i= 0; i--) { if ( (strcmp(subjects[i], subjects[sentence]) == 0) && (strcmp(actions[i], actions[sentence]) == 0) && (strlen(places[i]) != 0) ) { make_answer(i); return; } } /* Not enough information in actions and */ /* subjects arrays. */ strcpy(response, "I don't know"); return; } /*****************************************************/ /* Generate a response that states the location of */ /* where the subject and action occured. */ /*****************************************************/ void make_answer(int prev_sentence) { strcpy(response, subjects[prev_sentence]); strcat(response, " "); strcat(response, "was "); get_verb_ing(); strcat(response, places[prev_sentence]); return; } /*****************************************************/ /* Retrieve the ING version of the word from the */ /* dictionary (the ING version of run is running). */ /*****************************************************/ void get_verb_ing() { rewind (infile); fgets(dic_record, 80, infile); while (! feof(infile)) { if (match_verb_ing() == 0) break; fgets(dic_record, 80, infile); } return; } /*****************************************************/ /* If the root in the current dictionary record */ /* matches the root in the actions array, and the */ /* current dictionary record has an ING restriction, */ /* then extract the dictionary word and return 0. */ /*****************************************************/ int match_verb_ing() { int i; char *root; char *dic_word; root = extract_root(); if (strcmp(actions[sentence],root) == 0) { /* Root found, look for ING restriction */ for (i=24; i<33; i++) { if (isspace(dic_record[i])) break; if (dic_record[i] == ING) { dic_word = extract_word(); strcat(response, dic_word); return(0); } } } return(1); } Dictionary file : diction a DET the DET house NOUN street NOUN store NOUN jump NOUN go VERB go goes VERB go going VERB I go went VERB go run VERB run runs VERB run running VERB I run ran VERB run walk VERB walk walks VERB walk walking VERB I walk walked VERB walk jump VERB jump jumps VERB jump jumping VERB I jump jumped VERB jump is AUX was AUX to PREP in PREP on PREP where WH