Q L H A C K E R ' S J O U R N A L
===========================================
Supporting All QL Programmers
===========================================
#28 July 1998
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
2455 Medallion Dr.
Union City, CA 94587
swensontc@geocities.com
http://www.geocities.com/SilconValley/Pines/5865/
****** Note New Mailing Address ********
EDITORS' FORUMN
I have not received much feedback on the Qliberator Source Book
idea I mentioned in the last issue, and I have received basically
no "helpfull hints". So, my plan is to write what I can, put it
out on the Net, and see what feedback comes from it. If I make
any mistakes, I'm sure there are many willing to point out my
failings :-). Sometimes the best way to get an answer to a
question is to give a wrong answer, then many will pop up to give
the real answer. I'll probably put in questions that I have in
the hope that they too will get answered.
A part of the material going into the Source Book is coming from
stuff I'm writing for this newsletter. I have taken the article
on adding Config Blocks to Qlib program and expanded it quite a
bit. A couple of articles from this issue will also go in. I
also plan to have sections that touch on the various SuperBasic
extentions and programming aids available to the SuperBasic
programmers.
In other news, besides having past issues of the QHJ available on
my web page, I've added a number of articles that I've written
for other newsletters. Check the link from the main page.
STRUCTURED SUPERBASIC 2.6
Structured SuperBasic is a utility that has been printed a couple
of times in this newsletter. I have recently dusted the program
off and completed a new version. SSB 2.6 has all of the
functionality of SSB 2.5, but I have added a lot of error
checking, fixed a few bugs, added Config Block support, Command
Line support, Environment Variable support, and compiled it with
Qliberator. The manual has been expanded from 5 pages to 16
pages. The whole package has been zipped and should be available
your local QL BBS and the Internet. It can be downloaded off my
web page at http://www.geocities.com/SiliconValley/Pines/5865/.
For those that don't remember what SSB is, it is a filter program
that takes SuperBasic code, written in the form for SSB and
converts it to full SuperBasic code. SSB allows for no line
numbers, blank lines between code segments, conditional
compilation, Include files, and other features. SSB allows for
more readable and maintainable code. Download the package, unzip
it, and give it a try.
REVISION CONTROL SYSTEM (RCS)
Whether it's source code or system configuration files, it's nice
to be able to keep track of changes made to files over time. The
Revision Control System is a collection of programs that keep
track of different versions (revisions) of text files. A special
file is created by RCS that contains information about the
changes in a file and allow the user to get back to any revision
of the file.
RCS was designed primarily for programmers to keep track of what
changes were made to various source files. It was derived from
an earlier Source Code Control System (SCCS) and was first
developed for the UNIX operating system.
Since RCS came from a UNIX background, it understands the
concepts of different users accessing the same files and allows
"checking out" files and locking them from being changed by other
users. RCS can be used by a number of programmers all working on
the same code. It keeps track of who has what file "checked out"
and who makes what changes.
Although most QL programmers program by themselves, RCS can help
the QL programmer bring a form of discipline to their
programming. This is especially usefull for what can be called
"full production" programs; commercial or freeware. Given the
long life of QL programs (I'm still using programs written more
than 10 years ago), RCS can keep track of what changes were made
from revision to revision and why. A programmer can keep make a
rule that each RCS revision be a bug fix and the reasons for the
fix can be logged as part of the revision history.
Enough blathering on, now to jump right into what RCS is and how
it works.
Original File - This is the text or source code file that you
want to keep track of. It can be any text file.
Revision File - This is a single file that contains the original
text/source file and all of the revision history. It has the
same name as the original file except that a ',v' is appended to
the end. If the original file is 'test_c', then the revision
file is called 'test_c,v'. A revision file keeps track of only
one original test/source file. If you have 3 source code files
that make up a single executable, then RCS will have three
revision fills.
Check-In - All revisions to a file are put into the revision file
by checking them in. The program 'ci' is used to do this. 'ci'
is used to either create a new revision file or to check-in a new
revision into an existing revision file. Once a file has been
checked into the revision file, it is deleted.
Check-Out - When you want to edit a text/source file that has
been checked in to the revision file, you use 'co' (check-out) to
extract the text/source file from the revision file.
The whole process of using RCS is basically checking-in and
checking-out the same file. RCS is not aware of time, so there
is no need to check-in a text file when you have finished a
programming session. You really only need to check-in a file
when you have made all of the edits you need. If you are fixing
a bug in a program, you would only check-in the file when you
have fixed it. No need to keep track of working copies of the
files.
To show you how RCS is used, I've written a short Structured
SuperBasic program:
OPEN #3,con_100x100a100x100
CLS #3
INPUT "Enter The Name of a File :";file$
OPEN_IN #4,file$
REPeat loop
INPUT #4,in$
IF EOF(#4) THEN EXIT loop
PRINT #3,in$
END REPeat loop
CLOSE #4
CLOSE #3
I stored this program in the file 'readfile_ssb'. I then checked
it in to RCS using the following command:
exec ci;"readfile_ssb"
'ci' then asks me for my initials. Since the QL does not have
the concept of multiple users (and therefore user names), a
persons initials are used to keep track of who has checked out a
file and who has made what changes to that file. 'ci' then asks
for a Description. The Description is where the "why" of the
file change is kept track of. If you are using RCS to keep track
of bug fixs for a program, the Description section would be used
to mention the bug, what caused it, and how it was fixed. I do
not believe there is a limit on how long the description can be.
The Description is ended by a line with just a period on it.
'readfile_ssb' is now checked-in to the RCS revision file and is
deleted.
Now to get the file back to edit it, I ran the following command:
exec co;"-l readfile_ssb"
I need to tell 'co' what file I want, but I also have to tell
'co' that I want to edit the file. If I ran 'co' without the
"-l" option, 'readfile_ssb' would have been created, but it would
not have been "checked-out" from the revision file, and when I
went to check it in, RCS would not have allowed it. Under UNIX,
'co' would create a 'read-only' copy of the file.
'co' asked for my initials. Since the same person who checks out
a file is the only person the can check in a file, be sure to
remember what initials you used.
'readfile_ssb' is now extracted from the RCS file and the
revision file is changed to show that the file is checked out.
Once I am done editing the file, I can then check in the file
using 'ci'. Again I am asked for my initials. The file is now
checked back into the revision file and deleted. If you copied
the file before checking it back in and you have edited this
file, you can not incorporate those changes into the revsion file
since the file is 'officially' checked in and can not be edited.
RCS uses revsion numbers to keep track of the different times a
file is checked-in. The first revision is 1.1, then 1.2, then
1.3 and so on. Let's say that I have edited my file 4 times, I'm
now at revision 1.5 and want to recall revision 1.3. I would run
the following command:
exec co;"-r 1.3 readfile_ssb"
'co' would then dig though the revision history and generate
version 1.3 of 'readfile_ssb'.
RCS comes with more than just the 'ci' and 'co' programs.
'rcsdiff' is used to compare a working "checked-out" file with
the version still in the revision file. 'rlog' is used to view
the revision file.
Below is the output of 'rlog' from my example session:
RCS file: readfile_ssb,v
Working file: readfile_ssb
head: 1.2
branch:
locks: strict
access list:
symbolic names:
comment leader: "# "
keyword substitution: kv
total revisions: 2;selected revisions: 2
description:
Creation of RCS Archive
----------------------------
revision 1.2
date: 1998/06/11 20:41:01; author: tcs; state: Exp; lines: +2
-2
This is a second version of this program.
----------------------------
revision 1.1
date: 1998/06/11 20:19:34; author: tcs; state: Exp;
Initial revision
============================================================
RCS is a lot more complicated that I've mentioned here. It, and
SCCS, is fully documented in the book "Applying RCS and SCCS" by
Bolinger & Bronson, published by O'Reilly & Associates. RCS for
the QL is available from most freeware sources. The QL RCS
distribution does not come with the 'diff' program which it
requires. 'diff' is available as part of the C68 distribution.
RCS can not be used with regular SuperBasic programming, as RCS
would see any new line numbers as a change in the file. If you
loaded a file into SuperBasic, did only a line renumber, saved
it, and then check it back into RCS, RCS would any line with new
line number as having changed. RCS works well with Structured
SuperBasic as it has no line numbers. It also works well with C,
Forth, Pascal, etc.
ENVIRONMENT VARIABLES
The concept of Environment Variables comes from the Unix world.
They are used slightly in MS-DOS, but not at all to the same
extent as UNIX. For the QDOS world, the file ENV_BIN provides a
number of extensions that allow the use of Environment Variables.
Essentially an environment variable is a variable that can be
"seen" by exectuable programs. In SuperBasic we can set up all
kinds of variables, but if we execute a program from SuperBasic,
these programs can not "see" these variables and get data from
them.
The purpose of environment variables is to change the
configuration of a program. They function like Config Blocks,but don't require running a program to make the change.
Let's take a quick look at how we can change the behavior of
programs. There are five different ways of doing this:
1] User Intervention. This is where the user uses a menu or
answers queries from the program.
2] Config Blocks. This feature is pretty unique to QDOS, but it
allows the user to change default options without having to know
how to edit a binary file.
3] Configuration File. This is a separate file that the program
reads to determine how to set defaults and run.
4] Command line Arguments. Instead of the program querying the
user for information, the user types it in when they execute the
program.
5] Environment Variables. The user sets a variable that is then
read by the program and changes its default settings.
Each of the options have their own place and their own benefits
and faults. Some are more permanent, like Config Blocks and
Config files, while some are very short lived, like User
Intervention and Command line Arguments. Enviroment Variables
are in between as they can be set in a BOOT program, but they can
be changed by just typing in a new command.
The ENV_BIN file comes with 4 extensions. They are:
SETENV - Defines an environment variable.
ENV_LIST - Lists all defined environment variables.
ENV_DEL - Deletes an environment variable.
GETENV$ - Gets the value of an environment variable.
The two key commands are SETENV and GETENV$. SETENV is used like
this:
SETENV "VARIABLE=value"
SETENV takes a string argument of the type "XXXXX=YYYYY" where
XXXXX and YYYYY are two strings separated by an equal sign. Any
space before the equal sign is treated as a part of XXXXX and any
space after the equal sign is treated as a part of YYYYY. The
case of each string is important as "VARIABLE" is different from
"variable". By convention, upper case is used for variables.
The SETENV is done either in a BOOT or setup program or by the
user. The command for an executable to get the contents of an
environment variable is GETENV$. The command is used like this:
a$ = GETENV$("VARIABLE")
In this case a$ will be assigned the value of "value". This
comes from our previous SETENV command above. If the Environment
Variable "VARIABLE" is not set (does not exist) then the GETENV$
would return a Null string ( "" ).
Now I did say that executables use GETENV$ and not SuperBasic
programs. Since variables are already used in SuperBasic, we
would not gain much in using environment variables. There the
commands are use is in compiled SuperBasic programs, which are
executables.
I see the purpose of using Environment Variables as adding to the
flexibility of programs that use Config Blocks. Both Config
Blocks and Environment Variables are really designed to change
default program settings. User Intervention and Command Line
Arguments are designed to tell the program what the data is.
Using environment variables allows the user the ability of making
a temporary change to the default options of a program, without
having to go through the trouble of using "config". Environment
variables would be used to change a setting for a single session
and that's it.
Getting Config Blocks and Environment Variables to work together
is not difficult. The program would first get it's default
settings from the Config Block. It would then check to see if
there are any Environment Variables set. If there are, then the
Environment Variable settings would override the Config Block
settings.
WORKING DIRECTORY
As we all know, QDOS did not come with the concept of directories
and subdirectories. A number of extensions to QDOS are available
to help create the working concepts of directories. When using
ED drives, I found the Path (PTH) extensions usefull because they
would search a list of subdirectories looking for a particular
exectuable.
Now, I've never used a QL with a hard disk, so I'm not too
experienced with using the tools for handling directories, plus
my copies of some of the good articles dealing with this subject
are not available to me. So, I've been doing some thinking on
the matter, especially since I have been writing a freeware
program and I want to make it as versatile as possible.
The one issue that I've been pondering over is telling a program
where there data file are at. This is called a Working
Directory. It is a directory where the data files are at and any
newly created files will also go.
I know that DATA_USE is designed for this purpose, but I do not
want to type in a new DATA_USE before each program I execute. I
could set up a short little SuperBasic program to set up
everything, but I want to EXEC a program not LRUN it. Of course,
using a front end like QASCADE, this point is mute, since the
user is removed from the details of executing the program. With
PTH_ setup, I don't need to set PROG_USE, because PTH_ finds the
executable. I was looking a way of having the executable know
where the Working Directory is.
What I came up with is this: Have an item in the Config Block
for Working Directory. The contents would be something like
this, "WIN1_PROG_DATA_". This string would be appended to the
front of each file name used in the program. This does mean that
the user would not be able to type in a new device name, such as
"FLP1_FILE_EXT". Initally, the Config Block entry for Working
Directory would be the null string (empty), so that a user would
enter "FLP1_FILE_EXT". Once the user has created a working
environment, the Config Block can be changed to what the user
wants.
If this feature is linked to Environment Variables, it would
become more powerfull. By setting an Environment Variable for
the program, the user could temporarily override the Config Block
and change the Working Directory. If the Config Block was
changed to a Working Directory and the user needed to use the
floppy for a few times, the user would set the Environment
Variable to "flp1_" or to the null string ("") and then set
DATA_USE to flp1_. If they did not want to change the default
Config Block, a BOOT program could set the Environment Variable
and it would be active during the whole computing session. If
different programs used different Environment Variable names for
their Working Directory, then all Working Directories could be
set up in the BOOT program. Something that could not be
accomplished with just DATA_USE.
Since my experience in this area is kind of light, the more
experienced QLers may want to take this all with a grain of salt.
I prefer to think of it as a way that I would prefer to organize
things and others may have a different way.
The reason I am bringing up this idea is for programmers to
consider the concept and add it to their programs. The option is
not very difficult to implement, but it would a nice feature to
the program. The whole feature would take up no more than 10-20
extra lines of code to a program. By setting the default value
of Working Directory to nothing, the feature is essentially
turned off and it does not impede the user that does not want use
a Working Directory.
CASE STATEMENT IMPLEMTATION
When I recently was working on SSB 2.6, I was using a SuperBasic
implemtation of a CASE structure for the core of the program.
The deeper the structure got, the harder it was to read and
understand. I starting thinking of using another way of
implementing a CASE structure.
For those that don't know what a CASE structure or statement is,
it is essentually the same structure as a SuperBasic SELECT ON
statement, but it is not limited to numbers. Plus the general
idea of a CASE structure is to have a number of possible logic
statements with an action for all cases that do not fit one of
the logic statements. In a generalized CASE structure not all of
the comparisons must be based on the same data (string, number,
etc), but can vary.
Traditionaly, a way of creating the CASE structure is to have a
number of nested IF..THEN..ELSE statements, with the final ELSE
handling the default value. An example would be something like
this: you are reading in text from a file. You want to handle
the following cases: a line starts with a ##, or a line starts
with a **, a line starts with a period (.). If none of these
cases, then the line is passed to the output file.
Using nested IF..THEN..ELSE statements the pseudo code would look
something like this:
IF line starts with ## THEN
......
ELSE
IF line starts with ** THEN
......
ELSE
IF line starts with a period THEN
.......
ELSE
pass to output file
END IF
END IF
END IF
If you start having more than just a hand full of possible cases,
the structure can get long and difficult to read.
SuperBasic allows the use of NEXT in conjunction with a REPEAT
statement. This means that when the NEXT is reached, the rest of
the REPEAT loop is skipped and the processing goes onto the next
interation of the REPEAT loop. Using NEXT in this manner allows
for the creation of a different implementation of a CASE
structure. Below is an example of the two implementatios listed
side-by-side.
REPeat loop REPeat loop
IF .... THEN IF .... THEN
.... ....
ELSE NEXT loop
IF .... THEN END IF
.... IF .... THEN
ELSE ....
IF .... THEN NEXT loop
.... END IF
ELSE IF .... THEN
default ....
END IF NEXT loop
END IF END IF
END IF default
END REPeat loop END REPeat loop
Using the REPEAT..NEXT..END version may not seem as clean as the
"classical" IF..THEN..ELSE structure, but I think it is cleaner
when it come to reading and debugging. What I am looking for
from readers is to ponder over any downsides from using the
REPEAT..NEXT structure. I can't think of any logical problems
with using this structure over the classical one. If you know of
any, please send me a note. I'll add the productive comments in
the next issue.
CREATING LOADABLE EXTENSIONS USING QLIB
One of the things that has always amazed me about the QL was the
ability to load a binary file and have a bunch of new keyboards
available in SuperBasic. In most computers that had Basic built
in, the language was static and had no way to extend itself.
Other languages (like C, Fortran, or Pascal) used libraries of
functions and procedures to extend the capability of the library.
The first major loadable extention to the QL was ToolKit. From
then on the term toolkit has been used in reference to loadable
extensions. Popular toolkits are ToolKitII (TKII), DIY ToolKit,
and DJToolKit.
I knew that the first of these toolkits were written in Assembly,
but I did not know that they could be created by Qliberator. It
seems that QDOS executables and extensions are real close in
format and when compiled right, they can be interchangable. This
means that an executable can also be loaded as an extension.
To figure out how to create a toolkit, I grabbed a simplefunction, Qliberator, and gave it a try. The function is
included below:
10 REMark $$external
100 DEFine FuNction upper$(up$)
110 LOCal x, temp
120 FOR x = 1 TO LEN(up$)
130 temp = CODE(up$(x))
140 IF temp > 96 AND temp < 123 THEN
up$(x)=CHR$(temp-32)
150 NEXT x
160 RETurn up$
170 END DEFine
The function takes any string and converts it to all upper case
letters. The $$external is a compiler directive to Qliberator
that tells it that the next function or procedure needs to be
available outside of the executable. For each procedure or
function that you want to turn into an extension, you would have
to put the $$external directive in front of it.
If I was to put an additional line in the program,
180 PRINT upper$("This is a test")
then when I executed the program, line 180 would be executed. If
I LRESPRed the program, the keyword upper$ would be made
available.
When compiling the program it is a good idea to turn WINDS off,
since the extension will have no channels open. Otherwise 3
channels will be opened for it, wasting them. To lower the size
of the binary file, turning off NAMES and LINES might be a good
idea. Note that the case of the function or procedure will be
maintained in the extension. In my example, the name that will
show up when entering EXTRAS is "upper$" (all lower case). If I
defined the function a UPPER$, then "UPPER$" would show up in the
EXTRAS command. By convention, extensions should be done in all
upper case.
If you will be running the extension on a system that already has
the Qlib runtimes loaded, then compile the program without
runtimes. If you don't know if the Qlib runtimes will be
available, compile it with the runtimes included. It is a good
idea to compile both ways and let the user decide which one they
need. The example program when compiled without the Qlib
runtimes was 594 bytes. With the Qlib runtimes it was 11,146
bytes. The runtimes take up a fair bit of space.
If you load an extension that does not have the Qlib runtimes on
a system where the Qlib runtimes are not loaded, you will not get
an error message when you LRESPR the extension. When you call
the extention is when the error will occur. The exact error
message is:
Error "Runtimes Missing !"
Once you have compiled an extension, all that is needed is to
LREPSR it and test it out. Remember that you can't LRESPR while
any jobs, other than Job 0 (SuperBasic), is running.
               (
geocities.com/siliconvalley/pines)                   (
geocities.com/siliconvalley)