Copyright 1984 by ABComputing July 15, 1984 ÉÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ» º Venture FORTH º º º º by º º º º Guy M. Kelly º ÈÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍͼ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Introduction ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Hello again! I've been considering how best to proceed with this column, and unless there is negative reader feedback I have decided to cover topics and material that will both supplement and complement several available books on FORTH. The texts "Starting FORTH" by Leo Brodie and "FORTH Tools, Vol. 1", mentioned in PCFL/PCUG issue 2, will be referred to as we proceed. The public-domain executable FORTH program, also provided in PCFL/PCUG issue 2, will be used to run our FORTH examples and programs. Unless otherwise specified, whenever we mention the FORTH environment we will be referring to the traditional FORTH environment. Non-traditional FORTH environments (which will be covered in more detail later in this series) can be used within other operating systems, can emulate standard operating systems, can use standard-named file systems, can support calls to and from other programming languages, and can do all the other "normal" things done in the world of programming. Onward! ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Program Writing ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ In my last article, we emphasized the environment in which the traditional FORTH programmer worked. Some of the elements in the environment will be familiar to those who have used, or are using, interactive languages like APL, BASIC, or LISP. Those more familiar with "batch" languages such as Ada, Assembler, C, FORTRAN, or Pascal will find several distinct differences. When writing a program, you probably would consider three general time periods. ÉÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ» 1. º Planning º ÈÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍͼ Decision time: what the program is to do and how it should be done. ÉÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ» 2. º Implementation º ÈÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍͼ Working time: writing, editing, compiling, debugging, ad-nauseam. ÉÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ» 3. º Maintenance º ÈÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍͼ Support time: documentation (which should have been started at time 1), manuals, and field support (the other 90%). We all know that periods 1 and 3 are not only very important, but can have a dramatic impact on the time and effort expended during period 2. We all know it, but unfortunately many of us start (and end) our programming in period 2. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ The Implementation Period ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Let's first address what we do, and not what we preach. The implementation period is divided into several consecutive steps. In order, they will be: coding, editing, assembling/compiling (hereafter referred to as compiling), linking/loading (henceforth known as linking), and debugging. For anything more complex than a null definition (which corresponds to a procedure that returns when called), the edit-debug loop is required more than once, and it is important to iterate the loop rapidly (just a little computerese). Let's look at these steps in more detail. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Coding ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Write it down on paper. (Fortunately, these days keypunching is rare, so most of us only have to do this once). ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Editing ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Enter the code into the computer via an editor. In a "batch" environment this means invoking the operating system, having it load the editor program for us, and having the editor open the appropriate text file for editing. If the file is a large one, and we only want to change a single character, we still must process the entire file and save the updated file. Most editors will automatically relabel the old file as a backup and give the new copy the original file name. In the traditional FORTH environment, the editor is on-line as part of that FORTH environment, so we do not require that the operating system invoke the editor. Also, since a traditional FORTH system reads files in 1K blocks, the editor does not have to open the entire source file. We tell the editor which 1K block of code the offending character is in, edit that block, and save the modified block in the original file. What are the trade-offs in this approach? The "batch" method is simple for the applications programmer (but internally complex!) and slow, while the FORTH method is FAST (and internally simple), but requires that the programmer know in which 1K block of disk storage the character resides. This sounds terrible, but is little more onerous than knowing where in the source listing the character is (also a requirement for the "batch" mode). (EDITOR'S NOTE: Traditional FORTH displays screens consisting of sixteen 64 character lines. As 16 times 64 = 1024 = 1K, knowing the screen number in which a character occurs is equivalent to knowing the block number.) ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Compiling ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Again, in the batch mode we request the operating system to load the compiler, point the compiler to the program source code, and turn it loose. The compiler will crunch away on the source code and generate an absolute (or a relocatable) object-code file, and print a listing or generate a list file. The listing (or list file) will contain a variety of items, including a symbol table and perhaps a cross-referenced symbol list, and certainly (?) a list of the syntactical errors that it found. In the traditional FORTH environment, compilers are on-line (as well are the interpreter and the assembler) so we bypass the steps of invoking the operating system, having it load the compiler, pointing the compiler at the source file, etc. We need only to type "n LOAD" (where n is the starting number of a set of 1K blocks of source code on the disk). As an aside, except for a few tracks of self-booting object code, the entire FORTH disk contains nothing but source code. There are no .COM files, no .EXE files, no .REL files, no .TXT files ad infinitum. There are, in fact, no files at all, just numbered 1K blocks of disk storage which normally contain FORTH source-code modules but can contain anything that the programmer desires. Now for some trade-offs. In the "batch" environment, the compiler grinds through the entire source file, independent of the number of errors encountered, trying to do something - anything! - until it hits an "end" statement. For a big source-file this can take a while. (Most "modern" languages use source-code modules to eliminate this and other batch problems). FORTH, on the other hand, will terminate compilation on the first error encountered, flag the error (and in some systems automatically enter the edit mode with the cursor on the offending source-code statement) and wait for the programmer to correct the error. The programmer can either interactively debug the code successfully compiled up to the point at which the error occurred or, more likely, continue the compilation from the point at which the error occurred. The process does not have to be restarted from the beginning! The result is (as in BASIC, etc.) that the correction process incurs almost no lost time. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Linking ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ In the "batch" environment, we again return to the operating system, use it to load the linker, point the linker at the relocatable object-file, etc., which is another time-consuming and error-fraught process. FORTH does not mess with such illogical processes. The source-code file is available and FORTH uses it; hence, no relocatable files are necessary. Horrors! you say? Well, FORTH will typically compile source-code modules faster than most linkers will link relocatable object-modules, so there is no advantage to having the extra complexity and overhead. Also, the problem of updating the source code and forgetting to update the relocatable module is eliminated since there aren't any old (or new) relocatable modules to worry about. 'Nuff said. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Debugging ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Here we go again, folks: it's back to the operating system to load the debugger, so the debugger can load the object code, so we can (yawn) finally proceed to testing our program. At least these days most debuggers support symbolic debugging. (If you have to ask what this means, be glad that you don't know!) Ok, we now have the debugger, the symbol table, and the program in memory. How do we proceed with the debugging process? How do we feed data, parameters, and all that good information to the various program modules and subroutines? Well, folks, until a significant part of the program is running, we must use the debugger to load registers, set breakpoints, and all that sort of messy stuff because, the program either doesn't have an interactive (console) capability or it hasn't been debugged to that point yet. (Sound a little like catch 22?) FORTH is its own debugger. Parameter-passing is uniformly done via a parameter stack (which is separate and distinct from the return stack!), or via named-global variables. Symbolic debugging has always been a natural part of the FORTH environment, and any single "subroutine" (a FORTH word) is known to FORTH by its name. This means that the smallest of program modules or procedures can be tested individually and interactively without having any of the other modules present or working. Well, it almost means that. Of course, if you are trying to test a module which requires that a complex data structure exists, you must be able to create and inspect that data structure. But, FORTH allows you to do just that; in fact, FORTH supports the creation of any data type you can imagine by providing the capability of building custom compilers and defining words which are then used to create the required data types. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Recapitulation ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ We began this discussion by stating the desire to iterate the edit-debug loop as fast as possible. This would keep our attention focused on the programming problem and not distract us with the process of using the operating system and the various separate files (editor, compiler, linker, debugger) that must be loaded and invoked to create a variety of other files. (THING.SRC, THING.BAK, THING.REL, THING.OBJ, THING2.OBJ, THING.BAT) The time required to complete the loop, and to manage the process, can take from minutes to hours! The equivalent process in FORTH takes from seconds to minutes! It's not that FORTH doesn't have the same loop (editor, compiler, debugger). It does (well, almost; it traditionally skips the linker process), but the steps are done much faster, and consequently much more often. Less time between iteration means greater concentration and much more productive use of programming time. Another consequence of the rapidity and ease of the process is that a programmer is willing to try various possibilities, investigate a wider variety of possible data structures, and do a more careful design and debug of each of the modules. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Numbers ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ As an example, the complete source code for the FORTH operating system, editor, assembler, debug utilities, the interpreter and compilers, in total about 130K, can be compiled in less than three minutes! ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Caveats ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ There are a variety of trade-offs that make the FORTH environment have the characteristics it does. From the point of view of an operating system, FORTH is primitive but powerful. It has no named files, hence the burden of managing 1K disk-storage blocks is placed upon the programmer. Most programmers create a simple block-management system to aid in the process. The idea is that FORTH does not impose a particular scheme upon anyone; you are free to use none or to pick the one that is optimum for your purposes. Also, there is no directory to destroy and hence lose all those files that you know are still intact on the disk. Even while editing, the worst mishap will normally corrupt only a 1K block of source code. As a language, FORTH is: compact; modular; structured; transportable; extensible; incorporates an interpreter, assembler, and compiler; supports any data types; has virtual memory; has batch and auto-execute modules; has vectored I/O (I/O re-direction); has real-time processing; and many versions are multitasking. But, most of the facilities incorporated into the language are available as primitive functions - simple but powerful general purpose primitive operations which are used to build more complex and powerful operations. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Case Statements ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Most FORTH systems are not supplied with a case statement or an array function, and only supply a few string primitives. They could be provided, but the FORTH community has not decided upon a standard set for inclusion with all systems. Consider the case statement. A contest was run to select "the best case statement," and almost an entire issue of the magazine "FORTH Dimensions" (Vol.2 No.3) was devoted to publishing the results. If you want a case statement, pick the one among the many in that issue which best matches your needs. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Arrays ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ The situation with arrays is similar. There are so many ways to create and structure arrays, and arrays are so easy to code (typically requiring a line or two of high-level FORTH or FORTH assembly code) that the choice is left to the programmer. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ String Processing ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ String handling is similar. A variety of string packages have been published and are available for use; pick the one you like. FORTH words (procedures) usually pass all parameters via a parameter stack. This provides a uniform method of parameter passing and makes the procedures reentrant, but requires that the programmer manage the stack. Consequently the programmer is burdened with a task normally managed internally by other high-level languages, and a variety of low-level stack operations are found embedded into much of the high-level FORTH code. This accounts for a great part of the reputation of FORTH as being a "write-only" language. Several papers have been published in the FORTH community addressing this problem and one of the recent trends has been toward the use of "local stack variables". The San Diego group, in particular, is doing extensive work on the problem. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Speed ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Since there are no commercially available native FORTH machines, almost all versions of FORTH run on a 16-bit stack-oriented virtual machine, implemented via a set of fast and compact machine-code routines. Consequently, high-level FORTH runs on many processors at about one-tenth (1/10) the speed of assembly language, and about 10 or more times faster than most high-level interpreters. FORTH assembler words execute, of course, at machine speed, but, because of the extensive use of the parameter stack for communications between words, and because of the overhead of the virtual machine in threading from word to word, many native machines run FORTH assembly code at about 1/2 the speed of equivalent modules written in standard assembly, running in a non-stack (register oriented) environment. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Crashing the System ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Because FORTH was originally developed when hardware was expensive, the amount of memory was small, and the tasks to be accomplished were real-time control and data handling, the typical FORTH environment has very little built-in protection. It is quite possible for a programmer to crash the system with a key-stroke and in fact at least one company stated that a programmer learns not to crash the system faster with no protection than with only partial protection. Again, this is one of the trade-offs that must be made in the FORTH environment. The FORTH programmer is given total access to the hardware and therefore has total responsibility for system protection. This is not the same as saying that an application written using FORTH is crashable. Indeed, a running FORTH application is fully as robust as any other application program. In fact, because of the weak coupling between FORTH modules, and because of the ease of thoroughly testing each FORTH module, the typical application is quite robust. (Please don't remind me of "Easy-Writer I".) This claim is made on the basis of my own and other programmers' experiences with a variety of large commercial applications. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Documentation & Project Control ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Other areas of concern in a FORTH programming environment are those of documentation and programming teams. FORTH has peculiar requirements which must be considered if successful documentation and project control is to be maintained. These have been addressed in the literature and if properly handled are as successful as the techniques used with other languages. My experience in the matter of programming teams has been quite interesting. Because it is so easy to crash the system when debugging FORTH, a special protocol has to be implemented among active programmers. At its simplest, when about to try any potentially dangerous FORTH word, a programmer should ask other programmers on the system to save their current editing screen (1K source). This would be catastrophic in most other environments, but in the FORTH environment, it is a very minor inconvenience because an edited screen can be saved and restored in less that 2 seconds, using 2 key-strokes, and because a crashed system can be re-booted in only a few seconds. The simple and effective parameter-passing scheme and the weak coupling between modules, insures that each member of the programming team can do an immense amount of productive work before the system is integrated. My experience has been that system integration is simpler and requires much less time than is normally required in other high-level language environments. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Finale ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ The trade-offs made in designing FORTH have produced a unique and powerful environment which has a particular mix of high-level and low-level features. Many of them may seem strange and puzzling to a beginner, but there were reasons for these choices. Furthermore, because FORTH is an evolving language, and because of its extensibility and the ease with which it can be internally modified, these reasons are continually being challenged, tested, and either reaffirmed or abandoned. In fact, FORTH can and should be exactly what is required for your own needs and circumstances. As an illustration, the version of 83-Standard FORTH I included in the previous issue of PCFL/PCUG was tailored to be used in a teaching environment. It also happens to be an effective version for solving a variety of useful programming problems and is the only version I know of which has a simple, interactive, incremental meta-compiler which is used to create a new FORTH version building from an earlier version. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ File Name: ÛÛ forth1.txt ÛÛ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ