home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
GEMini Atari
/
GEMini_Atari_CD-ROM_Walnut_Creek_December_1993.iso
/
zip
/
language
/
f68k.zoo
/
docf68k.txt
< prev
next >
Wrap
Text File
|
1992-02-01
|
142KB
|
4,623 lines
Far from being complete, but it is a beginning ...
/**************************************************************\
* *
* F68K *
* *
* *
* Technical Reference Manual *
* *
* *
* last change: 06-24-91 *
\**************************************************************/
Content
F68K copyright notes
Introduction
Forth-83?
Loader
Input and output
Mass storage
Pointer and addresses
Header
USER variables
Values and locals
Vocabularies
Interpreter
Control structures
Reducing the kernel
F68K streaminterface
Lineeditor
Fullscreen editor (not longer provided)
F-EDIT
Possible errors
Documenation of loader implementations
Atari ST
Sinclair QL
Commodore Amiga
Glossary
###############################################################
F68K COPYRIGHT NOTES
###############################################################
F68K was created to support distribution of the FORTH
programmimg language. So there are only shareware-fees on F68K
for PRIVATE use. You are free to make, use and give away copies
of F68K, as long as the author's names are left visible, and
this file comes to the new user, too.
****************************************************************
Important!!!
****************************************************************
F68K will never be ready. It shall develop continously. So there
is no version number of the whole system, but only of the
kernel. Your version should be 1.0.
F68K is NOT thought to be a system which is ready to use on all
levels and all computers. It shall be as large and universal as
it can be at a time, but not more. In order to make it larger
and more universal, F68K needs YOUR help. So, if you have ported
F68K to a new hardware or have written a new tool or an
application, which can be of interest to other users, please
send it to me. I will find an appropriate place of your part in
the system and add your name to the F68K-programmers list you
find below.
****************************************************************
The F68K-kernel is COPYRIGHTED. While using F68K you accept my
ownership.
Copying Fees
If you normally charge a fee for making someone a copy of a
disk, you may charge the same fee for making a copy of F68K. The
fee must be based solely on the cost of making the copy, and
must be the same fee that you charge for a copy of any other
disk.
Licensing
If you develop soft- or hardware that uses the F68K-kernel and
wish to SELL your product along with F68K, please get in touch
with me so we can work out a licensing agreement. The
F68K-kernel may not be sold as part of any system without a
licensing agreement. I'll be willing to work out an agreement
that is fair to both of us.
If you do not want to sell anything together with F68K, but
nevertheless like it, please contact me or the 'Forth
Gesellschaft e.V.' in order to come to know the latest news on
F68K. Send me some (at least two) ST or DOS formatted disks and
a paid envelop with your address on it, I will register you and
send you the latest version of F68K at all times. If the disks
are not formatted or the envelop is missing, I will just keep
the rest.
Please understand that I cannot give away the entire kernel
listing with this free version of F68K. If you are REALLY
interested in that listing, please contact me and we will find
an appropriate agreement.
Documentation
As said above, F68K will never become ready. So documentation
will never be ready, too. You will always find some essential
documentation on the disks in the '.TXT' files and within the
sources, of course. If you would like to have the latest version
of documentation, see 'Licensing'.
Liability
I shall have no liability or responsibility to any person or
entity with respect to any loss or damage caused or alleged to
be caused directly or indirectly by F68K. This includes, but is
not limited to, any interruption of service, loss of business,
loss of information or that which is rendered inaccurate, or
loss of anticipated profits or ANY OTHER CONSEQUENTIAL OR
INCIDENTAL DAMAGES RESULTING FROM THE USE OF F68K, even if I
have been advised of the possibility of such damages.
Acknowlegdement
I am grateful to J. Staben for many hours of discussion on FORTH
specific problems. Thanks to the members of the local group
RHEIN/RUHR of the 'Forthgesellschaft e.V.' for fruitful
discussion of technical details.
My address:
Dipl. Phys. J. Plewe
Grossenbaumer Str. 27
D-4330 Muelheim/Ruhr
Germany
Tel.: 0208/423514
You also can get copies from:
FORTH-Gesellschaft e.V.
Postfach 1110
D-8044 Unterschleissheim
Germany
or from anyone, who is willing to make you a copy
---------------------------------------------------------------
These people worked at F68K (Hall of Fame)
J. Plewe F68K kernel, kernel extensions
Laxen & Perry M68000 Assembler
Hartwig Luedemann L&P assembler on 32 bit
Dirk Kutscher Sinclair QL loader
Markus Redeker Lots of bugs and tools, F-EDIT
Reinhard Scharnagl Disassembler
Wolfgang Schemmert Amiga loader
Thomas Beierlein OS9 loader
---------------------------------------------------------------
Hope you enjoy the system,
keep going forth, your's
J. Plewe
P.S. If you miss a message like 'If you like ... please send $xx
to ...' here is one for you:
You may support me and F68K by sending any amount of money!!
###############################################################
Introduction
###############################################################
FORTH is a widespread computer language that has been
implemented on nearly all important machines. If you have got a
PC, than you will be able to choose your system out of a set of
highly developed FORTH implementations. If you have one of the
less often sold computers, such as Mac, ST or Amiga this is true
to some degree, too.
Now FORTH is a language, who's applications typically do NOT run
on such machines, but on single-chip or home-build computers,
for example.
This is because FORTH does not care for operating systems or
complicated I/O. It may be easily be run on a naked system. In
those cases, everyone has to develop his own FORTH environment.
F68K wants to close this gap between the commonly used and the
'special' systems.
F68K was designed to run on ALL MC68000 computers without
changing and recompiling or reassembling the kernel. For that
reason, the kernel may not depend on the I/O or the address
configuration of the target system. In order to achieve that,
without trying miracles, there will have to be a loader for each
machine or operating system. This loader 'knows' all the machine
dependent stuff.
Porting F68K to a new computer means to create a suitable
loader. Because all loaders are similar, this is an easy task.
An experienced programmer might do that before breakfeast.
So, if you have just build a new MC68000 system, you will
immediately be able to use the tremendous power of a unique
FORTH programming environment.
###############################################################
Forth-83?
###############################################################
F68K follows the FORTH-83-standard in most cases, but it cannot
be a real FORTH-83 system at all, because it uses a 32 bit
stack. In some details it leaves FORTH-83 and turns towards the
new ANSI standard, which is not completely defined yet. So the
words COMPILE and [COMPILE] no longer exist. They have been
replaced be the more smart POSTPONE.
The old words are easily redefined:
: COMPILE POSTPONE POSTPONE ; IMMEDIATE
: [COMPILE] POSTPONE POSTPONE ; IMMEDIATE
There are slight deviations concerning control structures. See
the corresponding chapter "Control Structures" for details.
The most important deviation is the expansion of blocksize to
2kB. This gives the user a full 80x25 screen for source editing
(see "Mass storage") and allows the creation of an internal
forth filesystem, called FIFI. This is described in the chapter
"F68K streaminterface".
It is intended that F68K shall develop towards ANSI, although
the dpANS standard defines a block to be of a size of 1024
bytes. But for F68K blocks are of 2000 bytes, almost any ANSI
compatible source will compile and run correctly, even if some
bytes (2000- 1024) perhaps will be wasted.
###############################################################
Loader
###############################################################
The F68K system consists of an F68K system image and a loader,
which has to load the system image into memory and to provide it
with all necessary I/O- and memory information.
So it becomes possible to design the system image to be
perfectly independent of any hardware and/or operating system.
Porting F68K to a new system or even a completely new hardware
means to create the appropriate loader and the system will run.
Of course, the loader has to fit the system, so one has to
follow some rules when creating the loader.
What the loader does
The task of the loader is quite simple: it has to install the
code and the data part of the system image into memory, invent
some good addresses for the stacks and the TIB (Terminal Input
Buffer) and to store all these addresses in a given structure
together with the (absolute) addresses of the I/O functions and
to push the address of this structure on the returnstack,
indexed by the address register A7. It then simply has to jump
to the start of the code segment. That's all!
Loading the system image
As said above, the system image consists of two parts: a code
and a data segment. Both must be loaded into memory before the
system is ready to run. This action depends on the way, the
system image is stored on the mass storage device or ROM. In all
cases, it must be known, how these parts can be placed in
memory. For example, in the original distribution, the image was
created with an assembler under the operating system GEMDOS on
Atari ST. So, there is an image file that consists of the code
and data parts and a file header. In that header, which is 28
byte large, one will find with an offset of two the length of
the code segment and with an offset of six the size of the data
segment:
struct header
{
int magic;
unsigned long codesize;
unsigned long datasize;
int dont_care[9];
} header;
In the original distribution, the integer 'magic' holds the two
characters 'JP', which are the initials of my name. So the
loader has the possibility to check wether the given imagefile
contains a valid F68K image.
One will have to load the header, fetch the named values and
then load the two parts into appropriate buffers. If the system
comes in an other form, these informations must be given, too.
The buffers, of course, have to be larger than the sizes of the
segments. In most cases, a buffer of 64 kbyte for code is
enough. For the data segment, no limit can be predestined.
I/O functions
There are five functions, that must be given by the loader:
getting a character from the terminal, KEY
getting the terminal's status, KEY?
putting a character to the terminal, EMIT
reading an writing blocks from/to the
mass storage device, R/W
------------------------------------------------
reading data from a system dependent device
writing F68K data to a system dependent device
There is a defined data interface of these functions to F68K.
They all find theire parameters on a stack indexed by the
address register A7, which is the standard F68K returnstack.
They all leave outcoming parameters in the dataregister D0. Note
that this data interface is the same used by common C-compilers,
where parameters are passed on the returnstack (A7). This makes
it very simple to write a loader in C. When using assembler
language, it is important to know, that the parameters lie on
the stack in reverse order to those on the F68K-stack! On the
top of returnstack there always is the returnaddress. There is a
simple translation from F68K-parameters to stack positions:
Forth: ( para1 para2 para3 -- ret )
Stack (a7): 4(a7) 8(a7) $c(a7) d0
So this interface is easy-to-use even on assembler level.
The loader can define more then one routine for all six
functions. E.g. it can submit an EMIT for a terminal, a printer,
a serial port and a file. There may be functions for a KEY which
comes from the terminal or the serial line (not from the
printer, of cource). There has to be a table for each of the six
functions which has to be constructed as follows:
byte 0-3: number of routines
byte 4-7: first routine
byte 8-11: second routine
. .
. .
. .
Example:
keytable: DC.L 2
DC.L TERMKEY
DC.L SERIALKEY
The addresses of theses tables are later passed to F68K. They
can be accessed on FORTH-level using the words KEYS, KEY?S,
EMITS, R/WS, READSYSES and WRITESYSES. They give the address of
the 0. byte of the appropriate table. The first entry of each
table will be taken as the default I/O. So there is an easy way
to support all I/O devices available and to redirect I/O on
FORTH-level:
: >PRINTER ( -- ) KEYS 2 CELLS + @ ^KEY ! ;
Here is a description of the I/O functions and theire parameters
in detail. The user has not to care about registers and the F68K
stack. F68K saves all registers before calling the loader's
functions and sets the datastack to the right value after
return.
a.) KEY
KEY does not expect any parameter. It just waits for the
terminal to send a character. This character is embedded as the
least significant byte of long word (4 byte) and then pushed
onto the stack.
example:
KEY: JSR get_key ;the character comes in D0
RTS
or
long key()
{
return (long)getch();
}
b.) KEY?
KEY? does not expect any parameter. It checks the terminal,
wether there is a character available for KEY. In that case, it
returns a TRUE flag (something <>0), otherwise a FALSE flag in
register D0.
example:
KEY_QUEST: JSR termstate ;status comes in D0
RTS
or
long key_quest()
{
return (long)kbhit();
}
c.) EMIT
EMIT takes a character from the stack and prints it on the
terminal. Some terminal emulation can be done here. After EMIT,
there is no parameter on the stack.
example:
EMIT: MOVE.L 4(A7),D0 ;SP: return char
JSR char_out ;print the character
RTS
or
void emit(c)
long c;
{
putch((char)c);
}
d.) R/W
R/W is the most complex function of these four. It has three
parameters on the stack. First, there is a flag, wether reading
(flag = 0) or writing shall be performed, then there is the
blocknumber and the last parameter is the (absolute) address of
a buffer. When the flag is zero, then the block with the given
number has to be loaded into the buffer at the given address. If
the flag is something unequal to zero, then the data in the
buffer at the given address has to be written into the given
block. A block is an amount of 2048 byte = 2 kB on the mass
storage device. There are always 2048 bytes read or written.
Where they come from or where they go to only concerns to the
R/W function. F68K does not care! Because errors may occur
during load or save, R/W leaves a flag in register D0, which is
nonzero in case of success.
example:
*Forth parameters ( buffer block r/w? -- flag )
*stack position 4(a7) 8(a7) $c(a7) d0
* 0(a7) is returnaddress
R_W: TST.L $C(A7) ;read or write?
BEQ read_block
write_block: MOVE.L 8(A7),D0 ;get block number
ASL.L #2,D0 ;512 byte/sectors
MOVE.L #4,D1 ;number of sectors
MOVE.L 4(A7),A0 ;the buffer
JSR write_sectors ;flag in D0
RTS
read_block: MOVE.L 8(A7),D0 ;get block number
ASL.L #2,D0 ;512 bytes/sector
MOVE.L #4,D1 ;sectors to be read
MOVE.L 4(A7),A0 ;the buffer
JSR read_sectors ;flag in D0
RTS
or
long r_w(buffer, block, rwflag)
long buffer, block, rwflag;
{
if(rwflag==0)
return diskread(buffer, block);
/*not shown here*/
else
return diskwrite(buffer, block);
/*not shown here*/
}
Note that blocks must not be physical blocks on a floppy or
harddisk. They can also be emulatet, for example, within
streamfiles provided by an operating system.
e.) READSYS
READSYS allows the user to read F68K data from the system, which
loaded F68K. In general, this will be a file in an operating
system. This path is installed in order to be able to design a
unique and system independent import function e.g. for sources
in F68K. READSYS gets a buffer's address and the length of the
buffer as arguments. If succesful, it returns -1=TRUE, otherwise
0=FALSE.
example:
*Forth parameters: ( addr count -- flag)
*stack positions: 4(a7) 8(a7) d0
READSYS: TST.L FIRST
BNE NOOPEN
MOVE.L FILENAME,A0
BSR OPENFILE
MOVE.L D0,INHANDLE
NOOPEN: MOVE.L 4(A7),A0 ;BUFFER
MOVE.L 8(A7),D0 ;COUNT
JSR FREAD
MOVE.L 8(A7),D1 ;COUNT again
CMP.L D1,D0 ;error?
BEQ NOERROR
CLR.L D0 ;FALSE-flag
BRA RSYSEND
NOERROR: MOVE.L #-1,D0 ;TRUE-flag
RSYSEND: RTS
or
long readsys(buffer, count)
{
static int first;
static int inhandle;
if(first) fopen(&inhandle,"INFILE"); /*or so*/
return (fread(inhandle,buffer,count) == count);
}
f.) WRITESYS
WRITESYS allows the user to write F68K data to the system, which
loaded F68K. In general, this will be a file in an operating
system. This path is installed in order to be able to design a
unique and system independent SAVESYSTEM function in F68K. But
WRITESYS can also be (mis)used for other purposes, e.g. output
protocols or export of sources. WRITESYS gets a buffer's address
and the length of the buffer as arguments. If succesful, it
returns -1=TRUE, otherwise 0=FALSE.
example:
*Forth parameters: ( addr count -- flag)
*stack positions: 4(a7) 8(a7) d0
WRITESYS: TST.L FIRST
BNE NOCREATE
MOVE.L FILENAME,A0
BSR CREATEFILE
MOVE.L D0,OUTHANDLE
NOCREATE: MOVE.L 4(A7),A0 ;BUFFER
MOVE.L 8(A7),D0 ;COUNT
JSR FWRITE
MOVE.L 8(A7),D1 ;COUNT again
CMP.L D1,D0 ;error?
BEQ NOERROR
CLR.L D0 ;FALSE-flag
BRA WRSYSEND
NOERROR: MOVE.L #-1,D0 ;TRUE-flag
WRSYSEND: RTS
or
long writesys(buffer, count)
{
static int first;
static int outhandle;
if(first)
fopen(&outhandle,"OUTFILE"); /*or so*/
return (fwrite(outhandle,buffer,count) == count);
}
Using different devices with R/W
In some cases there may be more than one blockoriented device.
If so, R/W has to multiplex the different devices. For that
reason, a set of block ranges for each device has to be
declared. For example, there are a floppy drive and a harddisk
and the floppy gives room to 320 blocks. Then the following
table, called ROOTTABLE has to be allocated in the loader:
DC.L 2 ;there are two devices
DC.L 0 ;floppy uses blocks 0-319
DC.L 320 ;capacity is 320 blocks
DC.L 320 ;block >320 are on harddisk
DC.L 1000 ;capacity of harddisk is 1000 blocks
In dependency of the blocknumber, the function used by R/W now
has to switch between different access mechanisms, for example
R_W: IF blocknumber < 320
THEN access floppy
ELSE access harddisk
The word ROOTTABLE in F68K is a constant whih gives the pointer
to roottable. There is a USER-Variable called ROOTBLK, which is
added to the number given to BLOCK, before BLOCK uses R/W. So
the user may define simple words, which switch between he
devices.
For example:
: D0: ROOTTABLE 4 + @ ROOTBLK ! ;
: D1: ROOTTABLE 12 + @ ROOTBLK ! ;
When F68K is initialised, the first entry in the roottable is
written to the variable ROOTBLK, so this is an easy way to take
one device as default.
Buffers in memory
F68K needs at least five addresses of buffers in memory which
have to be allocated by the loader. First there are the two
buffers for code and data segments. Second, there is need for at
least two buffers for the data- and the returnstack. Last, a
buffer for the TIB must be available. The allocation of these
buffers may be done according the taste of the programmer or the
needs of the application.
Here are some hints:
The TIB must not be smaller than 256 bytes. It may be larger but
there is no need for a larger buffer in standard cases.
The stacks grow towards lower addresses, whereas TIB grows to
higher addresses. So the same pointer may be used as a base for
one of the stacks (usually the datastack) and the TIB. When the
F68K dictionary is expanded, it gros towards higher addresses in
code and data segment. It it a good advice to place the stacks
at the end of availible memory and the code and data segments at
the beginning.
Here is a possible memory map as an example:
lowest address (la): start of code segment
.
.
.
la + 64kb: return stack base
start of data segment
.
.
.
.
.
ha - 256 byte: start of TIB, data stack base
.
highest address (ha): end of TIB
As an second example, one could make the TIB buffer larger an
place the returnstack at the end of TIB, so it grows towards
start of TIB.
Note that F68K does NO error checking concerning stack or
dictionary overflows!
Starting F68K
When the I/O functions are written, the memory is allocated and
the system image is loaded, F68K can be started. The necessary
parameters, which are all known now, have to be stored into a
structure shown below which address' has to be pushed on the
returnstack. Then the jump into the system can be performed.
forthparas:
registers: DS.L 16 ;d0,d1,d2,d3.......,a5,a6,a7
data: DS.L 1
code: DS.L 1
datstk: DS.L 1
retstk: DS.L 1
TIBptr: DS.L 1
codelen: DS.L 1
datalen: DS.L 1
emittable: DS.L 1
keytable: DS.L 1
keyqtable: DS.L 1
r_wtable: DS.L 1
readsystable: DS.L 1
writesystable: DS.L 1
roottable: DS.L 1
;extensions like files, grafics, windowing can be placed here
or
struct forthparas
{
long registers[16]; /* to be filled by F68K */
void *data;
void *code;
void *datstk;
void *retstk;
void *TIBptr;
long codelen;
long datalen;
void *emittable;
void *keytable;
void *keyqtable;
void *r_wtable;
void *readsystable;
void *writesystable;
void *roottable;
/* possible extensions like files, grafics, windowing */
} forthparas;
Example:
MOVE.L databot,data ;start of data segment
MOVE.L codebot,code ;start of code segment
MOVE.L tib,datstk
MOVE.L retstack,retstk
MOVE.L tib,TIBptr ;same pointer for stack an TIB
MOVE.L #$20000,datalen ;data segment's size is 128 kb
MOVE.L #$10000,codelen ;code segment's size is 64 kb
MOVE.L #EMITTABLE,emit
MOVE.L #KEYTABLE,keytable
MOVE.L #KEY_QUESTTABLE,keyqtable
MOVE.L #R_WTABLE,r_wtable
MOVE.L #READSYSTABLE,readsystable
MOVE.L #WRITESYSTABLE,writesystable
MOVE.L #ROOTTABLE,roottable
MOVE.L forthparas,-(A7)
JSR codebot ;jump into the system, MUST be JSR
BRA exit ;when come back, then exit loader
(retstack, tib, databot and codebot shall be variables with the
pointers to returnstack, the TIB, the start of data and code
segments, respectivily)
or
set_forthparas();
f68k(forthparas);
Hopefully, F68K will prompt now with it's 'ok'!
The forthparas are visible from F68K simply using FORTHPARAS.
This gives access to the functiontables, that may be added at
the end of the structure. So there is a very flexible
possibility to extend the F68K loaderinterface e.g. for special
abilities of operating systems. There is no standardisation yet,
but I would suggest to reserve the first free place for a
pointer to a table of file functions, the next for grafics and
the third for windowing. The first implementation of one of
these, which reaches me, could set the standard, e.g. the F68K
standard GEM interface.
There are some very important things to say about the
'registers' area in the forthparas. The reader should have
noticed, that this area is not influenced by the loader. Indeed,
this area will be filled by F68K itself during initialisation.
All regsiters of the loaders runtime environment will be stored
here in the following order:
D0, D1, .... D7, A0, A1, .... A7
The value for A7 in this area (registers + 15*4) will be of the
location pointed to before F68K has been called. That means that
the top of this A7-stack will point to the forthparas. This
gives the possibility for the I/O-functions to restore theire
runtime environment completely, including the returnstack. This
can make sense, when the I/O-functions need some registers,
which the loader has saved on the loaders returnstack for them.
They can be accessed by the pointer held within 'register'. The
I/O-functions must take care that the original A7 (that is used
by F68K) will be restored before returning to F68K.
!!! Important !!!
Some critical operating systems need a specific set of register
contents to execute the loaders I/O-functions. F68K saves these
registers in the FORTHPARAS-structure as described above. In
order to make this area accessible to the loader functions, F68K
places the absolute address of the FORTHPARAS in bottom of the
(return-)stack, below the parameters. I/O-functions in those
critical environments therefore have the possibility to restore
vital registers and then execute the appropriate I/O-function.
In uncritical environments, one not has to take care about this
value on the stack, because it will be removed automatically
when returnig control to F68K.
###############################################################
Input and output
###############################################################
All of the F68K in- and output functions are vectorized. The
addresses of the I/O functions are held in the USER-variables
(KEY), (KEY?), (EMIT), (R/W), (READSYS) and (WRITESYS),
respectivily. The appropriate words KEY, KEY?, EMIT, R/W,
READSYS and WRITESYS always use these vectors. These Variables
are preset with the addresses of the F68K-words LOADERKEY,
LOADERKEY?, LOADEREMIT, LOADERR/W, LOADERREADSYS and
LOADERWRITESYS.
These words again only use vectors for execution. These are
^KEY, ^KEY?, ^EMIT, ^R/W, ^READSYS and ^WRITESYS. If the F68K
startup routine held in the USER-variable (COLD) does not change
one of these variables, then they will be filled with the first
of the addresses provided in the loader's I/O function tables
(see 'Loader') at system's entry. The user may store other
addresses fetched from these tables or completely new I/O
functions in these variables. When doing this, he should know
about some dependencies.
It is important that the addresse held in ^KEY, ^KEY?, ^EMIT,
^R/W, ^READSYS and ^WRITESYS point to functions provided by the
loader. The must not be executed in F68K simply using EXECUTE.
The system may crash. These functions may only be called using
LOADERKEY, LOADERKEY?, LOADEREMIT, LOADERR/W, LOADERREADSYS and
LOADERWRITESYS because some interfacing concerning the
parameters has to be done. On the other hand no simple F68K-word
may be stored in these vectors or the same reason.
The words EXPECT or TYPE use the lower level functions KEY and
EMIT. So if KEY is changed, all words using KEY, like EXPECT,
will be changed, too. So KEY influences even the input editor.
If the user wants to avoid this, he will be able to change
EXPECT and TYPE, too. They are vectorized with the
USER-variables (EXPECT) and (TYPE).
###############################################################
Mass storage
###############################################################
F68K supports mass storage devices like floppies and/or
harddisks. For F68K has to be a portable system, the mass
storage support must be portable, too. This is only possible
when NOT using existing file systems or other access mechanisms
on F68K level.
F68K accesses the mass storage device in portions of 2 kb = 2048
bytes. This quantity is called a BLOCK. Each block has a unique
number. The range of this number does not depend on F68K, but on
the implementation of the read/write primitive in the loader
(see 'Loader').
When refering a specific block by it's number, e.g.
27 BLOCK ( blocknumber -- address )
F68K finds an appropriate buffer using the word BUFFER and then
passes the buffer's address and the number of the block to the
word R/W, which then calls the read/write function in the
loader. R/W can be seen as the end of the mass storage interface
on the side of the device, whereas BLOCK is the end on the side
of F68K. BUFFER than is something like a tunnel between the
ends.
According to the stack comment for BLOCK, it expects a block
number and returns a address. This address points to the buffer,
where the data of the block was read in. Writing to a block
means writing into that buffer. If the buffer is marked as
changed, using the word UPDATE, it will be written back to disk
again when the buffer is used for another block or the user
executes FLUSH, which saves all UPDATE'ted buffers to disk. But
in the normal case, the user does not have to take care wether
is block is read from or written to disk. The user may access
mass storage in the same way as he does with memory. So, the
F68K blocks are a kind of virtual memory.
Although the physical size of a block on a disk is 2 kb (often
four sectors), the size of a buffer is only 2000 bytes. This is
because the first 48 bytes of each block are used for internal
management of blocks. The user may access these 48 bytes by
using negativ offsets on the address returned by BLOCK, but he
should be very carefull. By the way, the reserved bytes will be
used, for example, to realize block organisation on a disk, so
that streams of blocks may be refered by name, in a way it is
done with files on 'normal' operating systems.
So 2000 bytes of data may be used freely.
Handling Buffers
In a virtual memory system, buffers have to be handled. The
buffers are the connection between virtual and real memory.
Buffer handling in F68K is performed by the word BUFFER:
BUFFER ( blocknumber -- address )
Every F68K system must have at least one buffer, but any other
number is possible, too. More buffers cause less disk activity.
A F68K buffer has the following structure:
14 bytes header
2048 bytes block data
in detail:
$0 pointer to next buffer
$4 physical number of block in the data buffer
$8 logical number of block in the data buffer
$C UPDATE flag
$E data buffer, 2048 bytes
All buffers used by F68K are linked together in a cyclic linked
list. The first four bytes of a buffer are used for the
necessary pointer. There is a USER-variable called PREV, which
contains a pointer to the buffer most recently refered.
The second four bytes contain the number of the block which has
been refered. This is the number which is passed to R/W when
reading or writing the block. In contrast, the number in the
third entry in the buffer's header is a logical blocknumber. It
is only used when using a 'higher' disk organisation, where the
disk is divided into several streams of blocks. Then this number
may be the logical number of a block within such a stream. Last
there is a word (2 bytes) which is used as a flag, wether the
block has been altered after loading from disk.
When the user executes BLOCK or BUFFER, then a new buffer has to
be allocated. The mechanism to do that is quite simple.
Bufferallocation is always done with BUFFER. BLOCK calls BUFFER
itself. BUFFER expects a blocknumber on the stack. Now, first it
checks, wether the refered block is already loaded in one of the
existing buffers. The word ?CORE does this check. For all
buffers are linked in a cyclic list, ?CORE starts with the
buffer the USER-variable PREV points to, and compares the number
on the stack with the second value in the buffers header. On
equality, ?CORE stops returning the buffer's address, otherwise
it fetches the link to the next buffer until it reaches the
buffer it started with. If no equality has occured, it returnes
a FALSE- flag. If ?CORE does not result in FALSE, BUFFER simply
returns the buffer ?CORE found. If ?CORE could not find a buffer
containing the data of the desired block, BUFFER will use the
buffer following the one, which PREV points to. But before
returning the address of that buffer, it first checks the
UPDATE-flag in the header. If it is set, the buffer is written
back to disk to the block that is indicated by the physical
blocknumber in the header. It then clears the UPDATE-flag and
writes the address of that buffer to the USER-variable PREV. Now
the buffer is allocated and BLOCK may load it's data into it
using R/W.
The sense of this mechanism becomes clearer when observing the
chronological order in which buffers are used. When taking the
next buffer in the cyclic linked list, it is clear, that this
one is the 'oldest' among all buffers. It is a natural aim to
use the buffer, which is the at least recently used. This is
perfectly done by this mechanism.
Notes
All which is said above is valid when using the kernel directly.
BLOCK and BUFFER are DEFERred words, which may be redirected
e.g. by the blockstream system. The description above is meant
for the primitives (BLOCK and (BUFFER, which are originally used
by BLOCK and BUFFER.
There is another technical detail to be mentioned. To simplify
access, BLOCK first compares the given blocknumber, which it
converts into a physical blocknumber by adding ROOTBLK, with the
content of the global variable LASTBLK. If they are identical,
BLOCK returns with the content of LASTBUF. Otherwise the way
described above is gone and LASTBLK and LASTBUF are updated
accordingly.
###############################################################
Pointer and addresses
###############################################################
This chapter is very important, because F68K distinguishes
between two types of pointers or addresses. In general, all
addresses are relativ! There is no occasion in F68K, where
absolut addresses are used.
In most cases, the user has not to care for address handling. He
may use the '@' and '!' operators as usual.
There are some very special situations, where the user has to be
careful. These situation occur, when addresses of executable
code, e.g. addresses within the code segment, occur.
Then the user has to remember, that F68K consists of two
segments: the code- and the datasegment.
'Normal' addresses, which may be operands to '@' and '!' are
always offsets into the datasegment. That is clear, because the
user normally wants to access data, not code.
So, if the user defines a variable,
VARIABLE X
then a header and space for the data is allocated within the
datasegment, whereas the runtimecode of VARIABLE is located in
the codesegment. X gives the offset of the data within the
datasegment on the stack. So '@' or '!' work with these offsets.
Now consider the following problem:
The user wants to compile some words 'by hand', so he has to get
the appropriate codeaddress and to compile it.
Here is a somewhat unusual version of ': NIP SWAP DROP ;'
: NIP ( n1 n2 -- n2 ) [ ' SWAP JSR, ' DROP JSR, ] ;
This version compiles a different code than the natural one.
This comes from the fact, that F68K internally does not use
'JSR,' for codegeneration. As indicated by the name, it only
generates a JSR-code and does not care for relativ branches or
macro expansion ('JSR,' generates an additional MOVE-operation,
which is of no interest in this context. 'JSR,' generates code
of a defined length, which can be useful sometimes).
In fact, F68K uses the word 'COM,' for codegeneration. The word
' (tick) gets the address of the following word in form of an
offset into the CODEsegment. So no '@' or '!' is possible with
that address!! 'JSR,' knows this fact and it is only natural,
that words, which generate code, use addresses out of the
codesegment.
But for 'COM,' there is a difficulty: it should know wether the
word to be compiled has to be expanded as a macro. The
corresponding information is located in the control word in the
header of that word. But the header is in the DATAsegment! So
'COM,' needs the address of the word's header. This address is
found using the word H' (header tick):
: NIP ( n1 n2 -- n2 ) [ H' SWAP COM, H' DROP COM, ] ;
In order to make the relation between ' and H' clearer, one may
express ' by H':
: ' ( -- addr ) H' @ ;
Addressconversion between segments
There are some other occasions, where the user wants to access
data within the codesegment. E.g. he wants to access the value
of a variable without using the variable's name (for what
reason?). Then he has to know something about a variables
structure, which is the same for all CREATE structures. In the
codesegment, there is the runtime-code of a variable. It is the
code generated by 'JSR,' (see 'Generating Code'), that means,
that there are 8 bytes of code for each variable. Behind these 8
bytes there is a pointer to the data. This pointer is
datasegment-relative, of course. But the pointer itself is in
the codesegment, so it has to be accessed there. For that
reason, one has to define a word, which transforms a
codesegment-relative into a datasegment- relative address:
: CODE>DATA ( Caddr -- Daddr ) SYSBOT + $8000 + ;
SYSBOT is a constant that gives the address (datasegment-
relative) of the beginning of the codesegment. The $8000 has to
be added because the codeaddresses are relative to the middle of
the first 64k-codesegment (see 'Generating Code' again). Now the
variables data can be accessed:
VARIABLE X
' X ( pointer to runtimecode of a CREATE-word )
8 + ( pointer now behind runtimecode )
CODE>DATA ( make it relative to datasegment ... )
@ ( so @ can get the pointer to the data )
@ ( now gets the value )
By the way, 'X @' does the same and is a bit less complicated.
Converting from/to absolute addresses
In some cases it could be necessary to obtain the absolut
address from a relativ. This could be useful in writing
CODE-definitions. This conversion is done with the word
>ABS ( reladdr --- absaddr )
So '0 >ABS' gives the absolute start of the datasegment. To
convert codesegment relativ addresses, there is the word
>ABSCODE. '0 >ABSCODE' gives the absolute location of the
codesegment.
When using machinecode in CODE-definitions, two addressing modes
are defined to simplify access to F68K data and code. These are
DATA) and CODE), which have to be pronounced 'datasegment
indirect' and 'codesegment indirect', respectivily. Theire usage
is simple:
Variable X
CODE X@
' X # d0 .l move \ get pointer to X in codeseg
8 # d0 .l addq \ put it behind code
d0 code) d0 .l move \ get pointer to data
d0 data) sp -) .l move \ get data
next end-code
This is the same way to access a variables value as it was used
in the example above in high-level. It is important, that the
register holding the F68K-address is a dataregister d0-d7!
Last, there should be the possibility to access absolute
addresses, that means to convert an absolute address into an
relative F68K-address. This is performed with the word
(ABS) ( absaddr -- reladdr )
So '0 (ABS) @' reads the absolute address 0 (and gives the reset
SSP). (ABS) and >ABS are reversing each other.
Corresponding to (ABS) there is (ABSCODE) which converts an
absolute into a codesegment relative address.
!!!! Important Rule: If a location in the datasegment is
referenced, the pointer must always be relative to the
datasegment. If a location in the codesegment is referenced, the
pointer must always be relative to the codesegment. NEVER
compile a reference to codesegment as an pointer relative to
datasegment! If a conversion (CODE>DATA) has to be made, it
ALWAYS has to be made a runtime!
Example: accessing the VIEW-information in the codesegment
: TEST
... ['] A_WORD CODE>DATA 4- @ ... ;
is correct, but
... [ ' A_WORD CODE>DATA 4- ] LITERAL @ ... ;
is WRONG.
If you wonder why, remember that code- and datasegment may be
placed anywhere in memory. So it is not possible to fix a
location in the codesegment with an address relativ to the
datasegment. If the segments will be placed somewhere else the
next time, the intersegment-relative pointer will probabely have
wrong values!! Think of it!
'>R', 'R>' and 'R@'
Additionally, something very important has to be said about the
words '>R', 'R>' and 'R@'. In traditional implementations, these
words simply push, pop or fetch a word from/to the datastack
to/from the returnstack. In F68K, this words do some conversion
with the data while moving between the stacks. When pushing
something to the returnstack, it is always treated like an
address and it is converted into a absolute machineaddress. This
has to be done because this should be possible:
: FOO ['] FOO1 >R ;
The codeaddress of 'FOO1' is pushed onto the returnstack, so
that it will be executed when reaching the 'RTS' instruction
compiled by ';'. For the M68000 does not know anything about
F68K pointer- treatment, the address on top of the returnstack
has to be a valid machineaddress. So the conversion from a F68K
codesegment relative address (permitted by '[']') into an
absolute address is performed by '>R'. So '>R' implicitely uses
'>ABSCODE'. This has to be reconverted, if the data returns from
the returnstack. So 'R>' and 'R@' implicitely use '(ABSCODE)'.
For this reason it is not allowed to push something onto the
returnstack using '>R' and later get an explicite access using a
sequence like
RP@ @
because the reconversion is omitted in this case. A legal
sequence to do things like that is
RP@ @ (ABSCODE) \ do the right conversion
doing the conversion 'by hand'.
This highly compatible use of of '>R', 'R>' and 'R@' has to be
paid with a little more effort accessing inline-data in the
codesegment. Inline-values normally are accessed using the top
of returnstack as a pointer to the value. For now this pointer
is an absolute machineaddress, it has to be converted before
access using '@' can be performed. There are two possibilities
to do that. They are very hard to explain, so I let an example
do the explanation:
: GETINLINE ( -- N )
R@ >ABSCODE (ABS) \ convert address
\ RP@ @ (ABS) \ ... this is more simple
@ \ access inline-data
\ R> 4+ >R ; \ increase returnaddress
4 RP@ +! ; \ ... or this way
: TEST ( -- 27 )
GETINLINE [ 27 CODE, ] ;
Note that 'CODE,' has to be taken to create the inline-data
instead of ','.
###############################################################
Header
###############################################################
Each word defined in F68K is preceeded by a header. This header
contains information about the name of the word, the location of
the code and some flags. Additionally, all headers of one
vocabulary (see 'Vocabularies') are linked together as a long
chain of words. Each header holds a pointer to the header last
defined before (in the same vocabulary). The structure of a
header is very simple:
-----------
| Control | \ 16 bit
=====================
| Ptr to code | 'CFA' \ 32 bit
=====================
| Ptr to last header| 'LFA' \ 32 bit
=====================---------------------
| Cnt| Name of word as counted string ...|
------------------------------------------
In the controlword, some bits have a special meaning:
Bit 0: the word is SMUDGE. The vocabulary searching word FIND
will ignore words with this bit set. REVEAL will set this bit to
zero.
Bit 1: the word is immediate. When compiling, this word will
be executed, nevertheless.
Bit 2: the word is restrict. This means that this word must
not be executed interactivily. It can only be used in the
compiling state (STATE=-1).
Bit 3: the word is a macro. This bit will be set, when the
word has successfully been compiled using the defining words
'M:' or 'MCODE'. When the F68K-macrooption is on (MACRO ON),
then the F68K-compiler will not compile a reference to this
word, but will copy the complete code.
Bits 8-15: in this byte the length of the code is stored.
This number is measured in 16bit. The final RTS-instruction at
the end of the code is not counted. This number is important for
F68K-macrocompiler.
The pointer to code holds the 32bit-address of the code. It is
called the 'CodeFieldAddress' or 'CFA'. This is the address
given by the word ' ' '. This address is already relative to the
codesegment, so '@' or '!' must not be used with this address!
(If you wonder why, remember that code- and datasegment may be
placed anywhere in memory. So it is not possible to fix a
location in the codesegment with an address relativ to the
datasegment.) One longword before beginning of code, there is
the VIEW-field. This field contains a 32bit-value, which is the
number of the physical sourceblock, where this word has been
defined. The VIEW-field was placed by the code instead by the
header because so it will be possible to find the source again,
even when the header has been removed or destroyed. To access
this number, a source sequence like this is necessary:
' A_WORD CODE>DATA \ find codeaddress an convert it
4- @ \ fix pointer to the 32bit-word
\ before the code and fetch the
\ VIEW-information
Be carefull not to compile the address of the VIEW-field after
conversion (CODE>DATA). Conversions have to be done at runtime!!
(see: 'Pointer and Addresses')
The next field contains the pointer to the last recently defined
header (in the same vocabulary). It is called the
'LinkFieldAddress' or 'LFA'. This is an ordinary F68K-pointer
(relative to datasegment). This link is used when searching in
the vocabulary. The LFA of the first word in each vocabulary is
NULL (0).
After the linkfield there is the name of the word preceeded by a
countbyte. This name is a standard FORTH-string.
###############################################################
USER variables
###############################################################
FORTH passes it's parameters on a stack. This is the 'normal'
procedure. But sometimes it is usefull to have some 'real'
variables, which can be refered by name, equal to variables used
in other languages. They are declared in most cases with the
defining word VARIABLE.
But since FORTH is a multitasking, sometimes multiuser system,
this datatype cannot be used in all situations. VARIABLEs can be
changed by all tasks and users. So no task or user can rely on
the content of a variable. If a variable has to be bound to only
one task or user, it has to be defined using the defining word
USER.
USER-variables are all stored together in one field. The size of
that field is determined by the kernel and now is 2 kbyte. There
are about 300 bytes used by the kernel itself, so there is romme
enough. When creating a USER-variable, the offset of the first
free entry in the USER-area is stored together with the header
of the defined word. The access occurs with this offset together
with a pointer to the base of the USER-area. The pointer is
always held within the machine-register D5. Switching this
register to another USER-area means to switch all
USER-variables. Each task has it's own USER-area, so it may
change these variables without taking effect on other tasks.
There are some memory buffers connected to a task, which may not
be shared with others. These is e.g. the vocabulary stack. In
order to allow an automatical allocation of all these buffers
when creating a new task, F68K provides a simple mechanism: all
these buffers are organized in a linked list. Behind the pointer
for the link there will be the length of the buffer in bytes.
The address of the first element of that list is stored in he
USER- variable USERBUFS. So it is very easy to allocate all
necessary buffers.
Hint:
Sometimes it is necessary to put even the block buffers into
this list. This depends on the nature of the taskswitcher
(interruptive or cooperative). The one and only buffer provided
with the kernel has two longwords in front of it left empty, so
that is possible without taking completely new buffers.
###############################################################
Values and locals
###############################################################
F68K provides two types of data, which are not common to all
FORTH implementation, but will be fixed in the coming ANS-
standard. These are VALUE and LOCAL, which are similar in
implementation and usage:
<n> VALUE <name>
<n> LOCAL <name>
VALUE and LOCAL create an initialised datatype, which behaves
just like a constant. So
27 VALUE FOO
FOO . --> 27
In contrast to simple constants, VALUEs and LOCALs are changeble
using the prefix 'TO'. So
35 TO FOO
FOO . --> 35
This is the same with LOCAL.
LOCAL differs from VALUE, of cource. LOCAL can only be used
within ':'-definitions and creates a local value, which is
visible just before the next ';' is compiled.
: ADD ( a b -- a+b )
LOCAL B LOCAL A
A B + ;
B --> unknown!
3 5 ADD . --> 8
To see how LOCALs can be changed, here a somewhat silly example:
: ADD2 ( a b -- a+b )
0 LOCAL A 0 LOCAL B
TO B TO A
A B + ;
By the way, the best and fastest way to define 'ADD' in F68K is
MACRO @ MACRO ON
M: ADD ( a b -- a+b )
+ ;
MACRO !
So 'ADD' is as fast as '+' itself.
LOCALs are very useful when there are a lot of parameters on the
stack or when parameter handling is difficult. The use of LOCALs
in definitions like 'ADD' should be avoided, because they use a
lot of memory and time.
The use of VALUEs instead of VARIABLEs is strongly recommended,
because they are faster and need less memory. Often, the syntax
becomes clearer. The user should not use VALUEs where a CONSTANT
would do the job, too, because nothing is shorter and faster
then a CONSTANT.
Implementation
VALUE and LOCAL both create same code. LOCAL produces an extra
call to allocate the memory for the data. Both use an in-line-
address to find the data. VALUE creates the following code:
+--------------+-----------------+-------------+
| CALL fetcher | in-line-address | CALL storer |
+--------------+-----------------+-------------+
It is important, that the fetcher is compiled using a 'JSR,' so
that the 'CALL fetcher' always uses 8 bytes.
The in-line-address points to the data for VALUEs and to a
pointer to the data for LOCALs. The difference is due to the
fact, that this pointer for a LOCAL has to be modified at
runtime, and so it has to be fixed, when the code segment is
placed in ROM. The pointer holds a fixed address of a location
in the data segment, where the pointer to data will written at
runtime (the data segment may not be in ROM).
The task of the fetcher is to get the in-line-address and to
fetch the data from there. When referencing or compiling the
VALUE, always the fetcher is used. It is 'TO', which activates
the storer. 'TO' finds the codeaddress of the following VALUE-
type using `'`, adds an offset of 12 bytes (8 byte 'CALL
fetcher', 4 byte in-line-address) and executes or compiles this
address itself, corresponding the content of the USER-variable
STATE.
The storer then gets the in-line-address from in front of it's
call and stores the data on the stack in this location.
LOCAL additionally compiles a 'CALL initer' in front of the
sequence above. This 'initer' links 4 bytes off the returnstack,
initialises this place with the value from top of stack and
pushes the address of a routine on the returnstack, which
unlinks the allocated space on the returnstack at exit-time.
This is very complicated to explain, although it is very simple.
The user should take a glance at the sources. There are only a
few lines of assembler in the kernel.
Restrictions
Some little restrictions occur when using LOCALs. The user
should be very careful manipulating the returnstack. Defining a
local means to alter the first two elements on returnstack.
: TEST
... \ something
>R
0 LOCAL TEMP \ never!
R> \ never!
... \ something further
; \ crash!
One could use
R> R> R> SWAP >R SWAP >R
instead of 'R>', but this should be avoided for reasons of
readability.
###############################################################
Vocabularies
###############################################################
F68K supports a vocabulary structure, which allows usage of same
namens for different words. Vocabularies are lists of words
compiled into them. There is a search order of vocabularies,
which is followed when F68K tries to find a word extracted from
the source. When the user has created words with the same name
in different vocabularies, F68K will take the first one it
finds, e.g. the one within the vocabulary, which is the highest
in the search order. The gives the possibility to compile
different version of a word just by installing a appropriate
search order. Applications should always have an own vocabulary
and sub- vocabularies, they are hierarchic, so that the
applications words can easily be seperated from the words inside
the development environment. The main vocabulary of the
environment is called FORTH. System words like DUP, ROT or
FORGET are located in that vocabulary.
Usage
Vocabularies are created using the defining word VOCABULARY
followed by the desired name e.g.
VOCABULARY FOO
Implementation
The implementation had to be somewhat complicated, in order to
preserve the ROMability of the code segment.
Vocabularies consist of a normal header, which may be deleted
when headers are removed, too, and a set of pointers in both,
code and data segment.
There first is the runtime code of a vocabulary with a fixed
length of six bytes (JSR <32b-address>) in the codesegment. Then
there is a pointer (ptr1) to a field in the data segment, which
is normally located behind the header of the vocabulary. This
pointer (ptr3) contains the address of the link field of the
last word in the vocabulary. Behind this pointer there is a
second one (ptr4) which points back into the code segment. It
points to the location just behind the runtime code, where the
pointer (ptr1) into data segment is. So code and data segment
are linked forth and back in a vocabulary.
Vocabularies themselves are linked together with a pointer
(ptr2) which is located with an offset of 8 behind the
runtimecode. The last element of the vocabulary list can be
found in the USER- variable VOC-LINK. It is important to know
that this pointer gives addresses within the codesegment, so '@'
cannot be used. The user has to apply 'CODE@' instead.
CODE DATA
+--> ptr2 of former vocabulary
|
|
| JSR dovoca header
| ptr1 ---------------------------> ptr3 -----+
| ^--------------------------- ptr4 |
+---ptr2 |
|
|
|
last header <+
###############################################################
Interpreter
###############################################################
The subroutine INTERPRET to be decribed here is normally called
the 'outer interpreter' to make clear that there is an inner
interpreter as well. For F68K does not have any inner
interpreter because it creates native code, this distinction has
not to made here.
The F68K interpreter is invoked using the word INTERPRET. This
will interpret or compile the strings given by WORD until WORD
will return the address of a string which will cause NULLSTR? to
answer with true. This will be the case when the input stream,
coming from the terminal or a block, is exhausted. INTERPRET
could be implemented in the following manner:
: INTERPRET ( -- )
BEGIN
BL WORD NULLSTR?
WHILE
PARSER
REPEAT ;
As it can be seen, PARSER will do most of the work consuming the
input. PARSER is a DEFERred word which can either compile or
interpret the strings supplied by WORD. This depends on wether
the actual state of the system (STATE) is compiling (STATE<>0)
or interpreting (STATE=0). In the first case, PARSER will
execute COMPILER, in the latter it will execute INTERPRETER. The
switch between this to words for PARSER is done by the words '['
and ']' which are implemented like that:
: [ ( -- )
0 STATE ! ['] INTERPRETER IS PARSER ;
: ] ( -- )
-1 STATE ! ['] COMPILER IS PARSER ;
INTERPRETER and COMPILER each take the address of the next
string in the input stream as an input parameter and do both
yield no output parameter:
COMPILER ( addr -- )
INTERPRETER ( addr -- )
This implementation makes it possible to use the interpreter
itself for applications just by replacing the word executed by
PARSER.
A simple example for that is to list all words that would have
been isolated by WORD on the terminal instead of really
interpreting them:
: PRINTER ( addr -- )
COUNT TYPE CR ;
: :LIST ( n -- ) \ <n> :LIST
['] PRINTER IS PARSER
LOAD
['] INTERPRETER IS PARSER ;
or to show extended use of DEFER-words:
: :LIST ( n -- ) \ <n> :LIST
['] PARSER CELL+ CODE@ PUSH \ save runtime of PARSER
['] PRINTER IS PARSER
LOAD ;
###############################################################
Control structures
###############################################################
F68K provides control structures, which are identical to those
used in FORTH-83 systems in most cases. But in some points, F68K
turns to the new ANSI standard. This concerns the
BEGIN..WHILE..REPEAT(UNTIL)-structure. In F68K, any amount of
WHILEs are allowed, but there must be a THEN for each additional
WHILE behind REPEAT in order to resolve the references left open
by WHILE. It is also possible to use WHILEs together with AGAIN
or UNTIL, but there has to be a THEN for each WHILE. This gives
the structure some more flexibility, for there may be code
between UNTIL and THEN:
... BEGIN ... WHILE ... WHILE ... UNTIL ... THEN ... THEN
It seems to be clear, that a REPEAT can always be replaced by
'AGAIN THEN' with the additional possibility to place code
between AGAIN and THEN.
The other structures behave as usually. The following structures
are available:
IF ... (ELSE) ... THEN
BEGIN ... AGAIN
BEGIN ... WHILE ... REPEAT
BEGIN ... WHILE ... (WHILE) ... REPEAT ... (THEN)
BEGIN ... WHILE ... AGAIN ... THEN
BEGIN ... (WHILE) ... UNTIL ... (THEN)
DO ... LOOP
The word LEAVE is used to leave a DO...LOOP. EXIT is forbidden
between DO and LOOP because DO...LOOP holds some parameters on
the returnstack. ANSI says there has to be a word UNLOOP which
removes these parameters, so 'UNLOOP EXIT' should be possible.
###############################################################
Reducing the kernel
###############################################################
When using F68K on little machines, where not mass storage
device is connected and memory is spare, then it can be usefull
to cut off some words concerning mass storage from the kernel in
order to save memory. This incudes the one diskbuffer installed
in the kernel. So there is the possibility to save more than 2kB
of expensive memory. Here it is:
' UPDATE FENCE ! \ set FENCE a bit lower
FORGET UPDATE \ and forget the word before diskbuffer
From now on, no UPDATE, (LOAD or LOAD will be available and
there will be no buffer for the BLOCK- and BUFFER-words. BLOCK
and BUFFER assume that there always is at least one buffer. For
this is not the case now, it should be strictly avoided to use
these words. The result would be unpredictable.
Sources, of course, now only can be typed in by hand or been
downloaded from a server.
###############################################################
The F68K streaminterface
###############################################################
'Normal' computers have 'normal' interfaces for using files on
mass storage devices. For F68K is a very special thing, it
follows, that is must have a very special fileinterface.
Speaking more exactly, it is not a real fileinterface, because
it does not interface commonly known files. 'Files' in F68K are
a concatenation of blocks. So they are called BLOCKSTREAMS. It
is a blockstream interface to be described here.
I want to replay briefly the ideas, that lead to such an
interface:
First I was discontented with the too small editing area when
using the classical 1k-blocks. I wanted to use my full 80x25-
screen to write my sources. So for 80x25 make 2000 characters, I
had to take 2k for a block, because no mass storage device I
know allows the usage of 2000 bytes as a blocksize (not in a
simple manner). So there they are, 48 unused bytes. That nearly
broke my heart. As pain makes creative, there quickly came the
idea to use these 48 bytes to design a simple file-, sorry,
blockstreaminterface. The blocks just had to be linked together
forward and backward and some blocks had to used as directories.
That's all! So far history, now the present.
The interface was designed to be loaded immediately on the
kernel. So no sophisticated FORTH-features could be used. The
imagefile created with the streaminterface loaded should be used
as the new kernel.
The 'normal' block management words like BLOCK or LOAD exist
within blockstreams, too. So the user does not ave to learn a
new block handling. '1 LOAD' will load the first block as it
ever does, but now the first block is not the first block on a
disk, e.g. first track, first sector, but the first block within
a blockstream. There no longer is a dependency of the
blocknumber used on high level F68K and the physical blocknumber
given the the R/W primitive.
A blockstream is a simple bidirectional linked list. The first
longword within the unlucky 48 bytes of a physical block
contains the physical blocknumber of the following block. The
second longword holds the preceeding. If there is no following
or preceeding, for all blockstreams have a beginning and an end,
these longwords will enumerate to '-1'. Behind these two
longwords, there is a 16-bit statusword, which decides about the
type (data, directory,..., see below) of this block. So 10 Bytes
of the wasted diskspace are used now.
Here is the first important note: the first block has the number
'1', not '0'. This was done because in a blockstreamsystem that
contains a lot of blockstreams, there are a lot of first block,
of course. Due to the fact, that block '0' cannot be loaded
using the word LOAD, this would create a lot of unusable blocks
of 2k each. I dont like to waste anything.
Here is the structure:
| block 1 |
+--->|--------------|
| | |----+
| |--------------| |
| | -1 | |
| +--------------+ |
| |
| | | block 2 |
| +--->|--------------|<---+
| | | |
| |--------------| |
+-----------------------------| | |
+--------------+ |
|
.
.
.
.
| block n | |
|--------------| |
| -1 | |
|--------------| |
| |-----------------------------+
+--------------+
Only the actual block in use 'knows' where to find the following
or the preceeding block. The blockstream's information is stored
locally. There is no need for something like a FAT (File
Allocation Table) as a globally known place where all
informations about files are stored.
This has many advantages:
- it fits to the problen. The unused 48 bytes are local
storage capacity and can therefore only be used in a local
context.
- it is very save. When working with FATs the destruction of
the physical location on the device, where the FAT is stored, is
a fatal and unrecoverable error. Destroying local information
only causes local damages, which can be recovered sometimes.
- it is completetly independent from any device specific
characteristics. F68K does not care for the nature of the
device.
- very large devices can be managed. About 2^31 2k-blocks are
a lot of storage capacity.
- it is simple.
But it has disadvantages, too:
- it is NOT very fast. Especially in large blockstreams of
some hundreds of blocks it is a hard job to find a block with a
high number when a block with a low number was used last. All
blocks between them will have to be read. One can work around
this disadvantage using short blockstreams and many buffers.
Each blockstream has a unique name. These names are stored in
special directory blocks. Each directory entry holds the name,
the length and the physical number of the first block of a the
corresponding stream. Additionaly there is a status word. This
status words decides wether the described blockstream is a
'normal' blockstream containing user data or a directory again.
Hence, the directory structure is hierachic. The user can build
tree-like directories as they are used within other filesystems,
too.
The physical block 0 of each device will be a directory block,
the so called rootdirectory.
An entry in a directory block describing a blockstream has
following simple structure:
+---------------------------------------------------+
|name of blockstream, max 30 characters|frst|len |st|
+---------------------------------------------------+
^ ^ ^
| | |
phys. number of first block ---------+ | |
| |
length of blockstream --------------------+ |
|
status: 0=datastream, 1=directory ------------+
For each entry needs 40 bytes, one directory block can hold 50
entries. If more entries are needed, then a directory consists
of more than one block, which are organized like datastreams.
Expansion of a directory occurs automatically, the user does not
have to take care.
Implementation
The implementation of the words that are needed to handle
blockstreams are divided into two levels. First there is a set
of words which can be used in :-definitions and have something
of the character of an operating system call.
Second there is a set of words wich can be used typing shell
commands. The second level depends on the first.
The words of the second level will probably be used first, so
they are decribed first. All words here allow the usage of
pathnames. A pathname is a list of directories and
subdirectories which all must exist. The names of the
directories are seperated by '/'-characters. If the first
character of a pathlist is a '/', too, then the path is searched
beginning from the root directory. Otherwise search starts in
the actual directory.
Example: suppose the user to be in the directory MYDIR. In this
directory there is a subdirectory called YOURDIR. Then
'/MYDIR/YOURDIR/TESTSTREAM' and 'YOURDIR/TESTSTREAM' refer to
the same blockstream.
This a common syntax in many operating system so no further
description about pathname is given here.
Different devices can be accessed via the pathname putting the
devicenumber and a colon in front of a pathlist. No slash is
allowed behind the colon, or the device is ignored.
'1:MYDIR/TEST' will take the stream TEST from device 1 in the
path '/MYDIR/' whereas '1:/MYDIR/TEST' will search the actual
device. So for other devices than the current always the
complete pathname has to be given!
MOUNT ( -- ) usage: MOUNT
This will initialise a blockstream system copied to a device.
For directories contain physical blocknumbers, they are not
portable to other devices, where other blocknumbers are used.
MOUNT makes the blockstream system fit to the actual device.
Note that only a system, that has been UNMOUNTed, can be
mounted. Always make sure that you are working with MOUNTed
systems only. Oterhwise malfunction is guaranteed. It is not
possible to doubly MOUNT a system.
UNMOUNT ( -- ) usage: UNMOUNT
This will prepare a blockstream system to be copied to an other
device. From all physical blocknumbers contained in the system,
the number of the first physical block on the actual device will
be substracted. A later mount will reverse that procedure.
Always make sure that you are working with MOUNTed systems only.
Oterhwise malfunction is guaranteed. It is not possible to
doubly UNMOUNT a system.
MAKE ( -- ) usage: MAKE <name>
This will create a blockstream with length 1 in store it into
the actual directory or in the directory given together with
<name>.
USE ( -- ) usage: USE <name>
This will open the blockstream with the name <name> for usage.
Now the words BLOCK, LOAD or BUFFER refer to that blockstream. A
STREAM: structure called USESTREAM will be filled with the data
about the selected stream.
Notice that if a blockstream has been created with MAKE, then
USE must be supplied additionally.
DEL ( -- ) usage: DEL <name>
This will delete the blockstream with the given name. If the
stream has former been USEd, the F68K word created there will
not be deleted!
MORE ( n -- ) usage: <n> MORE
This will extend the blockstream which is currently in USE by n
further blocks. They will be appended to the end of the stream.
LESS ( n -- ) usage: <n> LESS
This will reduce the blockstream currently in USE by n blocks.
They will be taken from the end of the stream.
INSERT ( where amount -- ) usage: <where> <amount> INSERT
This will insert <amount> blocks at the position <where> in the
blockstream.
EXPEL ( where amount -- ) usage: <where> <amount> EXPEL
This will cut <amount> blocks out of the current blockstream.
The block <where> will not be cut, it remains in the stream. To
remember this, remember that the first block of a stream cannot
be EXPELled. E.g. 3 2 EXPEL will cut the block 4 and 5 from the
stream.
MAKEDIR ( -- ) usage: MAKEDIR <name>
This will create a subdirectory with the given <name>.
DELDIR ( -- ) usage: DELDIR <name>
This will delete a directory. Notice that this directory has to
be empty, e.g. it may contain nor streams neither directories,
otherwise an error condition exists.
CD ( -- ) usage: CD <name> or CD
This will change to the directory given in <name>. If no <name>
is given, then the actual directory path is printed on the
terminal.
DIR ( -- ) usage: DIR
Shows the content of the current directory.
DIRECT ( -- ) usage: DIRECT
Switches the blockstreamsystem of. Now BLOCK, LOAD and BUFFER
access the physical blocks again. The variable ROOTBLK is
deleted.
STREAMS ( -- ) usage: STREAMS
Invokes the blockstreamsystem. Now BLOCK, LOAD and BUFFER access
the logical blocks within the system. The variable ROOTBLK is
cleared and has not to be changed!
On the other hand, there are a lot of low-level functions. Some
of them correspond directly with theire high-level counterparts.
The low level functions are more flexible and therefor take more
parameters. There are a lot of things that can only be done
using the low-level words of the stream-interface. The most
important words will be decribed here.
NEXTBLOCK or LINKNEXT ( physblk -- next-physblk )
Get the link to the next physical blocknumber within a
blockstream. This is necessary when using systemvariables like
BLK. In former times one could ind the preceeding block by 'BLK
@ 1-'. These times are gone. Now it is 'BLK @ LASTBLOCK'. Note
that the physical number is returned. Words like BLOCK, LOAD or
BUFFER may not be applied to that number. The words (BLOCK,
(LOAD and (BUFFER have to be used instead. The logical
blocknumber during LOAD is hold in the USER-variable SBLK. The
user should be very careful not to mix up BLK and SBLK.
LASTBLOCK or LINKLAST ( physblk -- last-physblk )
Same as NEXTBLOCK, but in the other direction. See notes there.
SBLK ( -- addr )
This is a USER-variable which contains the number of the logical
block actually loaded. Use SBLK instead of BLK when working with
blockstreams.
BCREATE ( name length -- flag )
Creates a blockstream with the given length. The name has to be
a counted string. The returned flag is 0 when successful. The
flag has value <>0 for different reasons, that cannot be seen
from the flag. Errorconditions are a lack of diskspace or the
stream to be created already exists.
BDELETE ( name -- flag )
Deletes a blockstream from the directory. The name has to be a
counted string. The flag is 0 when successful. An errorcondition
exists when the stream does not exist. The user has to take care
that no stream-structure connected with the deleted stream will
be used after deletion. (be VERY careful!)
DCREATE ( name -- flag )
Creates a subdirectory. The name has to be a counted string. The
flag is 0 when successful. An errorcondition exists when there
is not enough diskspace to create the directory, which needs at
least one block.
DDELETE ( name -- flag )
Deletes a directory. The name has to be a counted string. The
flag is 0 when successful. An errorcondition exists when the
directory does not exist.
CHANGEDIR ( name -- flag )
Changes to the given directory. The name has to be a counted
string. The flag is 0 when successful. Changing a directory
means to put the physical number of the directory block into the
USER- variable DIRECTORY. An errorcondition exists when the
given directory does not exist.
###############################################################
Lineeditor
###############################################################
When porting F68K to a new system, the terminal control is
sometimes very hardware/OS-dependent. So a full screen editor
cannot work without changes, because this editor has to move the
cursor,clear the screen, ... and so on. In order to be able to
do this changes, there is a primitive lineeditor, which works on
the 2k-blocks. All actions of this editor are invoked from the
F68K commandline. They allow a rather fast and comfortable
change in textlines. I have created this editor when F68K
consisted of the kernel only. I had to CMOVE strings into the
blocks. After doing this, the lineeditor is really comfortable.
LIST ( nr -- )
Displays the screen with the number given. The first column
of the screen will contain the linenumbers with BASE 25. This
number has to be used for all other actions with the editor. The
blocknumber is saved in the variable SCR. For most terminals
only have 80x25 characters, LIST will wait for a key to be
pressed, otherwise the first line would always scroll away.
L ( -- )
Lists the block listed last again. It uses the number held
in SCR.
N ( -- )
Lists the following block. This will be the block
"SCR @ 1+".
B ( -- )
Lists the last block. This will be the block "SCR @ 1-".
R ( -- ) \ R <nr>
Shows the row <nr>. <nr> has to be given in BASE 25. So
there is one digit only. The appropriate numbers are shown by
LIST.
C ( -- ) \ C <nr>
Selects the column <nr> (decimal). This will be the actual
writing position.
$ ( -- ) \ $ <string>~
Write the string <string> to the position selected by R and
C. '~' is the delimiter of the string.
Example:
R h C 25 $ Hallo~
will insert the string "Hallo" in row 17 and column 25.
D ( -- )
Deletes a character at the position selected with R and C.
DM ( -- ) \ DM <nr>
Delete Multiple; Deletes <nr> characters at the position
given by R and C.
DL ( -- ) \ DL <nr>
Delete Line; Deletes the line <nr>. <nr> has to be given
with BASE 25.
IL ( -- ) \ IL <nr>
Insert Line; Inserts a line at line <nr> (BASE 25).
GL ( -- ) \ GL <nr>
Get Line; Copies the given line <nr> (BASE 25) into special
buffer.
PL ( -- ) \ PL <nr>
Put Line; Inserts the line from the buffer at the given
linenumber (BASE 25).
The user should be aware that this lineeditor is not simple, it
is primitive. No error- or rangechecking is done. It is very
easy to destroy the screen or to insert unEMITable characters.
The user will not see them, but the compiler will. So the user
has to be very carefull using the lineeditor.
###############################################################
Fullscreen Editor
###############################################################
F68K comes with a very simple full screen editor. Actually this
editor was one the first programs I ever wrote in FORTH. It is
capable to do the most necessary things only. In contrast to the
lineeditor it is quite save and comfortable. No manual is
necessary to use it. It is invoked using the command L ( nr -- )
or V ( -- ), where V edits the screen most recently edited with
L. A simple help can be read when typing ^?.
The editor needs some cursor manipulation words. These should be
preloaded. For details see the blockstream VT52-TERMINAL, where
all desired words (and more) are defined.
The cursor is moved around using the keys ^E, ^X, ^S and ^D. If
keyboard in use has cursorkeys, then the keycodes in the table
of actions in the editor's source can be changed easily. This
table can be extended, too.
I hope that one of the F68K users will write a better editor,
which could support some functions of the streamsystem, in
future.
###############################################################
F-EDIT
###############################################################
Markus Redeker has written a new fullscreen editor, which
should be installed instead of the one described above. It is
much more pleasant but uses some more facilities of a VT52
terminal. It is possible to make the editor fit into different
environments and different keycodes. So it is not longer
necessary to use only the keycodes of a ASCII-keyboard.
Cursorkeys or functionkeys may be used as well.
Here is the original documentation written by Markus:
The F-EDIT screen editor (Version 1.0)
1. How to get started (if F-EDIT is already installed)
Start F-EDIT the usual way by typing <blk#> L to edit the block
<blk#> of the USEd file. If this is the first block you edited,
F-EDIT will ask for your ID. Enter your initials and the
current date. They will be written automatically on the upper
right corner of every block you have changed.
The edited FORTH block fills the whole screen of the
computer; messages and questions are displayed on the last
line. To distinguish them from the edited text, they are
displayed inverse.
The use of the keys varies on different computers; to find
out which key invokes which function, type the traditional HELP
key of your computer (usually the key marked HELP, or that used
as HELP key by most other programs) and you will see the help
block with the ID you entered before on its upper right corner.
Read it and type any key to resume to the edited FORTH block.
Read also the following sections to understand the help screen
better.
2. Some unusual features
Current line:
begins at cursor position (C) and ends 80 characters later:
Cxxxxxxxxxxxx...
...xxxxxxxxxxxx
This convention makes the line stack much more useful, but be
aware of it if you want to delete a line.
String input:
F-EDIT displays the previous content of the edited string.
Enter either the entire new string - or nothing if you don't
want to change it. (Sorry, no input editing.)
User's ID:
After reading it, F-EDIT stores the identifier on the help
screen and reads it again when the system is started the next
time. So you can leave F68K, work in the operating system, and
start F68K again without the need of entering always your name
and the date. (This is specially useful after a crash...)
3. Words
l ( blk# -- )
edits the block of the USEd file with number <blk#>.
v ( -- )
calls the editor without initializing it: usually you can
continue where you stopped editing before; when an error occurs
while loading, the cursor is placed where the error occurred.
4. Structure of the program
The program consists of two files: the file F-EDIT contains the
main program and a "system file" contains system dependent
definitions and the help block. It shall be named after the
computer it is meant for: in my case it is called ATARI-EDIT.
5. Writing a System File
5.1. Loading the main program F-EDIT and call EDITOR vocabulary
5.2. Definition of the key table
The key table consists of definitions of the form
<key> --> <action>
where <action> is one of the internal words of the editor that
do the real work, and <key> is the number of the key that
invokes that action. Which number you have to take depends on
the definition of EDIT-KEY (see below). The possible <action>s
can be found in ATARI-EDIT.
Please use the key table of ATARI-EDIT as a model for
your own key table to make a computer change easier. (The
ATARI- EDIT key table is designed to be as compatible as
possible to all FORTH screen editors I know.)
5.3. I/O words
(The words REVERSE_VIDEO and NORMAL_VIDEO are expected to
exist by F-EDIT.)
The following words are DEFERred:
EDIT-KEY ( -- key )
is used instead of KEY by the editor to treat also the special
keys (cursor keys, HELP key, ALT etc.) which are not ASCII.
EDIT-KEY waits like KEY until a key is pressed and returns its
number. The editor uses the result <key> only to compare it
with the values in the key table; therefore you can code your
keys how you like it (and have to do it, because there is no
standard code). You can use all 32 bits of <key> - that is more
than usually needed but was easier to program.
Of course the editor needs also ASCII characters.
Therefore EDIT-KEY stores the ASCII number of the key (if there
is one) to the 8bit variable CHAR; it is taken from there if
<key> is not found in the key table.
SCREEN-I/O ( -- )
prepares the screen for editing, by making it possible to
write on the last position of the last line, and by other
system-dependent actions.
LINE-I/O ( -- )
switches back to the normal i/o mode by undoing the actions of
SCREEN-I/O. The editor calls LINE-I/O also before EXPECTing a
string, which happens in inverse mode. So DO NOT switch from
inverse to normal display!
You have to take care of the cursor yourself, because there is
no common way to handle it. Some programs show it only
expecting an input, others always. On Atari, I prefer the first
method because it is a bit faster.
5.4. Help block
The help block is usually the last block of the System File.
Store its absolute position in the variable HELPBLK by writing
BLOCKSTREAM ALSO BLK @ NEXTBLOCK HELPBLK !
in the last but one block. Defining a help block is absolutely
necessary, because the editor uses the value of HELPBLK to
check whether a system file has been loaded and refuses to
start if HELPBLK = 0.
6. Bugs & other insects
A system crash caused by F-EDIT seems unlikely (if F-EDIT is
installed right), but there are some details that may cause
problems:
- F-EDIT refers to the help block by its absolute number. If
you have changed the disk (which is possible at least with the
ATARI loader), HELP would show you a block with less useful
information.
More problems arise when you also invoke the "Get ID"
function: F-EDIT reads the ID from the help screen and writes
it back afterwards. So, if you see some nonsense as previous
ID, just press RETURN to let it how it was before.
- The messages will be displayed in a better way as soon as
F68K allows it.
###############################################################
Possible errors
###############################################################
This chapter shall prevent the users from possible errors, which
are known to me. Some of them I made myself a couple of times.
There is no order in these notes. I wrote them down like I found
them.
- If a blockstream has just been created using MAKE, it must be
prepared for use with USE. USE is VERY likely to be forgotten. A
common (errornous) sequence is
MAKE test
10 MORE
Now there is a blockstream 'TEST' with one block and an other
blockstream has ten blocks more.
- Headers can only be accessed when using `H'` instead of `'`.
###############################################################
###############################################################
Documentation of loaders for different systems
Implemtation specific notes
###############################################################
###############################################################
###############################################################
Loader implementation for Atari ST
###############################################################
The author of F68K (that's me) is the proud owner of an Atari
ST. So the loader for this machine is the most sophisticated
until now. First it was written in assembler and very simple,
taking the blocks directly from Disk. To give away any sources
meant to extract them from the disk and to put them into a DOS
file.
The next version has been written with the very nice freeware
SOZOBON C-compiler. This allows easy expansion of the loader in
the future. The F68K blockdevices are totally emulated within
GEMDOS-files. A configuration file, which can be edited with any
texteditor, is supported now to make installation easy. I will
come to that later.
The actual version was build using the commercial Turbo-C
compiler, because there were to much bugs in the libraries of
the SOZOBON system.
The SOZOBON compiler seemed to be made to write F68K loaders.
Only a very few lines of in-line assembler were enough to make
the I/O functions fit the the F68K parameter protocol. But the
source was not portable because the in-line assembler is very
compiler dependent. Using the Turbo-C compiler the in-line code
vanishes completely. But one has to use the compilerspecific
keyword CDECL for all I/O functions to achieve that parameters
are passed on the stack instead of passing them in registers.
The configuration file contains the name of the system image,
the sizes for code and data segments, the names of the READSYS
inputfile and the WRITESYS outputfile (see 'Loader') as well as
the number of devices and the names of the appropriate .SCR-
files. Some errorchecking is done while interpreting this file,
but the user should try to keep it clean, nevertheless. The
order of the entries in this file may not be changed (it's C,
not FORTH).
Installation of F68K on Atari ST
F68K for Atari ST will come completetly installed as a floppy
based system. This is not very fast, especially using the
blockstream system, but there is no reason not to install it on
a hard- or ramdisk. Therefore a hard- or ramdisk must exist, of
course. When putting the source to another device, a
recompilation of F68K can be usefull, when the order or the size
of the logical F68K devices, that means the GEMDOS files, have
changed. Otherwise just a copy of one or all files to the
desired place and changes to the corresponding entries in the
F68K configuration file F68K.CFG have to be made. If the user
wants to duplicate one file on a hard- or ramdisk, that means to
hold the same file on floppydisk and on e.g. harddisk, he has to
take care about the filesystem. It has to be UNMOUNTed before
copying. After the copy completed and the necessary changes in
the configuration file have been performed, both new F68K
devices will have to MOUNTed again. That's all. VIEW will
nevertheless search his information on the original device! To
alter this, the user will have to recompile the system.
Recompiling goes in two steps. It is possible to make the second
one only. First the filesystem itself has to be loaded on top of
the kernel. Therefore the name of the image in the configuration
file has to be changed into KERNEL.IMG. The filesystem's sources
are located in the file RAW.SCR, which indicates, that this
device cannot be accessed via the blockstream system. If RAW.SCR
is the first device in the configuration file, the system can be
started and '1 LOAD' can be performed. If it is not, the user
has to get the rootblock of the device from the ROOTTABLE and to
store it in the USER-variable ROOTBLK, e.g. if it is the second
(counted from 0) device
2 8* ROOTTABLE + 4+ @ ROOTBLK !
Then again, '1 LOAD'.
When loading finishes the new image can be saved using SAVE.
This image now is a GEMDOS file with the name given in the
configuration file behind 'output:'. This will be the new kernel
in future.
The user leaves F68K, changes the configuration file in order to
use the new image and starts F68K again. It will use the
blockstream system from now on.
In the second step, the system extensions can be loaded. After
changing to the appropriate device and saying 'MOUNT' for safety
the user may change to the directory SYSTEM via
CD SYSTEM
There will be a blockstream called LOADME which can be loaded
with
USE LOADME
1 LOAD
After the (hopefully) succesful completion, SAVE can be
performed again (the user has to make sure that the former saved
image is not overwritten now!).
After that the system is installed completely and ready to use.
VIEW will search on the logical devices where source has been
compiled from.
###############################################################
Loader implementation for Sinclair QL
###############################################################
**************************************************
*** ***
*** Loader for ***
*** ^^^^^^^^^^ ***
*** F68K ***
*** ^^^^ ***
*** on the ***
*** Sinclair QL ***
*** ***
*** by Dirk Kutscher ***
*** ***
**************************************************
Content
Hardware Requirements
Files
Getting started
Special features
Problems?
Remark
*************************
* Hardware Requirements *
*************************
To start F68K on your QL you need at least one 3.5" disc drive
and a minimum of 256 KB RAM. If you lack the disc drive you
might be able to run F68K but should not try to use the
Streaminterface to access DEVICE1_SCR since it is assumed to
provide 300 blocks which you cannot emulate on microdrive. I am
working at a mdv version at the moment.
************
* FILES *
************
The loader consists of the following files:
bootF68K_QL
This is a SuperBASIC-program which initialises the F68K-Disc for
the use on a QL-System. If your F68K version was distributed on
Atari Disc it is necessary to recreate the QLF68K_exe file (as
well as QLCONFIG_exe) since the job information in the header of
QL-files (Dataspace etc.) is not available on Atari Discs. Once
you have run this program on your QL Disc you can happily forget
it.
QLF68K_EXE
This the EXECutable loader created by boot. Start F68K by typing
'EXEC flp1_QLF68K_EXE'. Of course you can also use QRAM etc..
QLF68K_CDE
This is the machinecode program for loading F68K. If you have
the -exe file installed it should not concern you anymore.
QLF68K_ASM
This is the assembler source for the loader. It will probably
only assemble on Talent's Workbench Assembler, but it might give
you some ideas of how to write your own loaders. (Of course you
may also use it to expand the loader).
QLF68K_TXT this one
CHANGES_TXT loader development documentation
QLCONFIG_exe
This is the configuration utility for QLF68K_exe. It gives you
the possiblity to patch the following filenames according to
your personal hardware environment:
flp1_F68K_img (the F68K bit image)
flp1_F68K_out (the F68K output file)
flp1_F68K_in (the F68K input file)
flp1_DEVICE1_scr (Blockdevice for RW-access)
flp1_STREAMS_scr ""
ser1 (Printer device)
If you like to keep these names you just have to confirm by
hitting ENTER. You are then asked (in German), if you would like
these names (except for the printer) to be questioned again when
starting F68K. For experiments, first attempts etc. it might be
useful to answer 'j' (for 'yes'). If you are familar with the
system or even want save a complete application 'n' (for 'No')
will make the loader start F68K directly when executed.
QLCONFIG_cde
The mc-file for the configuration utility. Loaded by
bootF68K_QL.
*******************
* Getting started *
*******************
On executing QLF68K_exe the program first tries to allocate
enough space in the memory for F68K. If the neccessary amount of
bytes ($10000 + $20028 for code and data) is not available the
execution will terminate.
After this the program opens the console and gives you the
possibility to change the names for the necessary files. (See
files.QLConfig_exe) If you do change the names (e.g. the device)
you should make sure that these files really exist and that they
also can be accessed in the same way as the original file names.
If no change is necessary just confirm by hitting ENTER.
Now the program is loading the F68K-file (i.e 'flp1_F68K_IMG').
If loading has been succesful F68K should now prompt with the
copyright note and with it's 'ok'.
If you want to access the Blockdevice now you should make sure
that you can provide the specified files (e.g. DEVICE1_SCR and
STREAMS_SCR) on the specified devices otherwise you might at
least get an error report by QDOS or F68K. You do not have to
worry about closing channels when changing discs (e.g. on a
multitasking QL), since the RW routines are somewhat 'atomic' in
this regard: They open and close their channels each time they
are called. At first this seems to slow down the block accesses
but thanks to QDOS this impression vanishes because all the
often called directory blocks etc. are stored in slave blocks of
the QDOS filing system.
********************
* Special features *
********************
Since F68K's editor uses VT52-sequences to control the cursor
etc., which is normally non-standard on the QL, these control
codes are emulated by the loader's I/O routines. There is also a
translation table for some special characters ("Deutsche
Umlaute") including ENTER, BACKSPACE etc.
Nevertheless the user may still redirect I/O as he desires. I
have provided three different EMIT routines each giving a
different degree of emulation: Default is full emulation. The
VT52-sequences as well as the the translation tables are
supported. You could switch between these different modes by
some simple FORTH words:
: VT52_OFF ( -- ) EMITS 8 + @ ^EMIT ! ;
: VT52_ON ( -- ) EMITS 4 + @ ^EMIT ! ;
: NO_TRANSLATION ( -- ) EMITS 12 + @ ^EMIT ! ;
*************
* Problems? *
*************
Problems may occur if you have got the TOS-formatted F68K disc
and are not able to create the appropriate QL disc, probably
because you lack a disc driver or a transformation utility. In
such a case you can send me (adress below) two QDOS-formatted
3.5" discs AND a paid and self-adressed envelope and I will rush
you the latest QL version as soon as possible.
**********
* Remark *
**********
I have written this loader to support both the spread of the
programming language Forth and QDOS, the operating system of the
QL. So you can make as many copies of the loader as you want and
also distribute them as long as no profit is gained by the
distribution. I would also like my name to be kept visible
etc...
The programme has been successfully tested on R. Kowallik's
QL-Emulator for the Amiga.
I have no objections to somebody writing a C version of the
loader nor other improvements. I would be pleased to hear from
you then!
For suggestions, questions or any other comments please use the
following adress:
Dirk Kutscher
Kastanienweg 39
2804 Lilienthal
Germany
You can also contact me via eMail using one of the following
adresses:
Dirk_Kutscher@HB.MAUS.DE
Karl @ BBS.FORTH-eV.de
###############################################################
Loader implementation for Commodore Amiga
###############################################################
The amiga support for the F68K consists of 4 files:
--- LOAD4TH the loader
--- LOAD4TH.c it's source
--- LOAD4TH.info the logo to start it from the workbench
--- and this file.
In general, it's in functional agreement with the atari Loader
for the F68K, especially concerning the environment handling
with the configuration file F68K.CFG.
I added two little options to get screen data printed on paper:
--- Starting "LOAD4TH p" opens the file "dump.dat" in the
current directory and puts all bytes into it, that passed
through the "Emit" function.
--- starting "LOAD4TH s" does the same with the exception, that
CR's are are filtered out, furthermore any byte that follows a
LF. This provides a very simple and effective method to print
screens. (I like programming in bed, and because the amiga isn't
a laptop..) It is applied to the F68K "list" word, and very
little extra work on a conventional editor is necessary to clean
out the rest of OS-garbage contained in the file.
Since the amiga uses some special keycodes, three commands of
the fullscreen-editor had to be changed:
^F instead of ^? calls the help screen
^V instead of ^< does #1.load
^\ instead of ^> does load.exit
Despite of this, all operation is the same as described in the
F68K original documentation. Before first use, the blockfiles
have to be "unmount"ed and "mount"ed again as described in the
technical manual.
To start F68K from the workbench, the ".info"-file has to be
copied into the same directory, where the loader is. It will put
the appropriate logo on the workbench. The p and s option are
available from the CLI only.
If you have received these files not on an amiga-formatted
disk, the file "LOAD4TH.info" might have been renamed as
"LOAD4TH.inf" to be in line with naming rules. When transferring
this file to the amiga, it must be renamed to "LOAD4TH.info",
otherwise your workbench will not show it.
There is (at the present state of development) one major
problem, which should not be disregarded:
This loader opens a RAW: window on the amiga, which is quite
easy to program, but has a fixed line resolution of 77 true
characters per row. (There is a horizontal resolution of 80
Characters of this standard font, but two are lost for the
borderlines and one is reserved to place the leading cursor). If
anybody knows a simple trick to smash this on DOS-level, let me
hear.
Questions and hints:
Wolfgang Schemmert
Luisenstr. 51
W-6050 Offenbach
Tel. 069-88 56 06
FAX 069-81 10 50
###############################################################
Glossary
###############################################################
(ABS) ( absaddr -- reladdr )
Vocabulary FORTH
Converts an absolute address into a standard F68K-datapointer.
Example:
0 (ABS) @
\ gives the boot SSP
(ABSCODE) ( absaddr -- code-reladdr )
Vocabulary FORTH
Converts an absolute address into a standard F68K-datapointer.
Example:
0 (ABSCODE) CODE>DATA @
\ gives the boot-SSP.
(EMIT) ( -- addr )
Vocabulary FORTH
(EMIT) is a USER-Variable, which contains an F68K pointer to
the word, which is executed by EMIT. It is preset with
LOADEREMIT.
(EXPECT) ( -- adr )
Vocabulary FORTH
(EXPECT) is a USER-variable which contains the CFA of the word
which will be used by EXEPCT.
(FIND ( string -- controlword cfa | string -1 )
Vocabulary FORTH
Searches for a word with the name given with the string in the
vocabularies found in the search order. (FIND uses VOCSEARCH.
Example:
" DUP" (find
\ or
' (find Is find
" DUP" find
(KEY) ( -- addr )
Vocabulary FORTH
(KEY) is a USER-Variable, which contains an F68K pointer to the
word, which is executed by KEY. It is preset with LOADERKEY.
(KEY?) ( -- addr )
Vocabulary FORTH
(KEY?) is a USER-Variable, which contains an F68K pointer to
the word, which is executed by KEY?. It is preset with
LOADERKEY?.
(R/W) ( -- addr )
Vocabulary FORTH
(R/W) is a USER-Variable, which contains an F68K pointer to the
word, which is executed by R/W. It is preset with LOADERR/W.
(READSYS) ( -- addr )
Vocabulary FORTH
(READSYS) is a USER-Variable, which contains an F68K pointer to
the word, which is executed by READSYS. It is preset with
LOADERREADSYS.
(TYPE) ( -- adr )
Vocabulary FORTH
(TYPE) is a USER-variable which contains the CFA of the word
which will be used by TYPE.
(WRITESYS) ( -- addr )
Vocabulary FORTH
(WRITESYS) is a USER-Variable, which contains an F68K pointer
to the word, which is executed by WRITESYS. It is preset with
LOADERWRITESYS.
.LAST ( -- )
Vocabulary FORTH
Prints the name of the most recently defined word.
>ABS ( reladdr -- absaddr )
Vocabulary FORTH
Converts a standard F68K-datapointer into an absolute address.
Example:
0 >ABS
\ gives the physical start of datasegement
>ABSCODE ( code-reladdr -- absaddr )
Vocabulary FORTH
Converts a standard F68K-codepointer into an absolute address.
Example:
0 >ABSCODE
\ gives the physical start of codesegement
>R ( n -- )
Vocabulary FORTH
Pushes the top of stack onto the returnstack. A conversion is
done so that a standard F68K-codepointer becomes an absolute
machineaddress on the returnstack. 'R>' and 'R@' reconvert the
value on the returnstack so that conversion should be invisible
to the user.
?CR ( column -- )
Vocabulary FORTH
If the actual writing column (held in OUT) is greater than the
given column, a CR is performed.
[VOC'] ( -- ) \ : ... [voc'] <vocname> ... ;
Vocabulary FORTH
Finds the address of the named vocabulary. This may be very
usefull together with searching word VOCSEARCH. The returned
address corresponds to those held in CURRENT or CONTEXT. This
is the compiletime version of VOC'.
Example:
: findDUP
" DUP" [voc'] forth vocsearch ;
\ see also VOC'
^EMIT ( -- addr )
Vocabulary FORTH
^EMIT is a vector which contains the function which is actually
used to emit a character. Normally, I would have named such a
vector (EMIT), but in this case the content of ^ EMIT is not
executable by EXECUTE, for it needs somw interfacing provided
by EMIT, which calls the function ^EMIT points to.
^KEY ( -- addr )
Vocabulary FORTH
^KEY is a vector which contains the function which is actually
used to get a character from the terminal (or from elsewhere).
See ^EMIT!
^KEY? ( -- addr )
Vocabulary FORTH
^KEY? is a vector which contains the function which is actually
used to check the state of the terminal. See ^EMIT!
^R/W ( -- addr )
Vocabulary FORTH
^R/W is a vector which contains the function which is actually
used to read or write from/to mass storage. See ^EMIT!
^READSYS ( -- addr )
Vocabulary FORTH
^READSYS is a vector which contains the function which is
actually used to read a file in a system dependent manner. See
^EMIT!
^WRITESYS ( -- addr )
Vocabulary FORTH
^WRITESYS is a vector which contains the function which is
actually used to write a file in a system dependent manner. See
^EMIT!
ALSO ( -- )
Vocabulary ONLY
ALSO is a 'DUP' on vocabulary-stack. The first vocabulary in
the search order is searched twice after ALSO. In most cases,
this first vocabulary will be replaced by an other one.
Example:
FLOAT ALSO FORTH
\ The use of 'ALSO' is reverse to 'TOSS'.
BACKSPACE ( -- )
Vocabulary FORTH
Emits the ASCII-Code 8 in order to move the cursor one column
to left.
Example:
: backspace ( -- ) 8 emit ;
BACKSPACES ( n -- )
Vocabulary FORTH
Executes BACKSPACE in a loop.
BCREATE ( name length -- flag )
Vocabulary BLOCKSTREAM
Creates a blockstream with the given length. The name has to be
a counted string. The returned flag is 0 when successful. The
flag has value <>0 for different reasons, that cannot be seen
from the flag. Errorconditions are a lack of diskspace or the
stream to be created already exists.
BDELETE ( name -- flag )
Vocabulary BLOCKSTREAM
Deletes a blockstream from the directory. The name has to be a
counted string. The flag is 0 when successful. An
errorcondition exists when the stream does not exist. The user
has to take care that no stream-structure connected with the
deleted stream will be used after deletion. (be VERY careful!)
BELL ( -- )
Vocabulary FORTH
Writes '7' to the terminal which should respond with noise.
Example:
: bell ( -- ) 7 emit ;
BLK ( -- addr )
Vocabulary FORTH
BLK is a USER-variable, which contains the number of the
physical block actually loaded.
BYE ( ?? -- )
Vocabulary FORTH
Leaves the F68K interpreter and gives control back to the
loader.
CAPACITY ( -- n )
Vocabulary FORTH
Gives the length of the current blockstream.
Example:
1 capacity index
\ INDEX for the whole blockstream
CAPS ( -- addr )
Vocabulary FORTH
This USER-variable decides wether strings isolated from the
inputstream are capitalized. This can be very useful when
'misusing' the interpreter.
CD ( -- ) \ CD <name> or CD
Vocabulary FORTH
This will change to the directory given in <name>. If no <name>
is given, then the actual directory path is printed on the
terminal.
CELL+ ( n -- n+4 )
Vocabulary FORTH
Increases n by the width of one addresscell = 4 bytes.
CELL- ( n -- n-4 )
Vocabulary FORTH
Decreases n by the width of one addresscell = 4 bytes.
CELLS ( n -- n*4 )
Vocabulary FORTH
Calculates the width of n addresscells.
Example:
CREATE FIELD
0 , 1 , 2 , 3 , 4 , 5 ,
: 3 ( -- 3 )
FIELD 3 CELLS + @ ;
CHANGEDIR ( name -- flag )
Vocabulary BLOCKSTREAM
Changes to the given directory. The name has to be a counted
string. The flag is 0 when successful. Changing a directory
means to put the physical number of the directory block into
the USER- variable DIRECTORY. An errorcondition exists when the
given directory does not exist.
CHAR+ ( addr -- addr+1 )
Vocabulary FORTH
Calculates the address of the following character.
CHARS ( n -- n )
Vocabulary FORTH
Calculates the address distance of <n> characters. In F68K,
CHARS is a dummy, because the length of a character is 1.
CODE! ( n coderelative-addr -- )
Vocabulary FORTH
Stores a value into the codesegment. See CODE@ and CODE>DATA.
Example:
: CODE! CODE>DATA ! ;
CODE>DATA ( coderelative-addr -- datarelative-addr )
Vocabulary FORTH
Converts an address, which is relative to the codesegment, into
an address relative to the datasegment.
CODE@ ( coderelative-addr -- n )
Vocabulary FORTH
Fetches a value from the codesegment, e.g. '-addresses. See
CODE! and CODE>DATA.
Example:
: CODE@ CODE>DATA @ ;
' PARSER CELL+ CODE@ @
\ get the pointer to the runtime routine
\ of the DEFERed PARSER
\ = ' INTERPRETER or ' COMPILER
COLUMNS ( -- addr )
Vocabulary FORTH
Variable which holds the number of available columns on the
current terminal.
COMPILER ( addr -- )
Vocabulary FORTH
COMPILER tries to compile the input stream. It is used by
INTERPRET when STATE <>0.
DCREATE ( name -- flag )
Vocabulary BLOCKSTREAM
Creates a subdirectory. The name has to be a counted string.
The flag is 0 when successful. An errorcondition exists when
there is not enough diskspace to create the directory, which
needs at least one block.
DDELETE ( name -- flag )
Vocabulary BLOCKSTREAM
Deletes a directory. The name has to be a counted string. The
flag is 0 when successful. An errorcondition exists when the
directory does not exist.
DEL ( -- ) \ DEL <name>
Vocabulary FORTH
This will delete the blockstream with the given name. If the
stream has former been USEd, the F68K word created there will
not be deleted!
DELDIR ( -- ) \ DELDIR <name>
Vocabulary FORTH
This will delete a directory. Notice that this directory has to
be empty, e.g. it may contain nor streams neither directories,
otherwise an error condition exists.
DIR ( -- )
Vocabulary FORTH
Shows the content of the current directory.
DIRECT ( -- )
Vocabulary FORTH
Switches the blockstreamsystem of. Now BLOCK, LOAD and BUFFER
access the physical blocks again. The variable ROOTBLK is
deleted.
DROP ( n -- )
Vocabulary FORTH
DROPs the top element on stack (TOS).
DUP ( n -- n n )
Vocabulary FORTH
DUPlicates the top of stack (TOS).
EMIT ( character -- )
Vocabulary FORTH
Writes the character on the stack to the terminal. EMIT uses
the function stored in the USER-variable ^EMIT. This function
must use one of the loaders I/O-routines, which are stored in
the array EMITS, because some interfacing has to be done with
these routines.
EMITS ( -- addr )
Vocabulary FORTH
Gives the address of an array, which holds the number and the
entry-addresses of the loaders I/O-functions to emit a
character to the terminal. 'EMITS @' gives the number of the
functions. 'EMITS 4+ @' is the address of the first routine.
The routines in this table may not be executed from F68K
because they need some interfacing provided by EMIT.
EVALUATE ( c-addr count -- )
Vocabulary FORTH
This is an explicite call of the outer interpreter. The string
at c-addr with the length count is interpreted. EVALUATE may be
nested.
Example:
" 1 2 3 . . . " EVALUATE
\ gives: 3 2 1
EXPEL ( where amount -- )
Vocabulary BLOCKSTREAM
This will cut <amount> blocks out of the current blockstream.
The block <where> will not be cut, it remains in the stream. To
remember this, remember that the first block of a stream cannot
be EXPELled.
Example:
3 2 EXPEL
\ will cut the block 4 and 5
\ from the stream
FIND ( string -- controlword cfa | string -1 )
Vocabulary FORTH
Searches for a word with the name given with the string in the
vocabularies found in the search order. FIND is a deferred word
and initially executes (FIND.
Example:
" DUP" find
\ using kernel's FIND:
' (find Is find
FORTHPARAS ( -- addr )
Vocabulary FORTH
Gives a pointer to a structure which is provided by the loader
and holds all parameters the loader passes to F68K. F68K itself
will store the loaders registers here in order to give the
loader access to its own runtime environment. The FORTHPARAS
are of following structure:
Example:
struct forthparas
{
long registers[16];
/* to be filled by F68K */
void *data, *code;
void *datstk; *retstk;
void *TIBptr;
void *keytable;
void *keyqtable;
void *r_wtable;
void *readsystable;
void *writesystable;
void *roottable;
}forthparas;
INCLUDE ( -- ) \ INCLUDE <filename>
Vocabulary FORTH
This is an interactive word to 'USE 1 LOAD' a blockstream. This
is usefull, because it is not allowed to 'USE 1 LOAD' streams
from within other USEd streams. USEing a stream while loading
another USEd streams implies to make the new stream the actual
one. So there is no return! INCLUDE saves the actual stream and
restores it again (see SAVE_STREAM, RESTORE_STREAM).
INDEX ( fromblock toblock -- )
Vocabulary FORTH
Gives a list of the first lines in the specified range of
blocks. There is a convention that the first line in each
screen has to be a comment.
Example:
1 capacity index
\ INDEX for the whole blockstream
INSERT ( where amount -- )
Vocabulary FORTH
This will insert <amount> blocks at the position <where> in the
blockstream.
INTERPRET ( -- )
Vocabulary FORTH
This is the outer interpreter of the F68K-system. It will
expect some source code provided by SOURCE and interpret it
(surprising, isn't it?). It will return when the input stream
is exhausted, e.g. WORD gives back a NULLSTR?. INTERPRET
executes the DEFERed word PARSER to handle a string from the
source. PARSER holds either COMPILER or INTERPRETER.
Example:
: INTERPRET ( -- )
BEGIN
NAME NULLSTR?
WHILE
PARSER
AGAIN ;
INTERPRETER ( addr -- )
Vocabulary FORTH
INTERPRETER tries to interpret the input stream provided by
SOURCE. It is used by INTERPRET when STATE=0.
KEY ( -- character )
Vocabulary FORTH
Reads a character from the terminal. KEY uses the function
stored in the USER-variable ^KEY. This function must use one of
the loaders I/O-routines, which are stored in the array KEYS,
because some interfacing has to be done with these routines.
KEY? ( -- flag )
Vocabulary FORTH
Checks the terminal for availability of a character. KEY? uses
the function stored in the USER-variable ^KEY?. This function
must use one of the loaders I/O-routines, which are stored in
the array KEY?S, because some interfacing has to be done with
these routines.
KEY?S ( -- addr )
Vocabulary FORTH
Gives the address of an array, which holds the number and the
entry-addresses of the loaders I/O-functions to check the state
of the terminal. 'KEY?S @' gives the number of the functions.
'KEY?S 4+ @' is the address of the first routine. The routines
in this table may not be executed from F68K because they need
some interfacing provided by KEY?.
KEYS ( -- addr )
Vocabulary FORTH
Gives the address of an array, which holds the number and the
entry-addresses of the loaders I/O-functions to read a
character from the terminal. 'KEYS @' gives the number of the
functions. 'KEYS 4+ @' is the address of the first routine. The
routines in this table may not be executed from F68K because
they need some interfacing provided by KEY.
LASTBLK ( -- addr )
Vocabulary FORTH
This is a global variable, which holds the number of the
physical block last recently referred. Internally it is used to
access the actual blockbuffer very fast during LOAD together
with the content of LASTBUF. It may be very helpful while
debugging the blockstream system!
LASTBLOCK or LINKLAST ( physblk -- last-physblk )
Vocabulary BLOCKSTREAM
Same as NEXTBLOCK, but in the other direction. See notes there.
LASTBUF ( -- addr )
Vocabulary FORTH
This is a global variable, which holds the address of the
blockbuffer last recently used by BLOCK. Internally it is used
to access the actual blockbuffer very fast during LOAD together
with the content of LASTBLK. It may be very helpful while
debugging the blockstream system!
LESS ( n -- )
Vocabulary FORTH
This will reduce the blockstream currently in USE by n blocks.
They will be taken from the end of the stream.
LINKLAST ( block -- lastblock )
Vocabulary BLOCKSTREAM
Synonym for LASTBLOCK.
LINKNEXT ( block -- nextblock )
Vocabulary BLOCKSTREAM
Synonym for NEXTBLOCK.
LOAD ( blocknumber -- )
Vocabulary FORTH
Loads the block with the given number. During load, this number
is contained in the USER-variable BLK. BLK always contains the
physical blocknumber loaded!!
LOADEREMIT ( char -- )
Vocabulary FORTH
Executes the content of the USER-vector ^EMIT. Parameters are
converted from F68K to a standard C-format. So the content of
must not be executed using EXECUTE, but only by using
LOADEREMIT. ^EMIT can take one of the addresses held in the
array EMITS.
LOADERKEY ( -- char )
Vocabulary FORTH
Executes the content of the USER-vector ^KEY. Parameters are
converted from F68K to a standard C-format. So the content of
must not be executed using EXECUTE, but only by using
LOADERKEY. ^KEY can take one of the addresses held in the array
KEYS.
LOADERKEY? ( -- flag )
Vocabulary FORTH
Executes the content of the USER-vector ^KEY?. Parameters are
converted from F68K to a standard C-format. So the content of
must not be executed using EXECUTE, but only by using
LOADERKEY?. ^KEY? can take one of the addresses held in the
array KEYS?.
LOADERR/W ( buffer blocknumber r/w-flag -- )
Vocabulary FORTH
Executes the content of the USER-vector ^R/W. Parameters are
converted from F68K to a standard C-format. So the content of
must not be executed using EXECUTE, but only by using
LOADERR/W. ^R/W can take one of the addresses held in the array
R/WS.
LOADERREADSYS ( addr count -- flag )
Vocabulary FORTH
Executes the content of the USER-vector ^READSYS. Parameters
are converted from F68K to a standard C-format. So the content
of must not be executed using EXECUTE, but only by using
LOADERREADSYS. ^READSYS can take one of the addresses held in
the array READSYSES.
LOADERWRITESYS ( addr count -- flag )
Vocabulary FORTH
Executes the content of the USER-vector ^WRITESYS. Parameters
are converted from F68K to a standard C-format. So the content
of must not be executed using EXECUTE, but only by using
LOADERWRITESYS. ^WRITESYS can take one of the addresses held in
the array WRITESYSES.
LOCAL ( n -- ) \ <n> LOCAL <name>
Vocabulary FORTH
Creates a datatype similar to 'VALUE', but only within a
colon-definition. The memory for the data is allocated on the
returnstack.
MAKE ( -- ) \ MAKE <name>
Vocabulary FORTH
This will create a blockstream with length 1 in store it into
the actual directory or in the directory given together with
<name>.
MAKEDIR ( -- ) \ MAKEDIR <name>
Vocabulary FORTH
This will create a subdirectory with the given name.
MORE ( n -- )
Vocabulary FORTH
This will extend the blockstream which is currently in USE by n
further blocks. They will be appended to the end of the stream.
MOUNT ( -- )
Vocabulary FORTH
This will initialise a blockstream system copied to a device.
For directories contain physical blocknumbers, they are not
portable to other devices, where other blocknumbers are used.
MOUNT makes the blockstream system fit to the actual device.
Note that only a system, that has been UNMOUNTed, can be
mounted. Always make sure that you are working with MOUNTed
systems only. Oterhwise malfunction is guaranteed. It is not
possible to doubly MOUNT a system.
NEXTBLOCK or LINKNEXT ( physblk -- next-physblk )
Vocabulary BLOCKSTREAM
Get the link to the next physical blocknumber within a
blockstream. This is necessary when using systemvariables like
BLK. In former times one could ind the preceeding block by 'BLK
@ 1-'. These times are gone. Now it is 'BLK @LASTBLOCK'. Note
that the physical number is returned. Words like BLOCK, LOAD or
BUFFER may not be applied to that number. The words (BLOCK,
(LOAD and (BUFFER have to be used instead. The logical
blocknumber during LOAD is held in the USER-variable SBLK. The
user should be very careful not to mix up BLK and SBLK.
NIP ( a b -- b )
Vocabulary FORTH
Discards the second element on stack (SOS).
ORDER ( -- )
Vocabulary FORTH
Prints the actual search order.
OUT ( -- addr )
Vocabulary FORTH
USER-variable, which counts EMITs. It is reset by CR. OUT is
used to realize TABs or conditional CRs.
OVER ( a b -- a b a )
Vocabulary FORTH
Copies the second element on stack (SOS) to the top.
PUSH ( addr -- )
Vocabulary FORTH
Saves the content of addr on the returnstack. It will be
restored with the next 'EXIT' or ';' statement.
Example:
: hex.
base push
$10 base !
. ;
QUIT ( ?? -- ?? )
Vocabulary FORTH
Invokes the outer F68K interpreter (for F68K is a native code
system, there is no inner interpreter at all). The returnstack
is reset and the datastack is tested for underflow.
R/W ( buffer blocknumber rwflag -- )
Vocabulary FORTH
Writes or reads 2048 bytes from/to the buffer from/to the block
with the given number on the mass storage device. R/W uses the
function stored in the USER-variable (R/W). This function
normally should use one of the loaders I/O-routines, which are
stored in the array R/WS. rwflag=0: read; rwflag<>0:write;
R/WS ( -- addr )
Vocabulary FORTH
Gives the address of an array, which holds the number and the
entry-addresses of the loaders I/O-functions to read/write a
block from/to the mass storage device. to the terminal. 'R/WS @
' gives the number of the functions. 'R/WS 4+ @' is the address
of the first routine. The routines in this table may not be
executed from F68K because they need some interfacing provided
by R/W.
R> ( -- n )
Vocabulary FORTH
Pops a value from the returnstack onto the normal datastack.
This value should have been pushed onto the returnstack using
'>R', because addressconversion and -reconversion is done in a
'>R'-'R>' pair. See '>R'.
R@ ( -- n )
Vocabulary FORTH
Copies a value from the returnstack onto the normal datastack.
This value should have been pushed onto the returnstack using
'>R', because addressconversion and -reconversion is done in a
'>R'-'R@' pair. See '>R'.
READSYS ( addr count -- flag )
Vocabulary FORTH
Reads 'count' bytes to the buffer 'addr' in a systemdependent
manner. READSYS calls a routine supplied by the loader.
READSYSES ( -- addr )
Vocabulary FORTH
Gives the address of an array, which holds the number and the
entry-addresses of the loaders I/O-functions to read from a
file in a system dependent manner. 'READSYSES @' gives the
number of the functions. 'READSYSES 4+ @' is the address of the
first routine. The routines in this table may not be executed
from F68K because they need some interfacing provided by
READSYS.
RESTORE_STREAM ( -- )
Vocabulary FORTH
Restores the actual streamcontext from the returnstack. So it
can only be used in conjunction with SAVE_STREAM on one
executionlevel (because of the returnstackmanipulation).
ROWS ( -- addr )
Vocabulary FORTH
Variable which holds the number of available rows on the
current terminal.
RP! ( returnstackpointer -- )
Vocabulary FORTH
Set a new returnstack. Possibly can be used in conjunction with
RP@. Handle with care!
RP@ ( -- returnstackpointer )
Vocabulary FORTH
Gets the actual pointer to the bottom of the returnstack. Note
that 'R@' cannot be replaced by 'RP@ @' because
addressconversion is done within 'R@'. It can be replaced by
'RP@ @ (ABSCODE)' doing the reconverion 'by hand'.
SAVE-SYSTEM ( -- )
Vocabulary FORTH
Uses the WRITESYS-function to create a new image of F68K, which
can be reloaded by the system-dependend loader.
SAVE_STREAM ( -- )
Vocabulary FORTH
Saves the actual streamcontext on the returnstack. This context
may be changes afterwards but has to be restored by
RESTORE_CONTEXT on the same executionlevel (because of the
returnstackmanipulation).
SAVEAREA ( addr count -- )
Vocabulary FORTH
Same as PUSH, but for an area of memory. count bytes from addr
will be saved on the returnstack and be restored with the next
'EXIT' or ';' statement. It is used within 'EVALUATE' in order
to save the content of TIB.
SBLK ( -- addr )
Vocabulary BLOCKSTREAM
This is a USER-variable which contains the number of the
logical block actually loaded. Use SBLK instead of BLK when
working with blockstreams.
SPACE ( -- )
Vocabulary FORTH
Emits the constant BL =$20 to the terminal.
Example:
: space ( -- ) bl emit ;
SPACES ( n -- )
Vocabulary FORTH
Executes SPACE in a loop.
STREAM: ( -- ) \ STREAM: <name>
Vocabulary BLOCKSTREAM
Defining word for streamstructures. One example is USESTREAM
used by USE.
Example:
STREAM:XX
XX " TEST" ?BUSE
STREAMS ( -- )
Vocabulary FORTH
Invokes the blockstreamsystem. Now BLOCK, LOAD and BUFFER
access the logical blocks within the system. The variable
ROOTBLK is cleared and must to be changed!
TAB ( n -- )
Vocabulary FORTH
Moves the cursor to column n in the actual line. TAB uses OUT
to count the cursors position.
TOSS ( -- )
Vocabulary ONLY
'TOSS' is a 'DROP' for the vocabulary-stack. It will often be
used in conjunction with 'ALSO', which increases the
vocabulary-stack.
UNLOOP ( -- )
Vocabulary FORTH
UNLOOP is used when it is necessary to EXIT a word from within
a DO-LOOP-structure.
Example:
...
DO ...
IF unloop exit THEN ...
LOOP ...
UNMOUNT ( -- )
Vocabulary FORTH
This will prepare a blockstream system to be copied to an other
device. From all physical blocknumbers contained in the system,
the number of the first physical block on the actual device
will be substracted. A later mount will reverse that procedure.
Always make sure that you are working with MOUNTed systems
only. Otherwise malfunction is guaranteed. It is not possible
to doubly UNMOUNT a system.
USE ( -- ) \ USE <name>
Vocabulary FORTH
This will open the blockstream with the name <name> for usage.
A STREAM: structure called USESTREAM will be filled with the
data of the selected stream. Notice that if a blockstream has
been created with MAKE, then USE must be supplied additionally.
USER ( -- ) \ USER <name>
Vocabulary FORTH
Creates a new USER-variable with the given name.
USESTREAM ( -- addr )
Vocabulary BLOCKSTREAM
This is a structure created with STREAM:. It is used by USE.
VALUE ( n -- ) \ <n> VALUE <name>
Vocabulary FORTH
Creates a VALUE-type. VALUEs can be read out using <name> and
be written using 'TO <name>'.
Example:
27 VALUE X
X . --> 27
35 TO X
X . --> 35
VER ( -- n )
Vocabulary FORTH
Gives the BCD representation of the date the kernel has been
created. E.g. if the kernel has been assembled on 6-12-91 the
following sequence will yield '19910612':
Example:
HEX VER .
\ This makes it possible to compare
\ different versions using
\ standard operators like < or >.
VIEW ( -- ) \ view <name>
Vocabulary FORTH
Looks for the source of the word with the given name. The
appropriate screen will be displaid.
Example:
view view
VOC' ( -- vocaddr ) \ voc' <vocname>
Vocabulary FORTH
Finds the address of the named vocabulary. This may be very
usefull together with searching word VOCSEARCH. The returned
address corresponds to those held in CURRENT or CONTEXT.
Example:
" DUP" voc' forth vocsearch
VOCABULARY ( -- ) \ VOCABULARY <name>
Vocabulary FORTH
Defines a new vocabulary with the given name. The vocabulary is
created only, it is not located within search order. The use of
vocabularies is highly recommended in order to keep the number
of words in the main vocabulary FORTH as small as possible.
VOCS ( -- )
Vocabulary FORTH
Prints a list of all vocabularies.
VOCSEARCH ( string vocaddr -- controlword cfa | string -1 )
Vocabulary FORTH
Searches for a word with the name given with the string in the
specified vocabulary. The address of the vocabulary comes from
CONTEXT, CURRENT or VOC'. VOCSEARCH is used by (FIND.
Example:
" DUP" voc' forth vocsearch
WRITESYS ( addr count -- flag )
Vocabulary FORTH
Writes 'count' bytes from the buffer 'addr' in a
systemdependent manner. WRITESYS calls a routine supplied by
the loader, which is stored in the USER-variable ^WRITESYS.
WRITESYSES ( -- addr )
Vocabulary FORTH
Gives the address of an array, which holds the number and the
entry-addresses of the loaders I/O-functions to write to a file
in a system dependent manner. 'WRITESYSES @' gives the number
of the functions. 'WRITESYSES CELL+ @' is the address of the
first routine. The routines in this table may not be executed
from F68K because they need some interfacing provided by
WRITESYS.