Topics covered in this tutorial: * About this tutorial... * Running examples * Setting up your own Vocabulary Help * Setting up your own vocabulary * Using DOS/AREXX macros * Setting up HeliOS command macros * A useful command for use in macros: EDIT * Another useful command for use in macros: RUN * Using HeliOS_cmp * Getting user input * Setting up text and graphic output * A little more about HeliOS text streams * HeliOS Variables * HeliOS Constants * More about the concepts of "Run-time" and "Compile-time" * The Dictionary pointer * Using "CREATE" * Run-time initialisation of data structures within Dictionary memory * Easy ways of setting up text strings in HeliOS * The "Code Field Address", or "CFA" * Vectored Execution * Error handling routines * Using the Return stack * Using HeliOS includes * Calling Amiga libraries * Setting up loops in HeliOS * Counted Loops * Conditional Loops * Infinite Loops ---------------------- About this tutorial... ---------------------- By now, if you have followed all the preceding tutorial material, you should be fairly confident about writing small-scale HeliOS programs. Some people like to build upon their own ideas and go on from here to to construct their own programs, learning the language as they go. Other people like to work by copying and modifying existing programs, which is a also good way of learning more advanced techniques. As you will have seen, HeliOS provides the source code for several example programs, and it is a good idea to look at these to see how larger scale HeliOS programs can be constructed. Of course you may prefer to adopt a very different programming style: HeliOS is very flexible, and there are no fixed rules when it comes to program design. If you want to follow on from the example programs provided with HeliOS there is full source code available for complete large programs such as the HeliOS "Defender" arcade game. Whichever method of progress you choose, we cannot go too far along the learning path with you, for the simple reason that HeliOS is deliberately very flexible, and Amiga programming has so many facets that it would be quite impossible and undesirable to lay down fixed guidelines. Nevertheless, there are a few important techniques, ideas, and points of interest which we can describe here, which will perhaps help to speed up your progress in the early stages. This tutorial is devoted to a brief discussion of various topics which you will hopefully find interesting, and which you can build upon in your own work by reading the more detailed documentation in the "Dictionary.doc" and elsewhere. The sections of this tutorial have no particular natural order, and are presented as a mixed bag of stimulating ideas: if you yourself have further ideas which you think would benefit other HeliOS programmers, please let us know so that we can include them in future versions of the tutorials. Remember that the techniques and ideas mentioned here are only a selected few of the ways you can use HeliOS. It is a good idea to read the HeliOS "Dictionary.doc" file very carefully, since it describes many functions and methods which you will find useful in particular circumstances. ---------------- Running examples ---------------- This tutorial contains many shorts sections of example code. Remember that you can easily run these examples from the editor by simply highlighting them and pressing . Note that many short example phrases have associated comments to the right of them, often using a preceding "->" symbol. These "->" symbols, and the following comments, are merely intended to indicate what the code fragment is doing, and should not be included in highlighted sections of code to be run-tested: if you do try to run these comments, you will merely get an error. ----------------------------------- Setting up your own Vocabulary Help ----------------------------------- The Vocabulary Help system is intended to be easy for you to use yourself to create your own notes on any HeliOS function. Place the cursor over the word "OpenScreen" below and press to see the brief Vocabualry Help available on that HeliOS command word. OpenScreen Notice that only a stack diagram is given, with no descriptive notes: this is because the Vocabulary Help system is intended to give very short quick help to which you can easily add your own notes as required by editing the Vocabulary Help file in one of the HeliOS editors. Each individual has their own needs when it comes to help and reminders on various functions. It is really easy to add your own help notes, so do not hesitate to start making your own additions to the help file. As you will have seen, the Vocabulary Help file provided as a starting point merely displays a stack diagram, and this leaves plenty of spare space for any text comments you wish to add. The Vocabulary Help file itself contains instructions on how to add your own help text: just load this file into one of the HeliOS editors and read the instructions given in the file. The Vocabulary Help system is intended for brief quick "memory jogging" help, rather than detailed texts, and allows two short "pages" of help text which are quick to read and access: do not write an "essay" for Vocabulary Help, but give information which can be taken in at a glance. We recommend that you become familiar with this system early in your HeliOS programming so that you get maximum benefit during the learning period when help is most required. You will also find that the very act of creating your own help texts will in itself help you learn much relevant information. ------------------------------ Setting up your own vocabulary ------------------------------ You will probably soon find that you have created a nucleus of useful HeliOS functions which you like to use in all your programs, and you may even create separate "toolkits" of functions for different applications. You COULD manually load your special vocabulary each time you start HeliOS, or you COULD cut and paste this vocabulary into each of your program source code files, but this is rather tedious. It is easy, in fact, to include any special vocabularies by simply using "RUN" to load the relevant source code file at the start of your program. You can RUN as many separate external source code files as you wish from within any source code file, and you can also RUN source code files which themselves RUN other files: the system is very flexible and easy to use. Here is an example showing how to use RUN to compile an external source code file from within any program: RUN HeliOS:Source/MyOwnVocabulary.src The above line could be included anywhere in your source code, but would usually be placed immediately after the FORGET **CORE** line with which most programs begin. In fact the start of your source code is probably where you would want to initialise all of the general environment for your program, so you might have a typical program start something like this: FORGET **CORE** AMIGAINCLUDE HeliOS:HeliOS_AmigaInclude USERINCLUDE HeliOS:HeliOS_UserInclude DOSCMD $Assign MyProgram: HeliOS:MyProgram$ RUN HeliOS:Source/MyOwnVocabulary.src RUN MyProgram:MyProgramToolkit.src etc. etc. ---------------------- Using DOS/AREXX macros ---------------------- You will have noticed that the HeliOS editors have a macro recording system which allows you to record DOS/AREXX macro command lines and execute them via hot-keys for easy and instantaneous operation. These macros can only be created in the editor environment, but you should be aware that they can be executed both in the editor AND the interpreter environments. This useful facility allows you to make use of instant hot-key DOS or AREXX commands in the interpreter as well as the editors. Note that ordinary editor macros cannot be run from the interpreter: only DOS/AREXX macros have this facility. See the "DOSandAREXX_Commands.doc" for more details -------------------------------- Setting up HeliOS command macros -------------------------------- You can attach a HeliOS command line to a hot-key, and this allows you to run any HeliOS program at the touch of a key. This can be useful if you wish to use a certain command sequence often: for example you may like to attach the "FORGET **CORE**" command to a hot-key. To do this you must create a special form of DOS/AREXX macro. If you want to set up a DOS/AREXX macro command line to be treated as a HeliOS command line you must place a special code at the start of the line. There are two possible codes, "H>" and "E>", which HeliOS interprets in slightly different ways. 1. Using H> Placing H> at the start of the DOS/AREXX command line will cause HeliOS to execute that line as HeliOS code in a full interpreter "session", just as if that line had been entered at the interpreter command line. For example, setting up this line as a macro: H> FORGET **CORE** will produce a macro command which behaves exactly as if you had typed "FORGET **CORE**" at the intepreter command line. 2. Using E> Placing E> at the start of the DOS/AREXX command line will cause HeliOS to execute that line as HeliOS code without registering a full interpreter "session". The code will simply be executed without all the extra business of the session log etc. For example, setting up this line as a macro: E> FORGET **CORE** will produce a macro command which executes apparently in the background, with no visible effect but still doing its job of clearing the dictionary. See the "HeliOS_Macro_Commands.doc" for more details. ---------------------------------------- A useful command for use in macros: EDIT ---------------------------------------- You might like to set up macros, as explained above, to edit certain files which you use very often. To do this you can use the very useful EDIT command, which allows you to auto-load any file into a HeliOS editor. Look at this macro command line, for example: E> 3 EDIT S:Startup-sequence This will load the "Startup-sequence" file into HeliOS Editor3. Here is how the command works: E> 3 EDIT S:Startup-sequence ^ ^^^^ ^^^^^^^^^^^^^^^^^^ | | | | | File name | | | Edit command | Which editor you want the file to be loaded into If there is already unsaved text in the specified editor you will get a warning, giving you the opportunity to save the old text first. --------------------------------------------- Another useful command for use in macros: RUN --------------------------------------------- You might like to set up macros, as explained above, to run certain HeliOS programs, and to do this you can use the very useful RUN command. For example: F> RUN HeliOS:Source/MyUsefulProgram Using this method you could, for example, attach particular sets of your own "Toolkit" commands to each Function key, and thus you could customise your HeliOS environmemt at the touch of a key. The fact that you can attach literally ANY HeliOS program to a command key gives you limitless scope to customise your own programming environment. ---------------- Using HeliOS_cmp ---------------- The HeliOS_cmp program can be used as a straightforward stand-alone HeliOS compiler, or as a versatile method of testing any HeliOS program. The latter option is very useful provided that you observe the constraint that the full interpreter environment will not be available, so you cannot run software which uses facilities only present in the full interactive HeliOS system. Remember also that source code run from HeliOS_cmp will need to contain explicit commands to load whatever include files are required, whereas in the full interactive system include files are usually permanently on-line. HeliOS_cmp can be used in various different ways, and it is worth looking at these in this tutorial: 1. As a compiler of stand alone programs for use with HeliOS_exe. In this case you are interested in generating a compiled code file, but not a compiled vocabulary module as used in the full interpreter. You would use the "-v" command line option to prevent generation of a full vocabulary module. You would use a command line using "-v" something like this: HeliOS:HeliOS_cmp HeliOS:Source/MyProgram Ram:MyProgram -v ^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^ ^^ | | | | Compiler program Source code file Target file Specifies no (Optional) vocabulary module Note that if you do not specify a target file the compiled file will be given the same name and path as the source file with a ".cmp" suffix. The HeliOS_cmp program will open a 4-colour screen and present you with full compilation text output and error messages. In this case a final end-of-compilation message and free bytes statement will apppear. 2. As a compiler of programs to be "preloaded" into the interpreter. In this case you need to generate a compiled code file and a compiled vocabulary module to be used by the full interpreter. You would NOT use the "-v" command line option, but otherwise the command line format would be just like the previous case. HeliOS:HeliOS_cmp HeliOS:Source/MyProgram Ram:MyProgram ^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^ | | | Compiler program Source code file Target file (Optional) Note that if you do not specify a target file the compiled file will be given the same name and path as the source file with a ".cmp" suffix. The vocabulary module will have the same name but with a "V" suffix. The HeliOS_cmp program will open a 4-colour screen and present you with full compilation text output and error messages. In this case a final end-of-compilation message and free bytes statement will apppear. 3. To test run programs without generating any output files In this case want to simply compile a program and you are only interested in seeing the program run, not in generating compiled code. To do this you specify the command line option "null", as follows: (Note that the "null" can be in upper case (e.g. "NULL") if you wish) HeliOS:HeliOS_cmp HeliOS:Source/MyProgram null ^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^ ^^^^ | | | Compiler program Source code file Specify NO output files In this case you will probably want a visual environment for your program as close as possible to the setup of the full HeliOS interpreter. The HeliOS_cmp program will try to simulate the interactive HeliOS system display by by opening an 8-colour screen and setting its colours to your existing saved HeliOS system default colours. With this option the final end-of-compilation message and the free bytes statement will not apppear. ------------------ Getting user input ------------------ The Amiga allows many methods of gathering user input, and most of them are rather complex to set up and administer. Probably the most common, flexible, and system friendly method is to use an Intuition IDCMP (Intuition Direct Communication Message Port) which will gather user input from an active Intuition window. This can still be a very tedious process, but you are quite at liberty to use this method within HeliOS if you wish. However, the good news is that HeliOS provides a sophisticated automatic IDCMP handler which is ridiculously easy to set up and use. You can read more about this in the dictionary documentation, but here we will give a simple description of how to set up the HeliOS IDCMP handler. First of all, you must understand that the HeliOS IDCMP handler can only be used in conjunction with an Intuition window, which is fairly obvious since it employs an Intuition IDCMP! Next you must remember that there can be only one HeliOS IDCMP handler in operation at any particular time. When HeliOS starts up it always opens an IDCMP handler and attaches this to the HeliOS startup window: you do not have to do anything at all to set up this initial facility. Subsequently you can reset the HeliOS IDCMP handler to get user input from any window which your program opens, and you can switch easily and quickly between as many different windows as you wish. Finally, when you have finished, you should return the IDCMP handler to the HeliOS window. You do not need to worry about finally closing the HeliOS IDCMP handler, because it will be closed automatically when HeliOS itself closes down. To set the HeliOS IDCMP handler to get input from a new window you have just opened, you would say something like: MyNewWindow D@ MAKEINWINDOW The simple MAKEINWINDOW command is all that is required to set up user input from any window. To restore user input to the main HeliOS window you should use the single word: FORTHINWINDOW Remember that you MUST transfer the HeliOS IDCMP handler back to the HeliOS window before closing the other window you have been using. It really is that simple to set up the HeliOS user input handler! Of course, having attached the HeliOS user input handler to a window, you still need to know how to check for user input. This is very easy indeed, and is most often done using the word "KEY", which will halt program execution and wait for ANY user input. As soon as KEY finds some user input to report it "wakes up" and returns a code on the stack defining just what happened. If it was a simple key press you will receive an ASCII code, if it was another input event such as a gadget you will get a special code informing you just what happened. If you need further information about the nature of the event, you can get more details by inspecting special system variables such as RAWKEY, QUALIFIER, GADGETNUMBER, MENUNUMBER etc.. Of course, you may not want your program to go to sleep while waiting for user input, and in this case you can use the word "?TERMINAL", which tests for user input and returns at once with a user input code on the stack, or "null" if nothing is happening. It is important to become familiar with the use of MAKEINWINDOW, KEY, and ?TERMINAL right from the start of your HeliOS programming. This system is so easy to use that you will very soon become fluent in writing code using the simple input words described: there are more sophisticated tools available within HeliOS, but you will not need these to start with. Here is a short example which shows the use of KEY. : TESTKEY SCRCLR 5 4 CURPUT ." Press any key to see ASCII code returned by KEY ->" 5 6 CURPUT ." Press to quit" BEGIN KEY \ Get KEY 56 4 CURPUT \ Set cursor position 6 FPENSET \ Set colour to red DUP . \ Duplicate and print ASCII code 32 = \ Check if it was UNTIL SCRCLR ; TESTKEY See the "Dictionary.doc" and example programs for more details. ---------------------------------- Setting up text and graphic output ---------------------------------- In the previous section you saw how easy it was to set up user input within any window using MAKINWINDOW. The good news is that equally simple and rather similar methods are available for setting up text and graphic output in windows. To set up text output you use the function "MAKEOUTWINDOW", and to set up graphical output you use "MAKEGFXWINDOW". So, if you wanted to make a newly opened window active for user input, text output, and graphics output, you might say: MyNewWindow D@ MAKEGFXWINDOW MyNewWindow D@ MAKEOUTWINDOW MyNewWindow D@ MAKEINWINDOW Remember again that you MUST transfer the all HeliOS functions back to the HeliOS window before closing the other window you have been using. You saw in the previous section how to close user input by returning the HeliOS IDCMP handler to the HeliOS window, using "FORTHINWINDOW". The method of closing down text output is like that used to close the HeliOS IDCMP handler, except that you use the function "FORTHOUTWINDOW". The method of closing down graphic output is slightly different, and in this case you need to use two words: FWINDOW MAKEGFXWINDOW Once you have set up text and graphic output in your window you can use a whole range of convenient pre-coded HeliOS functions: HeliOS has many powerful text functions associated with its system of ten text streams and a complete set of graphic rendering functions. See the "Dictionary.doc" for more details. --------------------------------------- A little more about HeliOS text streams --------------------------------------- HeliOS text output, as you have seen in the earlier tutorials, is sent through a series of text streams which can be used to remember many characteristics of a text window. It is worth looking at the "Dictionary.doc" to see the wide range of text control functions available in HeliOS, but there are so many of these that you will probably find it easiest to learn them slowly as you need them. However, the use of the words STREAM and STREAMNO is worth describing at this point, because they are fundamental to using text streams. The word STREAMNO returns the number of the current text stream, and is very useful when you need to remember the old stream while jumping on a temporary basis to another stream. You can use code something like this: SCRCLR ." Here we are, starting off!" STREAMNO \ Get current stream number onto stack 8 STREAM \ Jump to new stream - number 8 10 10 60 12 SCRWIN 7 BPENSET 6 FPENSET SCRCLR ." Here we are in Stream 8!" CR CR ." We were in Stream " DUP . \ Duplicate original stream number in stack CR CR ." And now we are going back!" STREAM \ Jump back to original stream 1 20 CURPUT ." Now we are back!" CR CR ." Press to continue." WAITSPACE SCRCLR Try running this section of code. The word STREAM must be preceded by a stream number which must lie in the range 0-9 to select one of the ten HeliOS text streams (note that if you specify a number greater than "9" HeliOS will use a value of "9" internally by default). Note, by the way, that the text stream system is unique to HeliOS and is NOT part of the Amiga operating system: it USES the Amiga console device but it is not part of the standard Amiga console device functionality. Once you have defined the current text stream, HeliOS will allow you to set up a text window (using the SCRWIN function) and define text styles, cursor position etc.. Then you can change streams and set up another text window with different styles etc. Subsequently, simply evoking one of your preset streams will restore all text parameters to the ones defined for that stream. Note that text stream windows are NOT the same as Intuition windows, and you can have ten different text stream windows within a single Intuition window. When organising text within a window it is often desirable to have some lines of text at odd pixel offsets from other text: in other words, to have text lines not dictated in terms of font dimension displacement. This is easy to set up with HeliOS streams, and you can easily have two adjacent text streams with a single pixel difference in either vertical or horizontal alignment if you wish. To change pixel alignment of a text window you use the "SETCONORIGIN" command, immediately before performing a SCRWIN operation. The SCRWIN function will then configure its window with respect to the new pixel origin. You should always use the FCONORIGIN function to reset the standard text origin after creating a special offset text window, and it is a good idea to do this immediately after the SCRWIN call. Remember in this context that the SETCONORIGIN command only affects the SCRWIN command, and after using SCRWIN you can freely use SETCONORIGIN without having any effect on text output until the next SCRWIN command. Notice also that the word STREAM itself uses SCRWIN to initiate the text window of a newly selected stream, so you must be careful not to leave the text origin set to some peculiar setting. * It is important to note that streams do NOT remember individual pixel origin settings. So, the best philosophy for using SETCONORIGIN is to use this function immediately before SCRWIN and then use FCONORIGIN immediately afterwards to revert to the standard origin settings. For example: 1 5 8 8 SETCONORIGIN 10 10 40 5 SCRWIN FCONORIGIN etc. etc. Note that the parameters to the SETCONORIGIN function have two distinct categories: 1. The origin pixel offsets (horizontal and vertical) 2. The font-related character multipliers (horizontal and vertical) The origin pixel offsets are simple additive offsets, specified as either positive or negative, and are used to fine-tune the final screen position of the text window. The character multipliers are pixel values by which the window top left corner character offsets specified in SCRWIN are multiplied to give the initial pixel position for the top left corner of the text window. Character multipliers relate to horizontal and vertical font resolution, and would normally each be set to 8 for a standard 8x8 pixel font. For example: 0 5 8 8 SETCONORIGIN sets the vertical pixel offset to 5, and the vertical multiplier to 8. If you now say: 20 12 40 8 SCRWIN you are asking for a text window which starts 12 text character rows from the top of the console window. Since you have specified the font "character multiplier" as "8", the first thing HeliOS will do to get the vertical window pixel displacement is to multiply the character row offset by the character multiplier to get an initial text window vertical pixel offset: 12 x 8 = 96 pixels Having arrived at this basic font-related overall offset, HeliOS applies the fine-tuning offset adjustment specified by the origin pixel offset. In our example this would mean adding 5 pixels to the vertical offset. 96 + 5 = 101 pixels Thus our text window would open 101 pixels from the top of the console window. Text window pixel origin offset settings can be determined and modified individually using the variables CONHOFFSET and CONVOFFSET, and the character multipliers can be determined and modified individually using the variables CONHSCALE and CONVSCALE. Note that simply storing a value in one of these variables is sufficient to set the origin for the next use of SCRWIN, and reading these variables will always give you the current text window origin setting. See the "Dictionary.doc" for more details. ---------------- HeliOS Variables ---------------- HeliOS, as you know, handles much of its parameter passing via the stack. Other languages make more use of named variables, and many programmers who are already experienced with, for example, BASIC, may feel initially uncomfortable when confronted with the stack-oriented HeliOS system. Actually you can, and indeed must, use named variables in HeliOS to an extensive degree, although in some cases this may be less efficient (and often more involved) than using the stack. In most cases local parameter passing is best implemented using the stack, but global parameters are generally stored as variables. In some languages variables are treated as more or less "abstract" entities with sometimes quite complex properties, restrictions, and error checking. In HeliOS, variables are very transparently and directly associated with the accessing of data stored directly in memory: they are in no way complex or mysterious, and have very practical useage. In HeliOS a variable is simply an allocated section of memory with which a name has been associated. Although many other languages may obscure the fact, the use of variables is really all about naming a memory location where you are storing data, and this simple scenario is exactly how you should regard HeliOS variables. After all, what the computer is really doing when you manipulate a variable is to move data in and out of a labelled storage location: even in languages which attempt to insulate (isolate) you from the reality of what the CPU is doing, the underlying action is fundamentally similar. The problem is that the more you are insulated from direct considerations of the computer's CPU, the less efficient will be the data handling of your programs. HeliOS variables can be in 3 data sizes (Byte, Word, and Long), and you can easily generate string variables which contain counted ASCII text strings. Calling a HeliOS variable results in an address being placed on the stack: remember that this is NOT the variable data value (the variable contents), but the address where that variable's data is stored. You can regard variables as a little like "mail boxes" in memory, and these "boxes" have very simple properties: 1. Each variable has a unique identifying address which can be either 16-bit or 32-bit, depending how you defined the variable. 2. Each variable has a space allocated for storing its contents, and this data space can be either 1 Byte (8-bits), 1 Word (16-bits), or 1 LongWord (32-bits) in length for normal data variables. The "length" of string variables obviously depends on the length of the string itself. 3. Each variable has a name HeliOS variables are really very simple indeed, and have absolutely no constraints or checks whatever on useage. Variables are not "typed" in HeliOS, which is to say that the data stored in any numeric variable can be whatever you choose. Furthermore, HeliOS does no checking whatever on what you are doing with the contents of a variable: if you are silly enough to use the contents of a pure data variable as an address by mistake, HeliOS will let you go ahead and do it! It also is up to you to keep track of what size a variable is, and HeliOS will not check to see if you are trying to store a longword value into a byte sized variable. In other words you have total freedom (and therefore speed and efficiency) in handling variables, but it is entirely your own responsibility to ensure that you get everything correct. Most programmers would agree that this speed and freedom is ideal, but it may come as a shock to programmers who have been used to somewhat more restrictive systems. In this respect, as in many others, HeliOS is more like assembly language than other high level languages. When a HeliOS data variable is defined, the following need to be specified: 1. The initial data value 2. The data size (Byte, Word, or Long) 3. The addressing size (16-bit or 32-bit) 4. The variable's name This process of variable definition is very easy indeed. Here are a few examples, creating a variable called "PIGLETS": 123 VARIABLE PIGLETS -> 16-bit data, initial value 123, 16-bit addressed 123. DVARIABLE PIGLETS -> 32-bit data, initial value 123, 16-bit addressed ^ Specifies 32-bit data 123 VARIABLEL PIGLETS -> 16-bit data, initial value 123, 32-bit addressed ^ Specifies 32-bit addressing 123 CVARIABLE PIGLETS -> 8-bit data, initial value 123, 16-bit addressed ^ Specifies 8-bit data 123. DVARIABLEL PIGLETS -> 32-bit data, initial value 123, 32-bit addressed After this initial specification, whenever you use the variable's name you will get on the stack the address (either 16-bit or 32-bit, depending on how the variable was defined) where the variable's contents are stored. For example, let us assume that you want to create a normal 16-bit addressed variable called APPLES, with a value of "10" stored in it. You could define such a variable like this: 10 VARIABLE APPLES Note that you always have to supply an initial value when you first create a HeliOS variable, and saying simply VARIABLE APPLES will result in an error. If you now say, in your program code or the command line, the word APPLES, HeliOS will place on the stack the 16-bit variable address: this address is the place in memory where the number 10 is at present stored. Having got the variable address on the stack, you can either change the value of the data stored there, or read the old data contents. To change the contents of a variable, you might say 15 APPLES ! -> Stores a new value of "15" into APPLES To get the current contents of a variable, you might say APPLES @ -> Gets the current value of APPLES onto the stack Incrementing and decrementing variables is very simple: APPLES INC -> Increment the APPLES variable APPLES DEC -> Decrement the APPLES variable Moving data between variables is easy too: APPLES FRUIT MOVE -> Move the data contents of APPLES into FRUIT There are various words for getting and storing values in variables, and in general these functions are exactly the same functions which you use in ordinary memory accessing operations. In fact, always remember that a variable IS just a neatly labelled section of memory as far as HeliOS is concerned. See the "Dictionary.doc" section on "MEMORY OPERATIONS" for a full list of all the many powerful ways of accessing variables and memory in HeliOS. ---------------- HeliOS Constants ---------------- HeliOS Constants are even easier to use than variables, and are defined in a very similar way by specifying: 1. The initial data value 2. The data size (Word, or Long) 3. The constant's name Note that because HeliOS does not support byte sized stack or dictionary cells, it would be pointless to have a byte sized constant. Such a constant would need to be stored in 2 bytes of memory space and would require a 2 byte stack cell, thus effectively being no different from a normal 16-bit constant. Here are examples of constant definitions: 123 CONSTANT PIGLETS -> 16-bit data, initial value 123 123. DCONSTANT PIGLETS -> 32-bit data, initial value 123 ^ Specifies 32-bit data Once defined, when the constant's name is used, the actual data value of the constant will be placed on the stack. -------------------------------------------------------- More about the concepts of "Run-time" and "Compile-time" -------------------------------------------------------- HeliOS is both a compiler and an interpreter, and in many cases the same functional "words" are used by HeliOS in both modes of operation. In fact "compilation" is actually performed by the "interpreter", so you could perhaps say that during compilation a dual process of compilation and interpretation is taking place. This introduces a "dual-time" concept with respect to any program, because there will be some words specified within the program source code which are executed WHEN COMPILING, and others which only execute WHEN THE PROGRAM IS RUN later. In general we use two terms to distinguish between the execution times of HeliOS functions: 1. COMPILE-TIME When a word executes while HeliOS is compiling a program, this is said to be "COMPILE-TIME" execution for that word. An example of this is the word ":" with which you begin word definitions in your source code. When HeliOS is compiling your program, it reads the ":" and immediately (at COMPILE-TIME) executes that word in order to initiate the colon definition. 2. RUN-TIME Words specified within colon definitions are not executed as the program is compiled, but only later when that defined word is run either in the program itself or from the command line. As HeliOS at compile-time reads word names specified within a colon definition, it will simply compile them. The functions thus compiled will not be executed until later at RUN-TIME when the program is actually running. When command line input is being interpreted directly, even though it is not really the case that a specific program is being compiled, there still exist the distinctions between run-time and compile-time. In most cases at the command line words are executed at once, so the moment when the command line is actually executed is RUN-TIME for these words. However, if the command line contains a colon definition, the words which are compiled within that definition will have their COMPILE-TIME as the command line is executed. Here we only want to introduce you to the general concepts of run-time and compile-time: elsewhere in the tutorials we give specific instances where it is vital to understand the distinction between functions being executed at compile-time and run-time. We will just mention here that HeliOS has many functions which allow you to control completely the action of all words at run-time and compile-time, allowing you to customise the process of compilation as required. One other interesting and important feature of certain special HeliOS words is that they can automatically "sense" whether they are in "compile mode" within a colon definition, or are being executed directly, and change their behaviour accordingly. These words are very useful, and are called STATE-SENSITIVE functions. The name "state-sensitive" is used because HeliOS, like FORTH, uses an internal system variable called "STATE" to set whether the HeliOS system is in COMPILE-MODE or EXECUTE-MODE. You can get the value of this STATE variable (using the expression "STATE @L") and use this in conjunction with other special functions to generate your own special state sensitive words. If you are brave, that is...... ---------------------- The Dictionary pointer ---------------------- The HeliOS Dictionary memory space always has a current "top" position pointer which specifies the next free memory location in the Dictionary. As the Dictionary is used this "Dictionary Pointer" is gradually advanced until finally the Dictionary space is full. The current Dictionary Pointer is stored in a HeliOS system variable called "DP" and is always available by simply using the expression: DP @L Because this is quite a commonly used function, HeliOS provides a single word to access the current Dictionary Pointer: this word is "HERE". So HERE is directly equivalent to DP @L and both return the current first free cell in Dictionary memory space. -------------- Using "CREATE" -------------- If you wish to create data tables, string buffers etc. within the HeliOS dictionary, it is very easy and convenient to use CREATE or CREATEL. What happens when you use these functions is that you create a new word which when executed will return the address of the next dictionary space. For example, you might use: CREATE MyTable ; Create new word 1 , ; Start of data table 2 , 3 , 4 , When you subsequently use the word "MyTable", the 16-bit dictionary address of the stored value "1" (the first location of the allotted table), will be returned. Note here the use of the simple and convenient word "," which simply stores a data value in the next free location within the HeliOS Dictionary memory space, then advances the Dictionary Pointer to the next position. Notice that CREATE can be used to make a standard variable, like this: CREATE MyVariableName 0 , which is equivalent to 0 VARIABLE MyVariableName Here are a few useful points to note about CREATE. 1. The new word specified using CREATE will always return a pointer to the current next free Dictionary space when CREATE was used. 2. The words "ALLOT" and "," (and related functions) can be used after CREATE to allocate Dictionary memory and advance the Dictionary Pointer, thus effectively allowing the construction of convenient "protected" data space. 3. After using CREATE, the word HERE will return a pointer to the Dictionary address which will be returned by the word just created by CREATE. 4. Using CREATE only sets up a new word name header which when executed will return a pointer to the current Dictionary top free space. 5. Using CREATE does NOT allocate any Dictionary space or perform any other function to "protect" or affect in any way the Dictionary address which will be returned by the newly CREATED word. Another example of using CREATE might be: CREATEL MyName $ This is my name$ Using the word "MyName" will now return a 32-bit pointer to the string "This is my name". Another example might be: CREATEL MyNameTable $ My name 1$ $ My name 2$ $ My name 3$ $ My name 4$ You can see what this does.....(It creates a 32-bit addressed string array) Of course you can also use VARIABLE etc. to do similar things, but the use of CREATE is very convenient and worth adding to your repertoire. Remember that although CREATE is very convenient to use, it does have the disadvantage that data structures created using this method use precious Dictionary space. This does not matter in small programs, but you must be careful not to use CREATE too liberally for large data structures or in very large programs. See the next section for another reason to take care when creating data structures in the Dictionary memory area....... ------------------------------------------------------------------- Run-time initialisation of data structures within Dictionary memory ------------------------------------------------------------------- It is very important to understand that any 32-bit pointers referenced within the HeliOS Dictionary must always be initialised in run-time code. Look at this a fragment of HeliOS code: CREATEL MyDataStructure1 0. D, \ <--------------------------- 1 , | 2 , | 3 , | Points directly to previous structure | CREATEL MyDataStructure2 | | MyDataStructure1 D, \ --->---------- 1 , 2 , 3 , ( Notice that the above code is not within a colon definition, and will execute as the program is initially compiled ) : MyProgram MyProgram1 MyProgram2 MyProgram3 etc. etc. ; MyProgram Here you can see that the first field of MyDataStructure2 contains a 32-bit pointer to the first data structure, MyDataStructure1. Obviously this pointer is valid, and if you went on to compile and run the program containing this fragment, the pointer would work correctly: we have literally "defined" the first field of the second data structure to point to the first structure. No problem! Or is there......? There is a trap hidden here, which you must be very wary of. If you were to save the compiled program as a module to be used with the HeliOS executive program, you would indeed have a very bad problem. The problem is that the "MyDataStructure1" pointer would actually only have been initialised in the code fragment you saw above, which would execute at COMPILE-TIME (when the program was compiled before being saved as a HeliOS code module). Note that this pointer is an absolute 32-bit address, and would be valid specifically and only for the position in memory where the software was currently loaded (at compile-time). If the code module is later loaded into a HeliOS system which has been loaded into a different memory address (99% certainly the case!), notice that this absolute 32-bit pointer will be incorrect! The pointer was only valid in the initial "compile" environment, and will no longer be valid in the subsequent "execution" environment. In other words, absolute pointers in HeliOS code modules are NOT RELOCATED when a code module is reloaded by another HeliOS system. This means that you MUST ensure that all 32-bit pointers are initialised dynamically in RUN-TIME code. Thus, in the startup code of your actual program, within a colon definition WHICH WILL EXECUTE AT RUN-TIME, you must include a fragment of code like this: CREATEL MyDataStructure1 0. D, 1 , 2 , 3 , CREATEL MyDataStructure2 MyDataStructure1 D, 1 , 2 , 3 , : InitMyDataStructure MyDataStructure1 MyDataStructure2 D!L ; : MyProgram InitMyDataStructure MyProgram1 MyProgram2 MyProgram3 etc. etc. ; MyProgram Note the simple way that InitMyDataStructure is used to initialise the pointer: MyDataStructure1 returns a 32-bit pointer to MyDataStructure1 at run-time, and we simply use D!L to set this address into the first field of MyDataStructure2. So, an important "golden rule" is: * ALWAYS INITIALISE ALL POINTERS AND VARIABLES DYNAMICALLY IN RUN-TIME CODE There are two examples in the "HeliOS:Source" directory which demonstrate the use of run-time initialisation of linked data structures in Dictionary memory: you might like to look at these examples. These are the files "IntuitionGadgets.src" and "IntuitionMenus.src", where typical Intuition linked data structures for gadgets and menus are created within the HeliOS Dictionary space. ---------------------------------------------- Easy ways of setting up text strings in HeliOS ---------------------------------------------- It is most convenient, unless Dictionary space is in short supply, to set up text strings within the HeliOS Dictionary memory space. There are various ways of doing this, and below we give examples of some of the easiest and most commonly used. Strings which return 16-bit addresses: 20 $VARIABLE MyString -> Allocates space for a 20 character counted string, initialised to null. $CONSTANT MyString $Contents of MyString$ -> Creates a counted string with contents "Contents of MyString". CREATE MyString $ $Contents of MyString$ -> Creates a counted string with contents "Contents of MyString". CREATE MyString <$ $Contents of MyString$ -> Creates an uncounted string with contents "Contents of MyString". Strings which return 32-bit addresses: 20 $VARIABLEL MyString -> Allocates space for a 20 character counted string, initialised to null. $CONSTANTL MyString $Contents of MyString$ -> Creates a counted string with contents "Contents of MyString". CREATEL MyString $ $Contents of MyString$ -> Creates a counted string with contents "Contents of MyString". CREATEL MyString <$ $Contents of MyString$ -> Creates an uncounted string with contents "Contents of MyString". If you simply want a string when typing at the command line it is easiest to use the word $NEXTL as follows: $NEXTL Contents_of_MyString Notice that this word is quick and easy, but has the disadvantage that it requires that the string has no internal spaces: in fact $NEXTL will simply get the next word in the input stream delimited by spaces. If you want a string which includes spaces when typing at the command line it is easiest to use $DELNEXTL which uses the system string delimiter: $DELNEXTL $Contents of MyString$ Both these words return counted strings, and if you require an uncounted string it is easiest to simply increment the counted string pointer by one to skip the count byte. Both these words return PADL, where the string has been stored, so if you want to preserve the string for future use you must move it from PADL to somewhere safer, because PAD is used internally by HeliOS in many of its string manipulations. ---------------------------------- The "Code Field Address", or "CFA" ---------------------------------- This is part of the internal working of HeliOS which is very useful for you to know about, as you will see later. Originally, in FORTH, all words had the following sections: Name Field Address (= "NFA") -> The address of the word name itself Code Field Address (= "CFA") -> The address of the word's execution code Parameter Field Address (= "PFA") -> The address of the word's "body" Details of how these internals work need not worry you, especially since HeliOS is no longer internally constructed in the same way as FORTH, but you will from time to time see references to these names in documentation. In HeliOS, unlike FORTH, the various parts of any word are not stored in the same memory area, and although HeliOS words do still have NFA, PFA and CFA fields, these are not strictly the same as the old FORTH concepts. The really useful thing in all this is the "CFA", because you can make use of this address to EXECUTE a command indirectly. You do not need to know details of internal working, but you do need to know two important things: 1. How to get the CFA of any word 2. How to use the CFA to execute a word indirectly To get the CFA of any word, you can use these HeliOS functions: FIND ( _ _ _ CFA or 0 ) Used in the form: FIND MyWord to search the dictionary for "MyWord". If not found flag 0 is returned, otherwise the CFA of MyWord is returned. This is a state sensitive word and performs its function within a colon definition by compiling the found CFA as a literal. LATESTCFA ( _ _ _ CFA ) Returns CFA of last word in current vocabulary. MYSELF ( _ _ _ CFA ) Used within colon definitions to compile the CFA of the word being defined for recursive functions. ' ( _ _ _ PFA ) "tick" Used in the form: ' MyWord where MyWord is any word in the dictionary. Searches dictionary for MyWord and if found returns PFA. If not found an error is reported. This is a state sensitive word and performs its function within a colon definition by compiling the found PFA as a literal. CFA ( PFA _ _ _ CFA ) Converts HeliOS word PFA on stack to CFA. Obviously you can only get the CFA of a word after it has been defined, but the special case of MYSELF is interesting in that it allows you to obtain a reference to the CFA of the current word being compiled, thus giving the facility for employing recursion (this is an advanced operation and is only mentioned here for the sake of interest). Important note: There is a VERY IMPORTANT proviso which you must remember when getting the CFA of any HeliOS word: * YOU CAN ONLY FIND THE CFA OF A NAMED HELIOS FUNCTION IF THE VOCABULARY INFORMATION IS PRESENT This means that: * ALL "CFA FINDING" OPERATIONS MUST BE PERFORMED BY COMPILE-TIME CODE AND NEVER BY RUN-TIME CODE This is a subtle but vitally important point and needs further explanation: When a program is being compiled or interpereted the HeliOS system keeps on line a vocabulary word name list so it can locate any specified word. The functions "'" and "FIND" make use of this vocubulary name listing to find the PFA or CFA of a word at compile time. If you are subsequently running a program using the stand-alone HeliOS executive module, the vocabulary name list is no longer present, so your program code cannot use "'", or "FIND" functions to get a word CFA. This means that you must perform all CFA finding operations in code which runs at COMPILE-TIME, as the program is actually being compiled, when the full vocabulary information is present. The best way of doing this is to find all required CFAs at compile time and store them into variables which can be accessed at RUN-TIME. For example: 0 VARIABLE (FUNCTION1) <- COMPILE-TIME code 0 VARIABLE (FUNCTION2) <- COMPILE-TIME code 0 VARIABLE (FUNCTION3) <- COMPILE-TIME code etc. etc. : FUNCTION1 PLACE YOUR PROGRAM CODE HERE ; ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ <- RUN-TIME code : FUNCTION2 PLACE YOUR PROGRAM CODE HERE ; ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ <- RUN-TIME code : FUNCTION3 PLACE YOUR PROGRAM CODE HERE ; ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ <- RUN-TIME code etc. etc. FIND FUNCTION1 (FUNCTION1) ! <- COMPILE-TIME code FIND FUNCTION2 (FUNCTION2) ! <- COMPILE-TIME code FIND FUNCTION3 (FUNCTION3) ! <- COMPILE-TIME code etc. etc. Remember: * NO OPERATIONS WHICH REQUIRE ACCESS TO THE VOCABULARY LIST ARE POSSIBLE WITHIN "RUN-TIME" CODE Having acquired the CFA of a word, you can use the following HeliOS words to execute the word: EXECUTE ( CFA _ _ _ ) Executes word whose CFA is on stack. @EXECUTE ( a1 _ _ _ ) Executes word whose CFA is stored at a1. This facility for indirect execution has many uses, the most important of which, as we will see next, is "Vectored Execution". ------------------ Vectored Execution ------------------ This is a very important technique which you will certainly need to use often in your HeliOS programs. The term "Vectored Execution" refers to the technique of executing HeliOS functions by reference to indirect pointers. The idea here is that you can store the CFA of a word in a variable, or a table, and execute the word by referencing the stored CFA. Here are a few a simple ways you could use this. 1. Changing the operation of an already compiled word. You could make word called PrintName as follows: 0 VARIABLE CurrentName \ Set up a dummy variable : PrintName CurrentName @EXECUTE ; \ Create command word : Name1 ." Name1" ; \ Create Name1 LATESTCFA CurrentName ! \ Store CFA of Name1 as CurrentName This will give the function "PrintName" the action of printing "Name1", and PrintName can be incorporated in whatever routine you wish. At any time later you can change the operation of PrintName very easily: for example, you could change it like this: : Name2 ." Name2" ; \ Create Name2 : Name3 ." Name3" ; \ Create Name3 : Name4 ." Name4" ; \ Create Name4 FIND Name2 CurrentName ! \ Store CFA of Name2 as CurrentName After this PrintName would print "Name2". or FIND Name4 CurrentName ! \ Store CFA of Name4 as CurrentName After this PrintName would print "Name4". etc. 2. Using selective execution tables. You can conditionally execute one of a set of functions by reference to a table. Here is an example: : Name1 ." Name1" ; \ Create Name1 : Name2 ." Name2" ; \ Create Name2 : Name3 ." Name3" ; \ Create Name3 : Name4 ." Name4" ; \ Create Name4 : Name5 ." Name5" ; \ Create Name5 CREATE MyNameTable FIND Name1 , \ Store CFA of Name1 FIND Name2 , \ Store CFA of Name2 FIND Name3 , \ Store CFA of Name3 FIND Name4 , \ Store CFA of Name4 FIND Name5 , \ Store CFA of Name5 0 VARIABLE CurrentFunctionNumber : GetName MyNameTable CurrentFunctionNumber @ 2* + @EXECUTE ; In this example, "GetName" will have its operation controlled by the number stored in the variable "CurrentFunctionNumber". Note that the value of CurrentFunctionNumber has been doubled (using 2*) in the above code, because each entry in the CFA table is 2 bytes in length, so all offsets into the table must be in multiples of 2 bytes. This technique of executing CFAs from a table can be used for conditional execution in many circumstances, and in general is much more powerful than using lots of nested conditional IF...THEN statements to carry out actions based on the value of a variable. 3. Using words before they have been defined. This is the most important use of vectored execution, and is an absolutely vital technique which you will often need in your HeliOS programs. Normally you can only use a word AFTER it has been created, and this often imposes quite severe limitations on the way you organise your code. This in itself is not too bad, but there can be occasions where you need to have functions which refer to each other: this is obviously impossible because the first word created cannot call the second word because it has not yet been defined. This is where vectored execution can be useful, because it allows you to define a set of executable variables at the start of a program which can be referred to by intermediate functions and then set up at the end of the program to contain the CFAs of any intermediate HeliOS words. Here is a trivial example to illustrate the general idea: you will find more complex examples in the source code example files provided. 0 VARIABLE (MyFunction1) 0 VARIABLE (MyFunction2) 0 VARIABLE (MyFunction3) : MyFunction1 (MyFunction2) @EXECUTE ; : MyFunction2 (MyFunction3) @EXECUTE ; : MyFunction3 ." Hello" ; FIND MyFunction1 (MyFunction1) ! FIND MyFunction2 (MyFunction2) ! FIND MyFunction3 (MyFunction3) ! Notice that we used brackets surrounding the function names for the CFA storage variables. This is merely a matter of personal choice, but it is quite a useful idea to use some such consistent naming convention. ----------------------- Error handling routines ----------------------- It is useful to have a consistent method of dealing with program errors. You can easily create a custom error handler which prints an error message and closes down your program in a civilised fashion, deallocating all the resources which may have been opened before the error occurred. A good error handler allows all errors to be routed via a comprehensive sequential closedown routine, and this can be done easily using the HeliOS system customisable error handler word ERROR", which can be customised using SETERROR". When ERROR" senses an error it prints an associated error text message (which it gets from the next part of the input stream delimited by a '"' character) and then ABORTS program execution. This is OK as far as it goes, but it does not close down specific things related to your program, so you need to customise ERROR" to do a little more intelligent work. To do this you must first create a function which closes down everything your program opened: the error handler will use this function to end your program tidily. If you write such a function (called CLOSEDOWN for ease of reference here) you must make it intelligent enough to do two important things: 1. Close down everything in the exact reverse order in which it was created to help avoid memory fragmentation problems. 2. Check to see what has or has not yet been opened and only close down what is required. This is easy to do by having variables which are only set when an associated resource has been opened: for example, you could have a window handle variable which would be null until you filled it in when the window was actually opened. 3. It is a good idea if the CLOSEDOWN routine finally resets ERROR" to the standard system function, using RESETERROR". Here is a short general example of setting up a custom error handler. 0. DVARIABLE MYSCREEN 0. DVARIABLE MYWINDOW 0 VARIABLE (CLOSEDOWN) 0 VARIABLE (MYCLOSEDOWNERROR) : MYCLOSEDOWNERROR \ Action on error IF \ If there is an error CR \ Move down CR TYPE \ Type error message CR \ Move down CR ." Press to quit!" \ Print message CR \ Move down CR WAITSPACE \ Wait for (CLOSEDOWN) @EXECUTE \ Execute close down QUIT \ QUIT back to HelioS ELSE DDROP \ No error, so just DDROP THEN ; : CLOSEDOWN \ Closes down resources MYWINDOW D@ D0<> \ ? Close window IF MYWINDOW D@ CLOSEWINDOW THEN MYSCREEN D@ \ ? Close screen D0<> IF MYSCREEN D@ CLOSESCREEN THEN RESETERROR" \ Reset system ERROR" ; FIND CLOSEDOWN (CLOSEDOWN) ! \ Set execution variable FIND MYCLOSEDOWNERROR (MYCLOSEDOWNERROR) ! \ Set execution variable : MYPROGRAM (MYCLOSEDOWNERROR) SETERROR" \ Set custom ERROR" LIT$ $MyScreen$ 640 256 4 OPENSCREEN \ Try to open screen DFLAG0= ERROR" Failed to open screen!" \ Test for error SCREEN1 D! \ Success, so store etc. etc. ; See the "Dictionary.doc" for full details of all related functions: ERROR", ERROR"FUNCTION, RESETERROR", SETERROR". ---------------------- Using the Return stack ---------------------- HeliOS, like FORTH, has a secondary stack which is used internally by the system for various purposes: this is called the "Return" stack because one of its main functions is to store return pointers during nested command threading operations. The Return stack will be familiar to FORTH users, but remember that HeliOS uses the Return stack differently in its internal operation, so do not rely on being able to do the same "tricks" with the Return stack that you might do in traditional FORTH. The Return stack can be used as a fast auxiliary data storage space for "local" data during the execution of a colon definition. However, you MUST ensure that the state of the Return stack is left unchanged overall, and that it is restored to exactly the same status at the end of any colon definition function as it had when the function started. There are easy to use and very fast words for moving data between the HeliOS stack and the Return stack, in both directions. The Return stack also has the useful property that within loop constructs it stores the loop index and loop limit: this means that you can use the Return stack to get loop indices and generally manipulate loops in special ways. The use of the Return stack in HeliOS is very similar to FORTH from the programmers point of view, and in many ways HeliOS seems to be using the Return stack exactly like FORTH. However, HeliOS does use the Return stack internally in special ways, so do not assume that standard FORTH usage is always applicable unless specifically stated. There are several operators which allow you to access numbers on the Return stack and to move numbers between the Return stack and the HeliOS stack: >R ( n1 _ _ ) Move top of stack onto return stack. D>R ( d1 _ _ ) Move top of stack d-number onto return stack. DDUP>R ( d1 _ _ d1 ) DDUP and move tos d-number onto return stack. DI ( _ _ d1 ) Get copy of top d-number from return stack. DI' ( _ _ d1 ) Get copy ofsecond d-number on return stack. DJ ( _ _ d1 ) Get copy of third d-number on return stack. DJ' ( _ _ d1 ) Get copy of fourth d-number on return stack. DR> ( _ _ d1 ) Remove top r-stack d-number onto HeliOS stack. DUP>R ( n1 _ _ n1 ) DUP and move top of stack onto return stack. I ( _ _ n1 ) Get copy of top number on return stack. I' ( _ _ n1 ) Get copy of second number on return stack. ( = 1st loop limit. ) J ( _ _ n1 ) Get copy of third number on return stack. ( = 2nd loop index. ) J' ( _ _ n1 ) Get copy of fourth number on return stack. ( = 2nd loop limit. ) K ( _ _ n1 ) Get copy of fifth number on return stack. ( = 3rd loop index. ) K' ( _ _ n1 ) Get copy of sixth number on return stack. ( = 3rd loop limit. ) R> ( _ _ n1 ) Remove top of return stack onto HeliOS stack. R! ( l1 _ _ ) Sets Return stack pointer to l1. RP! ( _ _ ) Initialise return stack. RP@ ( _ _ l1 ) Return address of current return stack top. The most useful feature of the Return stack is that it can be used as a temporary store within the execution of a colon definition. If your stack parameter manipulations are getting complicated it is very useful to be able to move numbers off the HeliOS stack onto the Return stack. In effect you can use the Return stack as a kind of temporary storage data structure, and this is far faster and more efficient than using complicated HeliOS stack manipulations or using storage variables. The Return stack is a simple linear "last-in-first-out" structure, just like the data stack, so the rules for manipulating it are just like the ones you are used to using for the main HeliOS stack. Remember however that whatever numbers you store on the Return stack must be removed again before you get to the end of the current word definition: you cannot use the Return stack to pass arguments from one word to another. To see how the Return stack might be used to make parameter manipulation easier, imagine you have to write a short routine to solve the following equation: ax˛ + bx + c Assume that you start with all four parameter values on the stack in the following order: ( a b c x - - - ) First you might factor out the equation as: ax˛ + bx + c = x(ax+b)+c Now look at one way of breaking down the required operation: OPERATOR DATA STACK RETURN STACK a b c x >R a b c x SWAP ROT c b a x I c b a x x * c b ax x + c (ax+b) x R> * c x(ax+b) + x(ax+b)+c Here is this sequence presented as a word definition: : QUADRATIC ( a b c x -- n ) >R SWAP ROT I * + R> * + ; Now test it: 2 7 9 3 QUADRATIC Result -> 48 Perhaps you would like to try to solve this programming task using your own new method to see if you can do it more efficiently. Why not try to do it: 1. Without using the return stack. 2. Using the Return stack, but in a different way. --------------------- Using HeliOS includes --------------------- The documentation for the Amiga operating system uses a massive number of symbolic values, and it is quite impractical to do extensive programming of the operating system without using these symbols. If you try to work by using directly coded numeric values for all the Amiga symbols you will end up making many mistakes and your code will be clumsy and unreadable. In most languages you have to use the Amiga "Include files" to create an on-line reference system for all the symbolic values: then you can type the symbolic names in your code rather than numeric values. This is often implemented in a very slow and memory-greedy manner, and many programmers prefer to define symbolic values directly in their own code to avoid having the overhead of using the include files. HeliOS has an ideal solution to this problem, with a compact, efficient, and ultra-fast "precompiled include file" system. See the documentation file "HeliOS:Docs/UserInterface/IncludeFiles.doc" for details of how the HeliOS include system works. Setting up the HeliOS "high-speed" precompiled include file system is so easy, and this system is so fast in operation, that you should ALWAYS use it: then you can include ANY Amiga symbol directly in your code by simply typing its symbolic name. You can configure the main HeliOS Interpreter/Editor system to start up with default include files already installed automatically, and this is recommended for everyday programming work. However, if you want your programs to be compiled also from the stand-alone HeliOS compiler you need to include specific lines in the program code to load the include files during compilation, like this: \ ************************* \ Load include symbol files \ ************************* \ \ Note: these "include files" are pre-compiled (for speed) versions of \ the full Amiga includes and the HeliOS system includes. \ \ Uncomment the lines below for standalone compilation, but otherwise \ it is quicker to set these include files from the HeliOS menus AMIGAINCLUDE HeliOS:HeliOS_AmigaInclude USERINCLUDE HeliOS:HeliOS_UserInclude Notice how easy it is to use the two words AMIGAINCLUDE and USERINCLUDE to set up the include system to load any specified precompiled include files. Once the include files are set up, you can easily get symbolic values and add new symbols from within the main HeliOS Interpreter/Editor program. From within your program source code or the HeliOS interpreter command line you can access any Amiga symbol by simply typing its name. However there is one important thing to remember: * The HeliOS include system can be set to return either 16-bit or 32-bit values on the HeliOS stack. To set the HeliOS include system to return 16-bit values you use the word WORDINCLUDE. To set the HeliOS include system to return 32-bit values you use the word LONGINCLUDE. The default setting is always for 32-bit include values, and it is best to avoid confusion and possible errors by always returning the system to this default status as soon as you have finished using any 16-bit include values. For example: INTUBASE \ Put on the stack the 16-bit address \ where the 32-bit pointer to Intuition \ library base is stored WORDINCLUDE \ Set include system to return 16-bit values _LVOViewAddress \ Put on the stack the offset of the library \ call "ViewAddress" as a 16-bit value LONGINCLUDE \ Reset include system to default 32-bit setting LIBRARY \ Call Intuition Library function to get \ VIEW pointer D0RESULT \ Get dummy register D0 value which is the \ View address returned from the library call Try experimenting with the include system at the command line in the HeliOS interpreter until you are confident about the way the system works and in particular the use of WORDINCLUDE and LONGINCLUDE. ----------------------- Calling Amiga libraries ----------------------- This is very easy in HeliOS, and you should consult the "Dictionary.doc" for a full explanation: however, here we will include an example so that you can see the direct relationship between an assembly language library call and one done in HeliOS. Here are two examples of the same library call, one in assembler and the other in HeliOS: Assembler: DOSBASE dc.l 0 ; Initialised to DOS library base by startup code CONFILE dc.l 0 CON dc.b 'CON:0/12/640/200/HeliOS',0 OPENCON move.l #CON,D1 move.l #MODE_NEWFILE,D2 move.l DOSBASE,A6 jsr _LVOOpen(A6) move.l D0,CONFILE rts HeliOS: CREATEL CON <$ CON:0/12/640/200/HeliOS$ 0. DVARIABLE CONFILE : OPENCON CON 1 DREG D! MODE_NEWFILE 2 DREG D! DOSBASE WORDINCLUDE _LVOOpen LONGINCLUDE LIBRARY D0RESULT CONFILE D! ; Now compare them line by line: : OPENCON -> OPENCON CON 1 DREG D! -> move.l #CON,D1 MODE_NEWFILE 2 DREG D! -> move.l #MODE_NEWFILE,D2 DOSBASE -> move.l DOSBASE,A6 WORDINCLUDE _LVOOpen LONGINCLUDE LIBRARY -> jsr _LVOOpen(A6) D0RESULT CONFILE D! -> move.l D0,CONFILE ; -> rts Notice that HeliOS library calls directly mirror the way things are done in assembly language, so you may easily adapt any information you find in documentation or examples which refer to assembly language library calls. As a final example, look at the alternative HeliOS version using the word "LIBRARYL" instead of "LIBRARY": : OPENCON CON 1 DREG D! MODE_NEWFILE 2 DREG D! _LVOOpen DOSBASE LIBRARYL D0RESULT CONFILE D! ; Notice two advantages here: 1. We did not need to use WORDINCLUDE and LONGINCLUDE 2. We have the function offset followed by the line "DOSBASE LIBRARYL", and this could conveniently be made into a predefined colon definition which would be applicable to ALL our DOS library calls. We could make a word CALLDOS, like this: : CallDOS DOSBASE LIBRARYL ; Then we could say things like: _LVOOpen CallDOS _LVOClose CallDOS _LVORead CallDOS etc. etc. You can see from this example that careful structuring of HeliOS code can improve functionality and readability. -------------------------- Setting up loops in HeliOS -------------------------- We have already looked at the use of IF...ELSE...THEN constructs to set up conditional branching, and now we are going to learn how to implement loops in HeliOS. Loops are methods of repeatedly executing certain sections of code, and broadly speaking, there are three kinds of loop construct: 1. Counted loops which execute the loop code a predetermined number of times. 2. Conditional loops which execute the loop code until a condition is met. 3. Infinite loops which loop forever.....or almost.... ------------- Counted Loops ------------- Let us first look at counted loops, which are probably the most common. In HeliOS, counted loops are set up by specifying the "initial" and "limit" values of a "loop index". This loop index is given its initial value for the first iteration of the loop, then each time the loop executes the loop index is increased until it reaches the limit value, at which point the loop finishes. The loop index is incremented and checked against the loop limit each time around at the END of the loop. If the index is still LESS THAN the limit, the loop will branch back to the start, but if the index is GREATER THAN OR EQUAL TO the limit, the loop finishes and the code carries on past the loop end point. The HeliOS words which implement a simple counted loop are "DO" and "LOOP", where DO specifies the start of the loop and LOOP specifies the loop-back point. The loop index and loop limit are provided as parameters to the word "DO". The loop index and loop limit can be read onto the stack while a loop is executing, using the HeliOS words "I" and "I'" respectively. Here are the definitions of DO and LOOP: DO ( n1 n2 _ _ _ ) Where: n1 = loop limit n1 = loop index Used in colon definitions to mark the start of and set up a loop. The loop limit n1 is pushed onto the return stack followed by the loop index initial value n2. The loop executes until the index is incremented by LOOP to a value equal to or greater than the loop limit. LOOP ( _ _ _ ) Used in colon definitions to mark the end of a loop. The loop index is incremented and execution returns to DO if index is still less than limit. Note that a DO....LOOP construct MUST be defined within a colon definition. Using this information we can construct a test word to enable us to examine the behaviour of a loop in action. : LOOPTEST CR 10 0 \ Loop limit followed by start index (note the order!) DO I . I' . CR \ Print loop index followed by loop limit LOOP ; LOOPTEST This will display the following results: 0 10 1 10 2 10 3 10 4 10 5 10 6 10 7 10 8 10 9 10 Notice that the loop index never reaches the value of the loop limit while within the loop, because, as soon as the loop index equals the limit, the loop ends. As we said earlier, it is important to remember that the index is incremented and compared to the loop limit AT THE END of each loop. Look closely at the example and read the notes above until you are sure about the way these simple counted loops work. It is very important to really understand the mechanism of loops, so here again is a step by step breakdown of what happens as HeliOS executes a loop. 1. The word "DO" removes the limit and index parameters from the HeliOS stack and stores them on the Return stack (with the index on top of the Return stack and the limit in second place). 2. HeliOS executes sequentially the words within the loop, up to the word "LOOP". 3. The word "LOOP" increments the loop index (on top of the Return stack) and then performs the following checks. * If the loop index is still less than the loop limit (second on the Return stack) the loop returns execution to immediately after "DO" and the loop repeats again. * If the loop index is greater than or equal to the loop limit the loop finishes. 4. When the loop finishes, the index and limit values are removed from the Return stack and code execution continues with the word following the word "LOOP". Note that now you have information on how the loop works, you can trick the loop mechanism by modifying the loop index and limit values on the Return stack from within the loop if you wish. This can give rise to many useful techniques, and is perfectly safe once you know what you are doing. You can set up loops with any range provided that the limit is higher than the index, and remember that the index does not have to start at "0". Look at this interesting example: : NEGLOOP -230 -240 DO I . LOOP ; NEGLOOP will print out: -240 -239 -238 -237 -236 -235 -234 -233 -232 -231 Notice that the index (-240) still starts as less than the limit (-230). In many FORTH versions, if you specify the "DO" parameters such that the index is greater than or equal to the loop limit at the start, the loop will set off and recycle through the whole range of index values until it equals the limit again. HeliOS does not display this uncivilised behaviour, and will merely quit at the end of the first loop if you make a mistake like this. This brings us to an important point: * No matter what the starting parameters, a HeliOS loop will ALWAYS go through the intermediate code (or "body") of the loop at least once. This is because no checking of the index amnd limit values is carried out until the end of the loop is reached. Another somewhat subtle point is that you can only use the words "I" and "I'" to get the loop index and limit within the SAME colon definition in which the loop is defined. In other words, you cannot use "I" and "I'" in any sub-words within the loop. For example, the following code would not work correctly: : PRINTINDEX I . ; : TESTLOOP 10 0 DO PRINTINDEX LOOP ; The reason that this would fail is that the loop parameters are only at the top of the Return stack in the "root" level of the colon definition in which the loop is defined. When HeliOS enters the word PRINTINDEX in the above example the Return stack is used to store "Return" information by the HeliOS system, so the words "I" and "I'" will no longer return the actual loop parameters from the Return stack. Of course, you can have loops within loops, and these are called "Nested Loops". Nested loops are just like ordinary loops, but there is a useful way of getting at the loop parameters of an outer nested loop from inside the inner nested loop. This is done by using special Return stack operators ("J", "K" etc.) which get the stack values for outer loops from BELOW the top two stack items. For example, here are some useful Return stack operators to use in nested loops: I ( _ _ n1 ) Get copy of top number on return stack. ( = 1st loop index. ) I' ( _ _ n1 ) Get copy of second number on return stack. ( = 1st loop limit. ) J ( _ _ n1 ) Get copy of third number on return stack. ( = 2nd loop index. ) J' ( _ _ n1 ) Get copy of fourth number on return stack. ( = 2nd loop limit. ) K ( _ _ n1 ) Get copy of fifth number on return stack. ( = 3rd loop index. ) K' ( _ _ n1 ) Get copy of sixth number on return stack. ( = 3rd loop limit. ) Here is an interesting example of a nested loop: : NESTLOOP SCRCLR 0 400 100 DO 1+ DUP GFXSETAPEN 150 100 DO J I GFXWRITE LOOP LOOP WAITSPACE ; NESTLOOP Other useful function to use in counted loops are "/LOOP" and "+LOOP": these words allow you to specify increments other than "1" for the loop index for each loop cycle. Here are the definitions of these words: +LOOP ( n1 _ _ _ ) Used in colon definitions in the form: DO - - - - - n1 +LOOP to implement a loop in which the index is incremented by n1 each time the loop executes. The branch back to DO occurs as long as the index remains less than the limit if n1 > 0, or for as long as it is greater than the limit if n1 < 0. Note than n1 can be positive or negative for this word. /LOOP ( n1 _ _ _ ) Used in colon definitions in the form: DO - - - - - n1 /LOOP to implement a loop in which the index is incremented by n1 each time the loop executes. The branch back to DO occurs as long as the index remains less than the limit. In this case n1 must always be positive (unsigned) and the loop index can exceed 32767. For example: : PLUSLOOP 10 0 DO I . 2 +LOOP ; PLUSLOOP will give us: 0 2 4 6 8 Now look at these more interesting examples: : NEGLOOP1 10 0 DO I . -2 +LOOP ; NEGLOOP1 will give us: 0 : NEGLOOP2 0 10 DO I . -2 +LOOP ; NEGLOOP2 will give us: 10 8 6 4 2 0 : NEGLOOP3 -10 0 DO I . -2 +LOOP ; NEGLOOP3 will give us: 0 -2 -4 -6 -8 -10 Look at these examples and try to follow the way the loop index and limit are being used to control the loop. Here is another interesting example: : DOUBLE-LOOP CR 200 1 DO I . I +LOOP ; Here the index itself is used as the increment so that starting with one the index doubles each time: 1 2 4 8 16 32 64 128 256 etc. Note that in this example we did not set the loop index to start at "0". Can you see why? Yes, if we had done so the argument for +LOOP would have been "0", and in this case we would have created an infinite loop because the loop index would never change. ----------------- Conditional Loops ----------------- Conditional loops repeat until a certain condition is met, and are created, at their simplest, by using the "BEGIN...UNTIL" construct. We say "at their simplest" because there are a few more complex variants such as BEGIN...WHILE...REPEAT. Here are a few word definitions: BEGIN ( _ _ _ ) Used in colon definitions in the forms : * BEGIN - - - - - UNTIL Repeats if top stack value is "0" at UNTIL * BEGIN - - - - - AGAIN Repeats always * BEGIN - - - - - WHILE - - - - - REPEAT If WHILE finds a 0, it diverts execution to beyond REPEAT, leaving the conditional structure. If flag for WHILE is "true", execution is allowed to continue through to REPEAT and then back to BEGIN. BEGIN marks the start of a repeating section of code and a return point for UNTIL, AGAIN, and REPEAT. REPEAT ( _ _ _ ) Used in colon definitions to force execution to return to the point just after the word BEGIN. UNTIL ( flag _ _ _ ) Used in colon definitions to control a conditional branch back to BEGIN. If flag is false execution reverts to BEGIN, but if true execution continues ahead. WHILE ( flag _ _ _ ) Used in colon definitions to implement BEGIN - - - WHILE - - - REPEAT conditional structures. If flag is "false", WHILE diverts execution to beyond REPEAT, leaving the conditional structure. If flag is "true", execution is allowed to continue through to REPEAT and then back to BEGIN. Looking at the simple BEGIN...UNTIL loop, we can see that it is a very simple conditional structure. Here is an example: : WAIT-KEY BEGIN ?TERMINAL UNTIL ; WAIT-KEY This is so simple that it hardly needs explanation: the loop simply cycles until the value left on the stack by ?TERMINAL is non-zero, then execution continues from after UNTIL. Although BEGIN...UNTIL loops are very simple, they are very powerful and useful. Sometimes, however, you need a slightly more powerful version of the conditional loop, and this is provided by the BEGIN...WHILE...REPEAT loop construct. In this variant of the conditional loop, the "clever bit" is performed by the word WHILE, which acts according to a flag which it receives on the stack. If this flag is "false", WHILE diverts execution to beyond REPEAT, leaving the conditional structure. If the flag is "true", execution is allowed to continue through to REPEAT and then back to BEGIN. Here is an example: : WAIT-REPEAT SCRCLR 20 8 CURPUT ." I'm waiting for a key!" BEGIN BELL ?TERMINAL 0= WHILE ." ." 5 DELAY REPEAT SCRCLR 20 8 CURPUT ." Thank You!" 20 15 CURPUT ." Please press to continue...." WAITSPACE ; WAIT-REPEAT -------------- Infinite Loops -------------- The final form of loop construct is the infinite loop, and this can be set up using BEGIN AGAIN. As you might expect, this type of loop is fairly permanent, and will simply cycle around for ever..... Actually you can exit from an infinite loop by using a word such as ABORT, or by using SYS@ and SYS!. However, these techniques do require a little experience, and are only mentioned here for the sake of completeness. See the Dictionary documentation for more details. ************************************************************************* End *************************************************************************