         Q L   H A C K E R ' S   J O U R N A L
      ===========================================
           Supporting  All  QL  Programmers
      ===========================================
         #7                        January 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/

Editor's Forumn
   By Tim Swenson
   
   With this issue the QHJ is now 1 year old.  It's been fun
publishing the QHJ.  It's given me a chance to write the
kind of articles that I want to write; those aimed at QL
programmers. It's also kind of neat to have complete
editorial control.

   Over the last year I have "meet" a number of interesting
QL users from all over.  Just via e-mail I have meet 4 QL
users.  With QL users/programmers spread out all over the
globe, e-mail is a very effective way to keep in contact. 
It's far faster then regular mail.

   For those QL persons interested in getting into e-mail on
the Internet, there are a number of Public Access Unix
systems around the country that allow users e-mail access
for free or a minimal fee.  If you are interested, let me
know and I'll send you a listing of Public Access Unix
systems.  Odds are there is one near you.  If you are at
college, see you Computer Department to see if they have any
computers on the Internet.  You could get access this way.

   One note about format.  I've dispensed with the Table of
Contents.  Now that I am using MicroEmacs and a print
formating program, I don't know what page each article
starts on until I print the issue.  In the past I've printed
it twice, once to find out the page numbers.  To have time
and effort, I've cut out the Table of Contents.  Since the
QHJ is not very big, I foresee any problems.

   A note of warning to any MicroEmacs users out there. 
I've run across a bug with both versions 3.8 and 3.9 when
you have run out of memory.  I ran out of memory while
putting this issue together.  I deleted a few characters to
bring me below the limit and then saved the file.  The save
took too long and only twice briefly wrote to disk. 
MicroEmacs said that X lines were written, but upon
switching to QDOS, I found that the file had no size.  I had
to delete quite a number of lines before it would save properly.

   To prevent this problem, I've recompiled MicroEmacs (3.9)
and told it to grab all available memory.  I will only use
this version of MicroEmacs when doing large files.

   Well, I wish the best to you all for the new year.  May
your code be error free and you compiles fast.

Core Wars
   By Tim Swenson
                              
   A while back I took a week off from work to spend time at
home finishing some programming projects (hey, I had plenty
of leave stored up).  One of the projects that I finished
was in implementation of Core Wars for the QL.

   Below is the documentation that I wrote for the program. 
It details the whole program.
             
    INTRODUCTION
    
     Core Wars is a game where programs that the user's
write, battle with each other in the computer.  This game
is taken from the Computer Receations column of Scientific
American.  The game was conceived by A. K. Dewdney.

     Two programs are loaded into "memory" and then each
program is executed in turn, one line at a time.  When the
computer can not execute a command from one of the programs,
it declares that program to be the loser.

     Programs are written in an assembly-like language
called REDCODE.  The syntax of REDCODE will be explained
later.

HOW TO RUN THE PROGRAM

  Use LRUN to load and run the SuperBasic version.

    When the program starts you will be asked to enter the
file name of the first program.  All REDCODE programs are
stored in a separate file and then loaded by the program.
Enter the name file containing one of the REDCODE programs
you want to run.  You must include the drive name (flp1_,
mdv1_, etc).  A number of sample programs are included on
the distribution disk.  They have the _CW extention.

    You will be prompted for the file name of the second
REDCODE program you want to run.  You man run any
combination of REDCODE program.

    The screen will clear and two different windows will
appear.  As each program executes, the current line of the
program will be printed in the window designated for it.  It
will also print the memory location at which that line is
stored.  An example is:
         392  MOV -1 0
    This shows that the command MOV -1 0 was executed a
memory location 392.  These windows will allow you to see
each program as they execute.

    Below these windows is an area that will display the
results of each program as they move through memory.  Memory
consists of 1000 memory locations.  A graphics display of
memory had been broken down to two lines of 500 pixels.  As
each program affects memory, it will be reflected in this
graphics display.  You will not see the display at first,
since empty memory locations are black.  Memory locations
affected by Program 1 will appear as green, where as those
affected by Program 2 will appear as red.

    This display will vividly show you how the progams are
moving through memory and where they stand in relation to
each other.

    When a REDCODE program line can not be executed, the
word "ERROR" will be printed in the window of the losing
REDCODE program and the Core Wars program will stop and
return you to QDOS.

REDCODE PROGRAMMING

    REDCODE is an assembly-like language consisting of
commands and operands.  Each line of the program has one
command and one or two operands, depending on the specific
command.

    Below are the REDCODE commands:

MOV A B  - Move
           Move contents of address A to address B
ADD A B  - Add
           Add contents of address A to the contents of
           address B and store results in address B
SUB A B  - Subtract
           Subtract contents of address A from contents of
           address B and store results in address B
JMP A    - Jump
           Transfer control (goto) to address A
JMZ A    - Jump Zero
           Jump to address A if contents of address B are
           equal to zero
JMG A    - Jump Greater Than Zero
           Jump to address A if contents of address B are
           greater than zero
JMN A    - Jump Not Zero
           Jump to address A if contents of address B are
           not equal to zero
DJZ A B  - Decrement Jump Zero
           Subtract (decrement) 1 from the contents of
           address B and jump to address A if the contents
           of address B are equal to zero
DJN A B  - Decrement Jump Not Zero
           Decrement 1 from the contents of address B and
           jump to address A if the contents of address
           B are not equal to zero
CMP A B  - Compare
           Compare contents of address A and B then skip the
           next instruction if not equal
DAT A    - Data
           Non-executing statement.  Used to make a memory
           location useable for storage of data

    All REDCODE addresses are relative.  PC is the Program
Counter.  The Program Counter controls the execution of the
programs and is equivilent to the memory location of the
current program line.  MOV 0 1 means to put the contents of
address PC+0 (the current line) and put it in address PC+1
(the next line).  Negative numbers are used to mean lines
before the PC.

    REDCODE does make provisions for direct and indirect
addressing.  A # before a number is direct addressing and an
@ is used for indirect.  MOV #0 1 means to put the number 0
in the address PC+1.  The indirect commands:
                   DAT 20
                   MOV 0 @-1
    means to put the contents of address PC+0 and store it
at the address pointed at by the number at PC-1 (the
previous line).  This will put the MOV 0 @-1 line at PC+20.
Note that you must use a DAT statment and not just store the
number 20 at that memory location.  This is what the DAT
statement is for.

    Indirect addressing may be used for both A and B
operands, direct may be used for A, and direct may only be
used for B with the CMP command.  All other commands may not
use direct addressing of B.

    The MOV and DAT commands interact differently together.
If you MOV #0 20 and address PC+20 is a DAT statement, the
DAT 0 is stored at address PC+20.  If there is no DAT
steatement, the only 0 is stored at PC+20.  This second case
is used to put "logic bombs" into other programs, so that
when the 0 is reached, the Core Wars program has reached an
unexecutable command line and that program loses.

REDCODE PROGRAM FILES

    REDCODE programs are stored in a separate file and
loaded at runtime.  The file is an ASCII file created with
an ASCII editor, like ED, or The Editor.  Each command line
is on a separate line.  No blank lines are allowed in the
file.  The commands must start in the first column.  Only 1
space may separate commands and operands.  Comments are
allowed by using a # in the first column.  With a # comment,
the entire line is commented out and is ignored by the Core
Wars program.

    See the example REDCODE programs included on the
distribution disk to help clarify any questions you may
have.

Source Code:

** Program Name: CoreWars
** Version:      2.0
** Author:       Timothy Swenson
** Date Created: 17 Oct 1990
** Date Revised: 19 Oct 1990

**  The is an implementation of Core Wars as found in the
**    Computer Recreations column in Scientific American.

 top_mem=1000
 DIM memory$(top_mem,14)

@label
 pc_prg1 = RND(1 TO 1000)
 pc_prg2 = RND(1 TO 1000)
 IF ABS(pc_prg1-pc_prg2)=100 THEN GO TO @label

 OPEN #3,con_512x256a0x0_32
 PAPER #3,0 : INK #3,4 : CLS #3
 title
 PRINT #3
 PRINT #3,TO 30;"Version 2.0"
 PRINT #3,TO 26;"By Timothy Swenson"\\

 load_prg(pc_prg1)
 load_prg(pc_prg2)

 CLS #3
 title

 OPEN #4,scr_200x150a51x51
 OPEN #5,scr_200x150a262x51
 BORDER #4,2,4 : BORDER #5,2,4
 PAPER #4,2 : INK #4,1
 PAPER #5,2 : INK #5,1
 CLS #4 : CLS #5

 CURSOR #3,70,40
 PRINT #3," P R O G R A M  # 1"
 CURSOR #3,300,40
 PRINT #3," P R O G R A M  # 2"

 REPeat main_loop
   pc_main = pc_prg1
   prog=1
   cmd$=memory$(pc_main)
   PRINT #4,pc_main;" ";cmd$
   sep_line
   evaluate
   plot_mem pc_prg1

   IF pc_prg1=pc_main THEN
       pc_prg1=pc_prg1+1
   ELSE
       pc_prg1=pc_main
   END IF

   pc_main = pc_prg2
   prog=2
   cmd$=memory$(pc_main)
   PRINT #5,pc_main;" ";cmd$
   sep_line
   evaluate
   plot_mem pc_prg2

   IF pc_prg2=pc_main THEN
       pc_prg2=pc_prg2+1
   ELSE
       pc_prg2=pc_main
   END IF

   IF pc_prg1>top_mem THEN pc_prg1=pc_prg1-top_mem
   IF pc_prg2>top_mem THEN pc_prg2=pc_prg2-top_mem
 END REPeat main_loop

 STOP

DEFine PROCedure title
 LOCal x,y
   x=150 : y=10
   CSIZE #3,3,1
   OVER #3,1
   INK #3,2
   FOR z = 1 to 5
      CURSOR #3,x-z,y-z
      PRINT #3,"CORE WARS"
   NEXT z
   INK #3,4
   CURSOR #3,x-6,y-6
   PRINT #3,"CORE WARS"
   CSIZE #3,0,0
END DEFine title

DEFine PROCedure load_prg (location)
   INPUT #3,"Enter file name for Program : ";file_name$
   OPEN_IN #6,file_name$
   REPeat loop1
      INPUT #6,in$
      IF EOF(#6) THEN EXIT loop1
      IF in$(1)="#" THEN END REPeat loop1
      memory$(location)=in$
      location=location+1
   END REPeat loop1
   CLOSE #6
END DEFine load_prg

DEFine PROCedure plot_mem (num)
  LOCal x,y,c
     IF prog=1 THEN c=4
     IF prog=2 THEN c=2
     IF num>500 THEN
        x=num-500
        y=235
     ELSE
        x=num
        y=225
     END IF
     BLOCK #3,1,2,x,y,c
END DEFine plot_mem

DEFine FuNction get_num(address)
  LOCal temp$
   temp$=memory$(address)
   IF temp$="" THEN goto_error
   IF temp$(1 TO 3) <> "DAT" THEN goto_error
   RETurn temp$(5 TO)
 END DEFine get_num

 DEFine PROCedure sep_line
 LOCal blank
   IF LEN(cmd$)<5 THEN goto_error
   arg2 = 0
   opcode$=cmd$(1 TO 3)
   cmd$=cmd$(5 TO)
   opmodea=1 : opmodeb=1
   IF cmd$(1)="#" THEN opmodea=0
   IF cmd$(1)=CHR$(64) THEN opmodea=2
   IF opmodea<>1 THEN cmd$=cmd$(2 TO)
   IF (opcode$="JMP") OR (opcode$="DAT") THEN arg1=cmd$
: RETurn
   blank = " " INSTR cmd$
   arg1 = cmd$(1 TO blank-1)
   cmd$=cmd$(blank+1 TO)
   IF cmd$(1)="#" THEN opmodeb=0
   IF cmd$(1)=CHR$(64) THEN opmodeb=2
   IF opmodeb<>1 THEN cmd$=cmd$(2 TO)
   arg2=cmd$
 END DEFine sep_line

 DEFine PROCedure evaluate
   IF opmodea=0 THEN a=arg1
   IF opmodea=1 THEN a=pc_main+arg1
   IF opmodea=2 THEN a=pc_main+get_num(pc_main+arg1)
   IF opmodeb=0 AND opcode$<>"CMP" THEN PRINT #0,"Direct
mode not allowed to second argguement ": STOP
   IF opmodeb=0 THEN b=arg2
   IF opmodeb=1 THEN b=pc_main+arg2
   IF opmodeb=2 THEN b=pc_main+get_num(pc_main+arg2)
   IF a>top_mem THEN a=a-top_mem
   IF b>top_mem THEN b=b-top_mem
   IF opcode$="MOV" THEN MOV: RETurn
   IF opcode$="ADD" THEN ADD: RETurn
   IF opcode$="SUB" THEN SUBT: RETurn
   IF opcode$="JMP" THEN JMP: RETurn
   IF opcode$="JMZ" THEN JMZ: RETurn
   IF opcode$="JMG" THEN JMG: RETurn
   IF opcode$="JMN" THEN JMN: RETurn
   IF opcode$="DJZ" THEN DJZ: RETurn
   IF opcode$="DJN" THEN DJN: RETurn
   IF opcode$="CMP" THEN CMP: RETurn
   IF opcode$="DAT" THEN RETurn
    goto_error
 END DEFine evaluate

 DEFine PROCedure goto_error
  IF prog=1 THEN PRINT #4," E R R O R"
  IF prog=2 THEN PRINT #5,"E R R O R"
  FOR xx = 1 to 4
     BEEP 1000,10
  NEXT xx
  STOP
 END DEFine goto_error

 DEFine PROCedure MOV
   IF opmodea=0 THEN
     temp$=memory$(b)&"   "
     IF temp$(1 TO 3)="DAT" THEN
        memory$(b)="DAT "&a
     ELSE
        memory$(b)=a
     END IF
   ELSE
     memory$(b)=memory$(a)
   END IF
   plot_mem b
 END DEFine MOV

 DEFine PROCedure ADD
 LOCal temp
    IF opmodea=0 THEN
      temp=get_num(b)+a
    ELSE
      temp=get_num(b)+get_num(a)
    END IF
    memory$(b)="DAT "&temp
 END DEFine ADD

 DEFine PROCedure SUBT
 LOCal temp
    IF opmodea=0 THEN
      temp=get_num(b)-a
    ELSE
      temp=get_num(b)+get_num(a)
    END IF
    memory$(b)="DAT "&temp
 END DEFine SUBT

 DEFine PROCedure JMP
       pc_main = a
 END DEFine JMP

 DEFine PROCedure JMZ
 LOCal temp
    temp = get_num(b)
    IF temp=0 THEN JMP
 END DEFine JMZ

 DEFine PROCedure JMG
 LOCal temp
    temp = get_num(b)
    IF temp>0 THEN JMP
 END DEFine JMG

 DEFine PROCedure JMN
 LOCal temp
    temp = get_num(b)
    IF temp<>0 THEN JMP
 END DEFine JMN

 DEFine PROCedure DJZ
 LOCal temp
    temp = get_num(b)
    temp=temp-1
    memory$(b)="DAT "&temp
    IF temp=0 THEN JMP
 END DEFine DJZ

 DEFine PROCedure DJN
 LOCal temp
    temp = get_num(b)
    temp=temp-1
    memory$(b)="DAT "&temp
    IF temp<>0 THEN JMP
 END DEFine DJN

 DEFine PROCedure CMP
 LOCal tempa,tempb
   IF opmodea=0 THEN
      tempa=a
   ELSE
      tempa=get_num(a)
   END IF
   IF opmodeb=0 THEN
      tempb=b
   ELSE
      tempb=get_num(b)
   END IF
   IF tempa<>tempb THEN pc_main=pc_main+2
 END DEFine CMP

 DEFine PROCedure list_memory
   FOR x=1 TO top_mem
     PRINT memory$(x)
   NEXT x
 END DEFine list_memory

Example Core War Programs: (indented only for publication. 
Code must start in the first column)

IMP_CW:
   MOV 0 1

DWARF_CW:
   DAT -1
   ADD #5 -1
   MOV #0 @-2
   JMP -2

GEMINI_CW:
   DAT 0
   DAT 99
   MOV @-2 @-1
   CMP -3 #9
   JMP 4
   ADD #1 -5
   ADD #1 -5
   JMP -5
   MOV #99 93
   JMP 93

QLPatch
   By Tim Swenson
   
   One of the problems I've encountered in publishing souce
code in the QHJ, is the dilemma of publishing a newer version
of a program (or bug fixes).  I could publish the whole
program but this would waste bandwidth if only a small portion
of the code has been changed.

   Another way is to publish the changes to the program and
figure a way to have the user edit his version, add the
changes and create a newer (second) version.

   In my dealings with the Unix world, I have heard of a
program that does something like this, called Patch.  In
using Patch, the originator of the program runs the two
version of the code through a file comparison utility (like
Unix's diff) and creates a diff (or delta) file.  The end
user takes the diff file, version 1 of the program, and runs
it through Patch to create the second version.  Patch uses
the diff file as a guide to edit the source code.

   To make this work on the QL, I first needed a file
comparison program.  A few issues ago, I published FCOMP_C
from the C Users Group.  With only a few changes to FCOMP_C
I was able to get a program that would work for me.  I now
have FCOMP2_C.

    Now that I could generate a diff file, I needed a
version of Patch.  Not having the source code to Patch, I
decided to write my own version from scratch, based on the
output of FCOMP2_C.

    Below is the result of that effort, QLPatch.  This
version is only written in SuperBasic.  I plan to port it to
C, but I prefer to develop in SuperBasic because it's easier
than C.

    After the source code, I have included an example of
two versions of a file and the diff file for them.  Take
version 1 of the file, the diff file, and run them through
QLPatch and see that you indeed get the second version.

    After this is the diff file for FCOMP_C to create
FCOMP2_C.  Running this and FCOMP_C though QLPatch will give
you a version of FCOMP_C to use with QLPatch.

    If you encounter any bugs, please let me know.  I do
plan to use this program for the QHJ and I want it to work
as advertised.
    

** QLPatch
** By Timothy Swenson
**
**  This program takes version 1 of a file, a diff file
**    created by fcomp_c, and creates version 2 of the 
**    file.  This program can be used to distribute 
**    changes to a program with out having to redistribute
**    all of the source code.  You need only distribute those
**    lines needed.
**
**  This program can be used for any text file, be it a C
**    program, Achive program, or a text document.
**

TRUE = 1
FALSE = 0
IN_FILE = 4
DIFF_FILE = 5
OUT_FILE = 6

last_insert = FALSE

INPUT "Enter Souce File : ";in_file$
INPUT "Enter Diff File : ";diff_file$
INPUT "Enter Output File : ";out_file$

DELETE out_file$

OPEN #IN_FILE,in_file$
OPEN #DIFF_FILE,diff_file$
OPEN_NEW #OUT_FILE,out_file$

in_file_count = 1

REPEAT big_loop

   IF last_insert = FALSE THEN
      INPUT #DIFF_FILE,in$
      IF EOF(#DIFF_FILE) THEN EXIT big_loop
   ELSE
      last_insert = FALSE
   ENDIF
      
   IF LEN(in$) < 13 THEN END REPEAT big_loop
   IF in$(1 TO 13) = "Deleted line " THEN del_line
   IF in$(1 to 13) = "Deleted lines" THEN del_lines
   IF in$(1 to 13) = "Changed line " THEN chang_line
   IF in$(1 to 13) = "Changed lines" THEN chang_lines
   IF LEN(in$) < 19 THEN END REPEAT big_loop
   IF in$(1 to 19) = "Inserted after line" THEN insert
   
END REPEAT big_loop

** add rest of original file to new file
REPEAT loop
   INPUT #IN_FILE,in_file$
   IF EOF(#IN_FILE) THEN EXIT loop
   PRINT #OUT_FILE,in_file$
END REPEAT loop

@END
PRINT #OUT_FILE
CLOSE #IN_FILE
CLOSE #DIFF_FILE
CLOSE #OUT_FILE

** Start of Functions

DEFine FuNction EOS(x$)
   IF LEN(x$) < 3 THEN RETURN FALSE
   IF x$(1 to 3) = "To:" THEN RETURN TRUE
   IF LEN(x$) < 12 THEN RETURN FALSE
   IF x$(1 to 12) = "Deleted line" THEN RETURN TRUE
   IF x$(1 to 12) = "Changed line" THEN RETURN TRUE
   IF LEN(x$) < 19 THEN RETURN FALSE
   IF x$(1 to 19) = "Inserted after line" THEN RETURN TRUE
   RETURN FALSE
END DEFine EOS

DEFine PROCedure del_line
 LOCal z,x
   x = in$(14 to)
   FOR z = in_file_count TO x-1
      INPUT #IN_FILE,in_file$
      PRINT #OUT_FILE,in_file$
   NEXT z
   INPUT #IN_FILE,in_file$
   in_file_count = x+1
END DEFine del_line

DEFine PROCedure del_lines
 LOCal x,y,z,temp
   temp = "-" INSTR in$
   x = in$( 15 TO temp-1)
   y = in$( temp+1 TO LEN(in$)-1)
   FOR z = in_file_count TO x-1
      INPUT #IN_FILE,in_file$
      PRINT #OUT_FILE,in_file$
   NEXT z
   FOR z = x TO y
      INPUT #IN_FILE,in_file$
   NEXT z
   in_file_count = y+1
END DEFine del_lines

DEFine PROCedure insert
LOCal x,y,loop
   x = in$(21 TO)
   FOR y = in_file_count to x
      INPUT #IN_FILE,infile$
      PRINT #OUT_FILE,infile$
   NEXT y
   in_file_count = x+1
   REPEAT loop
      INPUT #DIFF_FILE,in$
      IF EOF(#DIFF_FILE) THEN GOTO @END
      IF EOS(in$) THEN EXIT loop
      IF LEN(in$) < 2 THEN
         PRINT #OUT_FILE,in$
      ELSE
         PRINT #OUT_FILE,in$(2 TO)
      ENDIF
   END REPEAT loop
   last_insert = TRUE
END DEFine insert

DEFine PROCedure chang_line
LOCal x,y
   x = in$(14 TO)
   FOR y = in_file_count TO x-1
      INPUT #IN_FILE,infile$
      PRINT #OUT_FILE,infile$
   NEXT y
   in_file_count = x+1
   INPUT #IN_FILE,infile$
   INPUT #DIFF_FILE,in$
   INPUT #DIFF_FILE,in$
   INPUT #DIFF_FILE,in$
   PRINT #OUT_FILE,in$(2 TO)
END DEFine chang_line

DEFine PROCedure chang_lines
 LOCal x,y,z,temp
   temp = "-" INSTR in$
   x = in$( 15 TO temp-1)
   y = in$( temp+1 TO LEN(in$)-1)
   FOR z = in_file_count TO x-1
      INPUT #IN_FILE,infile$
      PRINT #OUT_FILE,infile$
   NEXT z
   FOR z = x to y
      INPUT #IN_FILE,in$
      INPUT #DIFF_FILE,in$
   NEXT z
   INPUT #DIFF_FILE,in$
   REPEAT loop
      INPUT #DIFF_FILE,in$
      IF EOF(#DIFF_FILE) THEN GOTO @END
      IF EOS(IN$) THEN EXIT loop
      IF LEN(in$) < 2 THEN
         PRINT #OUT_FILE,in$
      ELSE
         PRINT #OUT_FILE,in$(2 TO)
      END IF
   END REPEAT loop
   last_insert = TRUE
   in_file_count = y+1
END DEFine chang_lines

Example files:

Version 1 File:
   This is line 1
   This is line 2
   This is line 3
   This is line 4
   This is line 5
   This is line 6
   This is line 7
   This is line 8
   This is line 9
   This is line 10
   This is line 11
   This is line 12
   This is line 13
   This is line 14
   This is line 15
   This is line 16
   This is line 17
   This is line 18

Version 2 File:
   This is line 1
   This is line 3
   This is line 4
   add a line here
   This is line 5
   This is line 6
   add a line here
   add a line here
   This is line 7
   This is a line 8
   This is line 9
   This is line 13
   This is line 14
   This is a line 15
   This is a line 16
   This is line 17
   This is line 18
   add a line here: 1
   add a line here: 2
   add a line here: 3

Diff File:
   Deleted line 2
    This is line 2
   Inserted after line 4:
    add a line here
   Inserted after line 6:
    add a line here
    add a line here
   Changed line 8
    This is line 8
   To:
    This is a line 8
   Deleted lines 10-12:
    This is line 10
    This is line 11
    This is line 12
   Changed lines 15-16:
    This is line 15
    This is line 16
   To:
    This is a line 15
    This is a line 16
   Inserted after line 18:
    add a line here: 1
    add a line here: 2
    add a line here: 3

Diff File for FCOMP2_C:
  (some lines wrapped for printing,so be carefull when
re-typing.)

Inserted after line 0:
 
Inserted after line 13:
    UPDATED:   11 Nov 91 by Timothy Swenson
         Added changes so that output will be sent to a file.
    Output file will be third file arguement.
 
      Usage: CRUN "flp1_fcomp2 flp1_file_ver_1
flp1_file_ver_2 flp1_diff"
Deleted line 25
 
Deleted line 28
 
Changed line 54
 #include <string.h>
To:
 #include <string_h>
Inserted after line 69:
 FILE *fd;
Changed lines 93-94:
    if(argc != 3)
       fatal("fcomp requires two file names.");
To:
    if(argc != 4)
       fatal("fcomp requires three file names.");
Inserted after line 95:
    if (( fd = fopen(argv[3],"w")) == NULL ) {
       fprintf(stderr,"Cannot open file %s.\n",argv[3]);
    }
    
Changed line 107
       puts("The files are identical.");
To:
       fputs(fd,"The files are identical.");
       fclose(fd);
Inserted after line 144:
        fclose(fd);
Inserted after line 169:
       fclose(fd);
Changed line 207
     printf("Inserted after line %d:\n",ep->line1);
To:
     fprintf(fd,"Inserted after line %d:\n",ep->line1);
Changed line 218
        printf("\nChanged ");
To:
        fprintf(fd,"Changed ");
Changed line 220
        printf("\nDeleted ");
To:
        fprintf(fd,"Deleted ");
Changed line 222
        printf("line %d\n",ep->line1);
To:
        fprintf(fd,"line %d\n",ep->line1);
Changed line 224
        printf("lines %d-%d:\n",ep->line1,a->line1);
To:
        fprintf(fd,"lines %d-%d:\n",ep->line1,a->line1);
Changed line 228
        printf(" %s",A[ep->line1-1]);
To:
        fprintf(fd," %s",A[ep->line1-1]);
Changed line 234
     printf("To:\n");
To:
     fprintf(fd,"To:\n");
Changed line 238
     printf(" %s",B[ep->line2-1]);
To:
     fprintf(fd," %s",B[ep->line2-1]);
Inserted after line 241:
    fprintf(fd,"\n");
Inserted after line 247:
    fclose(fd);
Inserted after line 254:
    fclose(fd);

The German Connection
   By Tim Swenson
   
   Through various means I have gotten in contact with
Franz Herrmann of the German QL User Group "Sinclair QL
User Club e. V."  After some communications, he sent me a
disk with some interesting files.

   Firstly, there is the IFE (Inter-group Freeware
Exchange) database that constains listings to over 53 MB of
QL programs.  The German group is organizing a collective
freeware library with a number of European QL groups (plus
1 American group).  This is sort of like the Quanta
library, but you do not have to be a member of any group to
benefit from the library.

   I got two files that listed and described the programs
and one file that listed the participating groups.  There
was an executable program that looked like it was supposed
to provide easy access to the descriptions, but I could not
get it to run (bad paramater error).  But since the files
are all text, I could still access them.

   There was also included a couple of the IFE circulars
(newsy newsletters) that sort of gave the story of the IFE
project.  Even thought this is from a German group, all
items are in English.

   The only problem I can see with the IFE is the language
barrier.  Some of the programs are not in English.  You
will need to have a knowledge of Italian to run some of the
Italian programs, but this is to be expected.

   Secondly, Franz sent me a text editor called QED but
succombed to the same problem as the IFE executable, a bad
parameter error.

   The last item is called QL-ZOO.  It is a version of the
Unix utility ZOO.  ZOO is a file compression utility like
PKZIP or compress.  As I understand, it will compress and
uncompress files that are compatable with the Unix version
of ZOO.  Unlike the other executables, I was able to get
QL-ZOO to run.

   For those that wish to contact Franz Herrmann his
address is:
        Franz Herrmann
        Talstrae 21
        W-5460 Ockenfels
        Germany
        Franz_Herrmann@bn.maus.de
   
New QL
   Reader Response from Giuseppe Zanetti
   
   [  Giuseppe has sent this response via e-mail from Italy.
 His English is far better than my Italian, but I did make a
few touch-ups where needed. - ED]

   Your idea on the last page of Novembers issue of the QHJ
was great ("Why not make a QL-Clone...").

   My idea is:
   
   CPU is a 68XXX mounted on a single PC-IBM board, so that
you can use the PCs hardware (better than QL equivalent) for
I/O.  I think it may be a static RAM mapped on lowest QL
memory, then load QDOS, disabling RAM write pin, emulate an
EPROM, and reset 68K.

   Keyboard, video and other I/O may be shared with PC via a
lot of dualport RAM and I/O ports (and IRQ).  Other side
features would be a 68K QL bus to connect to QL dependent
devices.  Memory up to 16 Meg.

   PC IBM Features (PC is used as a slave): QL screen
emulator (via VGA), keyboard, mouse, joystick, disks and
hard disks would format to QL format (and used as FLP and
FSK), networking, scanner and other features.

   It is also possible to use this board as a Mac emulator.
   
Editor's Response - This idea has possibilities.  It is
easier than designing a new QL from scratch.  Cost would be
just for the board and software, if the user already had a
PC compatible system.  I'd be a little worried that too many
kludges may have to be done to get it to work.  Designing a
new QL from scratch will take longer and cost more, but it
would be a cleaner solution.  This is just my opinion.  Plus
sometimes the ideal solution is not feasable.

