Jump to content

Lee Stewart

+AtariAge Subscriber
  • Posts

    5,846
  • Joined

  • Last visited

Blog Entries posted by Lee Stewart

  1. Lee Stewart

    fbForth Tutorial
    This is the first of several tutorials to help those new to Forth, fbForth 2.0 in particular, to understand the language and its programming environment, as well as to gain some facility with it. fbForth is based on TI Forth, which was derived mostly from FIG-Forth with some influence from Forth-79. There are more recent Forth standards; but, compatibility with TI Forth was the prime concern.
     
    The biggest difference between TI Forth and fbForth is that fbForth is a file-based system, whereas TI Forth reads/writes directly from/to disk sectors without regard to any file structure. This is dangerous for the health of the disk and the user, especially, if you were to inadvertently use a disk with files on it for other systems. It also makes it difficult to exchange programs. Not only does fbForth coexist with as many unrelated files as will fit on a disk, you can create many different blocks files on the same disk as long as there is room. A TI Forth disk cannot contain anything but Forth blocks.
     
    I suppose that is more than sufficient for preamble. Let’s get on with learning fbForth.
     
    To operate the fbForth 2.0 System, you must have the following equipment or equivalent:
    TI-99/4A Console
    Monitor
    fbForth 2.0 Module (see this forum thread’s post #1 to get yours: fbForth—TI Forth with File-based Block I/O )
    Peripheral Expansion Box (PEB) with
    32 KiB Memory Expansion
    Disk Controller with 1 or more Disk Drives
    RS232 Interface (optional)
    Printer connected to RS232 interface (optional)
     
     
    If you wish to work through these tutorials but do not have this equipment or equivalent (CF7+ or nanoPEB, which substitutes for a PEB with the first three PEB items above), all of the software and firmware are available in the above-referenced thread for the Classic99 and MAME emulators. I also can supply the same for CaDD Electronics’ PC99 emulator, if you need it.
     
     
    It is a good idea to have a copy of fbForth 2.0: A File-Based Cartridge Implementation of TI Forth (the manual—available in the above forum thread) for reference, especially for looking up commands (Forth words—more below) in the glossary (“Appendix D”). Please note that the glossary is in ASCII order, which is listed at the bottom of every glossary page. Also, if you have a copy of the first edition of Leo Brodie’s excellent beginner’s book on Forth: Starting FORTH, “Appendix C” of the manual cross-references conflicts with fbForth 2.0.
     
    After powering up:
     

     
    and selecting “2 FOR FBFORTH 2.0:12” for 40-column text mode, say,
     

     
    If FBLOCKS is found, you will be presented with:
     

     
    followed by (after your color choice):
     

     
    If FBLOCKS cannot be found, you will see:
     

     
     
    The system blocks file is FBLOCKS and must be present in DSK1 for the first series of screens to display. If fbForth does not find it there, the second screen displays. fbForth will still work just fine. You just won’t be able to display the menu of loadable utilities with MENU until you make FBLOCKS the current blocks file, which you can do by typing the following at the console’s flashing cursor if FBLOCKS is in DSK2, say:
    USEBFL DSK2.FBLOCKS 1 LOAD You can force fbForth to look for FBLOCKS on another disk at boot time if you hold down the number of that disk immediately following your startup-screen selection.
     
    Notes about the welcome screen:
    The first two lines and the last line of the welcome screen appear regardless of the presence of FBLOCKS.
    The version number of the cartridge includes the revision number after the ‘:’.
    The line beginning with “FBLOCKS mod:” comes from block #1 of FBLOCKS and will always reflect the current date of the system blocks file, FBLOCKS, which is always kept up to date in the above forum thread.
     
    Commands in Forth are called “words”. You will note that Forth words included in the normal text of these tutorials appear in boldface and are surrounded by spaces. This may look awkward when the space after a word precedes a comma, period or similar punctuation mark; but, since those punctuation marks are also Forth words, this practice avoids ambiguity.
     
    Speaking of spaces around words, that is how the Forth text interpreter ( INTERPRET ) knows it has the next word to look up in its dictionary (linked list of already defined words). It searches the dictionary from the most recently defined word to the very first word defined. In fbForth, that word is EXECUTE . See what I did there? EXECUTE has a space after it and it’s before a period. You are in the hands of the Forth text interpreter in two places, viz., at the console’s blinking cursor and when a block is loaded by the word LOAD . The input stream is viewed by the interpreter as a series of tokens separated by one or more spaces.
     
    If the interpreter finds the word, it executes the word and gets the next token.
     
    If the interpreter cannot find the token as a word in the dictionary, it checks to see if the token can be converted to a number in the current radix (number base). If it can, it pushes that number onto the parameter stack, which is often termed “the data stack” or, simply, “the stack”. The parameter stack, by the way, consists of a stack of cells, much as a stack of plates in a cafeteria, with the same restriction: You can only readily remove (pop) the top plate, i.e., the last cell on the stack is the most accessible and thus the first one popped off. This Last-In-First-Out situation is known as LIFO. Furthermore, in fbForth, a cell is 16 bits or 2 bytes wide. In computer parlance, 2 bytes constitutes a word; but, to avoid confusion with talking about Forth commands as words, we will generally use “cell” instead of “word” to mean “2 bytes”.
     
    Finally, if the token is not a word in the dictionary and it cannot be converted to a number, the interpreter gives up and issues an error message that repeats the word it could not find followed by a question mark. It also clears the stacks (parameter stack and return stack, about which more later) and, if loaded from a blocks file, leaves two numbers on the parameter stack to aid in finding where in the input stream the error occurred. These numbers are the contents of user variables IN (the position in the input stream immediately following the token causing the error) and BLK (the block number being interpreted). When loading a block that aborts with the error report just described, you can type WHERE to put you into the editor with the cursor at the error. We will talk more about the editor in another lesson. Otherwise, you may just want to type SP! (stack pointer store) to clear the parameter stack.
     
    After you finish entering one or more successfully interpreted words and/or converted numbers with <ENTER>, the interpreter will display “ ok:n” to let you know its success. The ‘n’ after the colon is the depth of the parameter stack, i.e., how many numbers are currently on the stack. Here are a few lines typed at the console:
     

     
    The first line is from just tapping <ENTER>. Everything is OK with nothing on the stack. The second line pushes ‘4’ onto the stack and indicates all is well with one number on the stack. The third line pops and prints ( . ) the number, showing the stack as now empty. The last line obviously was not understood by the interpreter, hence the error message.
     
    Let’s wind this lesson up with showing you the most common way to define a new word in Forth. The defining word we will use is : . : starts a high-level Forth definition, which is terminated with ; . The first token that must follow : is the name for the new word. fbForth is case-sensitive. HELLO is different from hello . In our definition, we will use the word .” , which means “print string”. ." accepts any characters into the string except for " , which is the terminator. As soon as it sees the " , it prints the string:
     

     
    We will now define the word HELLO and add CR to the definition before and after the print-string code. This will put the cursor at the beginning of the next line each time it is executed. Typing the newly defined word, HELLO , will execute its contents:
     

     
    Remember that Forth words must be separated by spaces. The Forth Interpreter looks in the input stream for the next word until it finds a space or the end of the input stream. Upon finding a space, it then looks for the next word that starts with the next, non-space character. There are six words used in our definition of HELLO above, which are:
    : HELLO <---the word we are defining CR .” CR ; That’s all for now.
     
  2. Lee Stewart
    We can write better temperature conversion words than we did at the end of the last lesson. If you thought that the words using /MOD were contrived to introduce /MOD , you were mostly on target. To obviate the necessity of using /MOD , all we need to do is to manage the dividend before division by adding half of the divisor. As before, we must adjust the sign of the rounding term to match the sign of the dividend and adjust the conversion formula such that the rounding term is an integer.

    For TC>TF , the rounding formula is
     

    F = 9C/5 ± 0.5 + 32
    F = (9C ± 2.5)/5 +32
    and, for an integer rounding term, we have


    F = (18C ± 5)/10 + 32
    Now, we can write the new definition for TC>TF :
     

    : TC>TF ( degC -- degF )  
    Similarly, for TF>TC :
     

    C = 5(F - 32)/9 ± 0.5
    C = (5(F - 32) ± 4.5)/9
    C = (10(F - 32) ± 9)/18  
    and, the new definition for TF>TC :


    : TF>TC ( degF -- degC )  
    It would be a good exercise for you to walk through the execution and track the stack contents for each of the above words, much as we did in Lesson 3.

    As always, I need your feedback to make this tutorial more useful.
     
     
  3. Lee Stewart
    In this lesson we will learn a few new arithmetic words, several words for stack manipulation and how to use them all in programming, i.e., defining new words.

    Before we do much more Forth arithmetic, let’s exercise our brains with some infix-to-postfix and postfix-to-infix conversions. Remember that infix notation is the same as algebraic notation and postfix is the same as RPN. Many of these exercises are based on or taken directly from Brodie’s Starting FORTH.

    Convert the following infix expressions to their postfix counterparts. Each answer is in the spoiler following the infix expression:

    1. a + bc
    2. a(b + c)
    3. (a - 10b)/3 + c
    4. 2a + 5b + (c + d)/3
    5. 0.5ab/100
    6. (a - b)/c

    Convert the following postfix expressions to their corresponding infix expressions:

    1. a b - a b + /
    2. a b 10 * /
    Now, let’s try to define some words that do calculations, using only the arithmetic operators we have learned to this point. Let’s define words that convert liquid measure in gallons, quarts, pints and fluid ounces to fluid ounces. We want to write out a phrase such as

    2 GALLONS 3 QUARTS + 5 CUPS + 25 FLUID OUNCES +

    to put on the stack the result in fluid ounces. Starting with pints, we can define the next higher volume in terms of the next lower as follows:

    : FLUID ( -- ) ; a no-op, i.e., do-nothing visual place-holder word.
    : OUNCES ( floz -- floz ) ; a no-op visual place-holder word that indicates a value in fluid ounces is on the stack and unchanged by OUNCES .
    : PINTS ( pt -- floz ) 16 * ; converts pints to fluid ounces.
    : QUARTS ( qt -- floz ) PINTS 2 * ; converts quarts to fluid ounces.
    : GALLONS ( gal -- floz ) QUARTS 4 * ; converts gallons to fluid ounces.

    Note that the stack effects are comments in the above definitions for reminding us of each word’s function. You do not need to type them to have a functional definition.

    We can define the singular forms of the above words, with identical stack effects, in terms of the plural word names above as follows:

    : OUNCE OUNCES ;
    : PINT PINTS ;
    : QUART QUARTS ;
    : GALLON GALLONS ;

    These are now synonyms of the words included in each definition. Now we can write such phrases as the following:






    You can verify with a calculator that each result printed by . is the total liquid measure in fluid ounces of the quantities added before printing.

    Now, let’s define words to perform the arithmetic in the above six infix-to-postfix exercises. We will name each word as Exn , where n is the exercise number:

    1.

    2.

    3.
    4.
    5.

    6.
    We can only do integer arithmetic with the Forth we have learned thus far. Two more division operators can help us manage this a little better, viz., MOD (pronounced “mod”) and /MOD (pronounced “slash-mod”):

    MOD ( n1 n2 — rem ) leaves on the stack the remainder rem from n1/n2.
    /MOD ( n1 n2 — rem quot ) leaves on the stack the remainder rem and the quotient quot from n1/n2.
     




    As we discovered in the exercise definitions above, #4 is very difficult and #6 is impossible without some stack manipulation we haven’t yet learned. Here are some words that will help us to manipulate the stack:

    DUP ( n — n n ) duplicates the top stack cell.
    SWAP ( n1 n2 — n2 n1 ) reverses the top two stack cells.
    OVER ( n1 n2 — n1 n2 n1 ) copies the second cell to the top of the stack.
    ROT ( n1 n2 n3 — n2 n3 n1 ) rotates the third cell to the top of the stack.
    DROP ( n — ) drops the top cell from the stack.
     



    EX4 can now be defined as
    Here is a commented version of EX4 to explain a little better how it works. The running contents of the stack are shown in comments as “stack:...” when the stack is changed by a line of code:
    and EX6 is now tractable as
    and the commented version to monitor the stack:
    Let’s try our hand at defining words for these two formulas for converting between Fahrenheit (°F) and Celsius (°C) temperatures:
    F = 9C/5 + 32
    C = 5(F - 32)/9

    Let’s define TC>TF to do the Fahrenheit-to-Celsius conversion (formula #1) and TF>TC for the opposite conversion (formula #2). Try it yourself before opening the spoiler below to see one way to do it:
    : TC>TF



    : TF>TC




    Now let’s improve these words to round to the nearest degree using /MOD instead of / so we can work with the remainder of the integer division. We also need to expand the factors 9/5 and 5/9 to 18/10 and 10/18, respectively, so we can halve the divisor and still get an integer:
    : TC>TF



    : TF>TC




    Here are commented versions for clarity:
    : TC>TF
    : TF>TC
    Be sure to try some negative temperatures. Compare the results with a calculator. Anything wrong? The following fbForth 2.0 word will help us craft a better rounding solution:

    SGN ( n — -1|0|1 ) leaves on the stack the sign (-1 or 1) of n or 0 for n = 0. The symbol ‘|’ in the stack effects means “or” and separates possible results, only one of which will be left on the stack.
     




    To get the above temperature-conversion words to round properly in both the positive and negative directions, we need to change the sign of the half-divisor term to match the remainder given by /MOD . Because SGN consumes the number it is testing, we need to DUP it before we hand it off to SGN . All we need to do now is to multiply the half-divisor term by the sign, add the result to the remainder term and divide again. This time we don’t care about the remainder. This quotient will be our rounding term of 1, -1 or 0, which, when added to the previous integer result, will give us our correctly rounded conversion:
    : TC>TF



    : TF>TC




    And commented versions for clarity:
    : TC>TF
     
    : TF>TC
    That’s all for this session. Please, feel free to ask questions and make suggestions; and certainly, let me know of any mistakes you find.
  4. Lee Stewart

    fbForth Tutorial
    This lesson will discuss more of the details of RAM organization, working with the stack and some minimal Forth programming.

    The 32 KiB expansion RAM on the TI-99/4A is organized as follows in fbForth 2.0: The 8 KiB lower RAM (2000h – 3FFFh) contains four fbForth block buffers, low-level Assembly Language support, system global variables (termed “user variables”) and the return stack. You will notice here that hexadecimal numbers in this discussion (not in actual Forth code!) have a trailing ‘h’. The return stack starts at the top of this space at 3FFEh and grows downward toward the low-level support code, which currently ends at 3A03h. This allows 766 cells for address storage for deeply nested code. Colon ( : ), introduced in the last lesson, makes extensive use of the return stack. Your code can make use of the return stack as well; but, you must be very, very careful when you do, that you do not interfere with the return scheme of fbForth. In a later lesson we will discuss how to safely use the return stack because it certainly can be useful.

    The 24 KiB upper RAM (A000h – FFFFh) is almost entirely free for your Forth programming pleasure. This is due to the fact that the entire resident dictionary of fbForth 2.0 resides in the 32 KiB ROM of the cartridge. The last word of the resident dictionary is TASK and resides at A000h for reasons that will be explained in a later lesson. The next available address is A00Ch. This is the beginning of the user-definable dictionary. Any words defined after this point are still part of the dictionary and, as a result, become part of the language. This is what makes Forth extensible.

    The top of memory in the upper 24 KiB area contains the Terminal Input Buffer (TIB) and the base of the parameter stack at FFA0h. This means that all but 108 bytes of the upper 24 KiB area is available for your programming. This, of course, includes the parameter stack, which grows downward toward the dictionary, which grows upward toward the stack.

    Now is a good time to elaborate on a comment Owen left on the last lesson, viz., verbalizing or pronouncing Forth words. Forth was intended to be a language you could speak. That is pretty much why Forth functions, routines, constants and variables are called “words”. I will try to remember to include the usual pronunciation of standard Forth words as we discuss them. A very good source for such pronunciations is Derick and Becker’s FORTH Encyclopedia: The Complete FORTH Programmer’s Manual, 2nd Edition. The words appear in ASCII order, so they are easy to find. Each of the entries includes the word’s pronunciation. An example from the last lesson is SP! , which is usually pronounced “S-P-store”. The “SP” part happens to refer to the stack pointer, which is why I had said “stack pointer store”. Another example, as Owen mentioned, is “dot-quote” for ." and “quote” for " .

    Most words’ pronunciations are not difficult to figure out; but, there are a couple of heavily used words that are not obvious. One is @ , which is pronounced “fetch” because it pops an address from the stack and fetches its contents to the stack. Another is ! , which is pronounced “store”. It requires two numbers on the stack: the number to store and the address in which to store it. We will discuss these in greater detail ere long.

    We turn our attention, now, to working with the stack. As mentioned last time, it operates in a LIFO mode. To show you how it works, we will use . and a new word, .S (pronounced “dot-S” and means “print stack”). .S will non-destructively print the stack contents from the bottom up (left to right), with ‘|’ representing the bottom of the stack. Recall that . pops the top number off the stack and prints it:

    You will see that the system response of “ok:5” displayed after .S is executed shows the stack to have the same depth as before it was executed. Notice that the last of the five numbers entered is on top of the stack, that it is the first one popped and printed by . and that the system response shows the depth to be one less after each . . The last line demonstrates what happens when you execute a word requiring a number on the stack but with nothing on the stack. The number 11776 (2E00h) printed is significant because the bottom edge of the stack is the start of the TIB and the last thing entered at the terminal (console) was ‘ . ’, which has an ASCII code of 2Eh.

    A Forth convention when defining or listing words is to show the state of the stack before and after the word executes. The stack effects (also, stack signature) are indicated by showing within parentheses what the word expects on top of the stack before it executes to the left of an em-dash (—) (or two or three dashes [--]) and what it leaves on the stack to the right. Also, the most accessible number is always on the right on either side of ‘--’, which represents execution of the word. For example:
    ! ( n addr -- ) shows that the stack effects of ! , which requires the storage address addr to be on top of the stack and the number n, which will be stored at addr, below it. The absence of anything to the right of the execution indicator (--) shows that ! leaves nothing on the stack.

    Let's do a little math with the stack. We will start with the basic four: addition, subtraction, multiplication and division. Here are those definitions with their stack effects:
    + ( n1 n2 -- sum ) pronounced “plus” - ( n1 n2 -- diff ) pronounced “subtract” * ( n1 n2 -- prod ) pronounced “times” / ( n1 n2 -- quot ) pronounced “divide” Each of these operators requires two numbers on the stack and leaves the result on the stack when it is done. Below are examples of each operation, showing the result printed with . :
     
     
    As you type the above at the terminal, you will see that the operator is after the numbers it operates on. This is an example of postfix notation or RPN (Reverse Polish Notation). You are probably more familiar with infix (algebraic) notation:
    1 + 2 = 3 where the operator is between the two numbers it operates on. The postfix nature of Forth is the highest hurdle you will likely need to get over. Words in Forth can certainly be defined in an infix way; but, postfix is more efficient and easier to implement.
    An interesting bit of history regarding RPN: Reverse Polish Notation implies that there is a Polish Notation, which, of course, there is. Polish logician Jan Łukasiewic (yahn woo-kah-SHEH-vitch) invented prefix notation to simplify a branch of mathematical logic. The operator precedes its two operands in prefix notation. It became known as Polish Notation (PN) due to the nationality of Łukasiewic. Quite naturally, when postfix notation arrived on the scene with the exact opposite form, it became known as Reverse Polish Notation (RPN).

    At the top of this lesson, we discussed memory organization, stating that virtually all of the available programming memory is one chunk between the top of the parameter stack and the most recently defined word in the dictionary. Each of these locations is readily available with resident fbForth 2.0 words:
     
    SP@ ( -- addr ) pronounced “S-P-fetch”; leaves on the stack the address of top of stack HERE ( -- addr ) pronounced “here”; leaves on the stack the address of next available dictionary location The available RAM is the difference between these two addresses. Since the stack is at the higher address, we want to subtract HERE from the top-of-stack address:
     

    We can make a couple of useful definitions from this:
    : SIZE ( -- n ) SP@ HERE - ; : .SIZE ( -- ) SIZE . ." bytes free" ;  

    The first one leaves the size in bytes of free RAM between the top of the stack and HERE . The second prints that number followed by “bytes free”. You may have noticed that I slipped in another word in the definitions above, viz., ( . Pronounced “left paren”, it begins a comment, which is terminated by ‘)’. Comments are ignored by the text interpreter. Obviously, the comments above are not necessary for the definitions to work. You can enter the above definitions without them. They help us to remember what the words do. We will make it a habit to include the stack effects in this way when we actually begin storing our new words in a blocks file a lesson or two hence.

    We will conclude this lesson by defining one more useful word. CLS is a resident fbForth word that clears the screen but does not move the cursor. To also move the cursor to the top of the display screen, we will define PAGE for which we need another fbForth word that sets the cursor:
     
    GOTOXY ( x y -- ) sets the cursor to the x-y coordinates on the stack. The upper left corner of the display screen is at x = 0, y = 0.
    To show GOTOXY in action, we will place the cursor somewhere in the middle of the screen and print an ‘X’ there:
     

    And, now, our definition of PAGE :
     
    : PAGE ( -- ) CLS 0 0 GOTOXY ; clears the screen and sets the cursor to the top left corner of the display screen.
    Here is the screen after the definition and just before execution:
     

    and after execution:

    That’s it for this session. We will do more stack manipulation and programming next time.
×
×
  • Create New...