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: VALUE LOCAL 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 -- ) \ :LIST ['] PRINTER IS PARSER LOAD ['] INTERPRETER IS PARSER ; or to show extended use of DEFER-words: : :LIST ( 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 This will create a blockstream with length 1 in store it into the actual directory or in the directory given together with . USE ( -- ) usage: USE This will open the blockstream with the 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 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: 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: 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: INSERT This will insert blocks at the position in the blockstream. EXPEL ( where amount -- ) usage: EXPEL This will cut blocks out of the current blockstream. The block 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 This will create a subdirectory with the given . DELDIR ( -- ) usage: DELDIR 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 or CD This will change to the directory given in . If no 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 Shows the row . has to be given in BASE 25. So there is one digit only. The appropriate numbers are shown by LIST. C ( -- ) \ C Selects the column (decimal). This will be the actual writing position. $ ( -- ) \ $ ~ Write the 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 Delete Multiple; Deletes characters at the position given by R and C. DL ( -- ) \ DL Delete Line; Deletes the line . has to be given with BASE 25. IL ( -- ) \ IL Insert Line; Inserts a line at line (BASE 25). GL ( -- ) \ GL Get Line; Copies the given line (BASE 25) into special buffer. PL ( -- ) \ PL 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 L to edit the block 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 . 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 --> where is one of the internal words of the editor that do the real work, and 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 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 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 - 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 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'] ... ; 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 or CD Vocabulary FORTH This will change to the directory given in . If no 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 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 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 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 blocks out of the current blockstream. The block 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 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 blocks at the position 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 -- ) \ LOCAL 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 Vocabulary FORTH This will create a blockstream with length 1 in store it into the actual directory or in the directory given together with . MAKEDIR ( -- ) \ MAKEDIR 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: 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 Vocabulary FORTH This will open the blockstream with the 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 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 -- ) \ VALUE Vocabulary FORTH Creates a VALUE-type. VALUEs can be read out using and be written using 'TO '. 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 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' 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 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.