Jump to content
IGNORED

Forth: A Brief Primer (#2)


Willsy

Recommended Posts

Forth: A Brief Primer
A Forth primer for anyone that is interested in dipping their toes into the Forth language…
Words (Forth parlance for routines/commands/subroutines) take their arguments off the stack, do something with them, and if necessary, push results back to the stack. In this way, Forth words are ‘chained’ together, using the stack to communicate with each other. It really is as simple as that.
TOM DICK HARRY
The above looks strange if you are used to BASIC, but the above is a valid Forth program (assuming those words exist in the Forth system that you are using). When Forth code executes it starts executing at the left, and proceeds to the right, so TOM executes first, then DICK, then HARRY then the “program” ends. Now, it may be that TOM places a number on the stack when he executes. DICK may take that number and do something with it, leaving a result for HARRY.
Let’s try an example:
: TOM ( n – n*2 ) 2 * ;
: DICK ( n – n*4 ) 4 * ;
: HARRY ( n – ) 9 + . ;
This probably looks terrifying. Baffling. But please don’t be afraid. Often, ones objections to learning Forth is based on the assumption that is it difficult and/or complicated, but in actual fact, it is the complete opposite. It’s so simple, that when you eventually “get it” you might even feel somewhat stupid, a bit like when you learn how a certain magic trick works when you’re a kid, and when you realise it, you think “Is that IT? Is that ALL it is? Are you KIDDING me?!”
Let’s break that little “program” above down and see how it works.
First, we see a colon. This symbol is used to say to the Forth system “Hey, I’m going to define a new word, so listen up”. Following the colon, we have the name of the new word: TOM. Note, there must be a space between the colon and the name. Next, the open bracket. This is actually the start of a comment (REM in BASIC). Inside the comment, we are using a simple convention/notation to describe stack requirements of the word, usually called the “stack signature”. Note: THIS IS NOT CODE. It’s a comment, and as such it’s purely optional, but it helps readers to understand what the word does to the stack without having to dissect the code inside the word. See the article Forth: A Sojourn Into Stack Comments for more information.
Inside TOM we see the following code:
2 *
Looks a little strange, but, following this from left to right, it’s quite easy to break down what happens. REMEMBER, the stack comment tells us that a number should already be on the stack when this word is called. Let’s say 9 as an example.
The next thing that happens is that TOM itself pushes the number 2 onto the stack, so the stack looks like this:
9 2 <--top of stack
Then, the * word executes, which simply means multiply. Multiply takes two values from the stack, multiplies them, and pushes the result back to the stack. So, 9*2 is 18. So, if we passed 9 in on the stack, 18 would “come out” of TOM onto the stack.
The next thing we see is a semi-colon ; which says to the Forth system “Hey, I’ve done with my definition of the word TOM, thanks very much.”
At this point, the Forth systems “knows” a new word, TOM, and you can test it immediately:
44 TOM .
The system will display the value 88 (44*2).
What just happened? We asked the Forth system to put 44 on the stack, then asked it to execute TOM, which it did. TOM took the value on the stack, multiplied it by two, and put the result back on the stack. Then the word . (dot) took the value off the stack (in other words, it consumed it), and displayed it for us.
So, TOM is written and tested. That wasn’t so hard, was it?
Let’s have a look at DICK:
: DICK ( n – n*4 ) 4 * ;
We can see that this word takes a value from the stack, multiplies it by 4, and leaves the result on the stack.
And finally, HARRY:
: HARRY ( n – ) 9 + . ;
Note the stack comment. There is nothing on the output side. This word takes the top value on the stack, adds 9 to it, and uses . (dot ) to display the result, placing nothing on the stack.
Now, this is simply a collection of “words”. It’s not really a “program” as such. It’s more like three subroutines that exist independently of each other. That’s how Forth programs are developed: You split your program up until logical blocks of functionality, each having its own word (a block of functionality may have many words – it’s up to you how you “factor” your program”). These words can be written independently of each other, and tested by supplying data from the stack. When they are tested to satisfaction, you call it done, and you move on to the next logical part of the program.
We could do with some glue; another word, to bind TOM DICK and HARRRY together into a program:
: TDH ( n -- ) TOM DICK HARRY ;
So, here’s a word called TDH (Tom, Dick, Harry!) that calls TOM, then DICK, then HARRY, in that order. The stack comment tells us that we need to pass a number in on the stack before calling TDH. How do we do that?
Simple:
99 TDH
When we type the above and press enter, we get the result 801: ((99x2)x4)+9
Let’s look briefly at TI BASIC version of the above program for comparison purposes:
10 N=99
20 GOSUB 100
30 GOSUB 200
40 GOSUB 300
50 PRINT N
60 END
100 N=N*2
110 RETURN
200 N=N*4
210 RETURN
300 N=N+9
310 RETURN
  • Lines 10 to 50 are the equivalent to the word TDH
  • Lines 100 to 110 are TOM
  • Lines 200 to 210 are DICK
  • Lines 300 to 310 are HARRY

Now, let us look at the Forth version again:

: TOM ( n – n*2 ) 2 * ;
: DICK ( n – n*4 ) 4 * ;
: HARRY ( n – ) 9 + . ;
: TDH ( n -- ) TOM DICK HARRY ;
A lot shorter, and uses no variables – the data is carried in and out of the words using the stack. Each word can be tested independently of the other words, and bound together at the end into a program that does some work.
If you've read this far, then hopefully you are thinking “Well, it’s kinda weird, but I get it”. If that is the case, congratulations, you have just learned Forth. I’m not being flippant. Forth is all about words, and the stack. And when you’ve got that concept down, the rest is just a matter of experience.
Forth on the TI-99/4A
Hopefully you’ll try the above out on your favourite TI-99/4A Forth. There’s the excellent fbForth written by Lee Stewart, which is a much more advanced version of TI’s original TI FORTH, and has the advantage of running from a cartridge. There’s also my own Forth cartridge, TurboForth (and its companion website, http://.turboforth.net) and there is Brian Fox’s new Forth system, the name of which escapes me at the moment (chime in, Brian!).
Feel free to post comments/questions. It’s all about learning, sharing, and having fun.
  • Like 6
Link to comment
Share on other sites

My as yet work in progress Forth is called CAMEL99. Why?

It is derived from a simple ANS/ISO Forth system called CAMEL Forth

 

The author of CAMEL Forth, a fellow Canadian, Brad Rodriguez Phd. created the system in response to the 1994 ANS Forth spec.

The specification was designed by a committee...

 

"A camel is a horse designed by a committee"

 

I gather he was less than enthusiastic about the new language spec. :-)

Link to comment
Share on other sites

Excellent Willsy.

 

How would one set up a variable based on a random number? We could define the word "DICE" to display a random number between 1 and 6 .. but how would we store that random number in a variable?

 

I can take this one if you like.

 

This is where we have a fundamental difference in how you think when writing in Forth vs BASIC.

Each language gives us a "structure" to work in and these 2 languages are quite different

 

So we need to have the random number words defined in our system. I will assume we have them.

 

In BASIC you have RND. So in Forth we would call our word RND as well.

 

In BASIC you might write

 

100 MYRAND=RND

 

In Forth you could do the very same like this:

VARIABLE MYRAND  \ variables must be created before use

RND MYRAND !     \ RND puts a number on the stack
                 \ MYRAND puts its own memory address on the stack
                 \ '!' (pronounced 'STORE') stores the number in the memory address
                 

So in Forth it's more like using POKE() to put a number in variable.

 

In other words Forth is lower level than BASIC, a little like Assembler with some lipstick.

 

 

To get the contents of a variable we use the 'fetch' operator which is just a '@' character.

MYRAND @  . \ fetch the contents of MYRAND to the stack and print it with '.'
 457 ok
_

Note: Typically RND takes an input from the stack to control how big the result number will be.

I left it out to simplify the explanation.

Edited by TheBF
Link to comment
Share on other sites

Great question Retrospect. If I understand correctly, you wouldn't. You'd store the number in the stack. If I'm correct, then I guess you'd need a way to efficiently retrieve that number from the stack. Which would be my next question, how? Perhaps assign that result to a word?

Edited by Sinphaltimus
  • Like 1
Link to comment
Share on other sites

You are correct Sinphaltimus. You might not want or need to keep a random number in a variable.

You normally just get it and use it.

 

Which is the same with BASIC's RND function. It just returns a value. Do whatever you want with it.

 

So Forth RND typically wants a number on the stack to set the range from 0 to n

So here is DICE defined:

: DICE     ( -- n )  6 RND ;

When you type DICE at the console your will see 'ok" :)

 

What the heck? Well DICE just plopped a random number onto the stack. That's ALL it can do.

 

If you want that number it's there to use.

 

To see it use the '.' word. (called dot) :)

 

To store it in a variable see my previous post.

 

Make sense?

Link to comment
Share on other sites

Actually it's best to load a Forth onto your machine and play around with the stack.

You will crash the machine because it is your right to do so when you are brave enough to work directly with the hardware.

 

Get "Starting Forth" https://www.forth.com/starting-forth/0-starting-forth/

 

or read Willy's tutorial pages.

Link to comment
Share on other sites

Here is some material from a manual I am writing for people who know TI-BASIC and want to play with Forth.

 

 

Look at the program listing below:

10 CALL CLEAR

20 PRINT “Hello world!”

30 GOTO 20

 

Now look at it in CAMEL99 Forth with some "helper" words loaded

: 10: CLEAR ;

: 20: C" HELLO WORLD" PRINT ;

: RUN 10: BEGIN 20: AGAIN ;

 

 

To a TI-BASIC programmer it looks weird. This program is to help you understand how BASIC is really working and what it takes for a compiled programming language to imitate BASIC.

We have loaded our TI-BASIC word set into CAMEL99 so we have some familiar word names, but they seem to be backwards. Why? That has to do with how Forth uses something called the Stack which we will explain in the next lesson. Also notice we don’t have to CALL those sub-programs. That’s because all Forth words are sub-programs so they are all called by Forth by default.

 

We have used the colon/semicolon structure to create something that looks like line numbers. PLEASE NOTE: They are NOT line numbers, they are now WORDs in Forth but they let you see how a line number in BASIC performs the same function as a WORD does in Forth. Line numbers are just an identifier, that the computer can use to find code when it needs to.

 

BASIC’s line numbers are labels to let the computer find pieces of code

 

Forth WORDs are labels to let the computer find pieces of code

 

Let’s review what our new Forth words (line numbers do)

 

10: is obvious. It calls CLEAR, which fills the screen with spaces and puts the cursor on the bottom line.

 

20: Uses C” to put a literal characters in the definition that end at the other quote. Literal means the characters are “compiled” into place in memory just as they appear. And PRINT can take a string like that and PRINT it to the screen.

 

30: This is where things get different. We defined a word called RUN. RUN is performing the function of the BASIC interpreter, which is to “RUN” the code in each line number in order one at a time.

Forth does not have a RUN function so we did it manually.

Structured Programming

Forth is known as a structured programming language so it does not have GOTO. This may be a very weird thing for people who have only used BASIC.

Structured languages do not let you jump anywhere. They provide you with ways to jump but it is well… structured. To create an infinite loop (goes forever) we use the BEGIN/AGAIN structure. AGAIN is a GOTO that can only jump backwards to BEGIN. I hope it is clear in our RUN word that 20: is going to go on forever because it is between BEGIN and AGAIN.

Forth Version

Now please do not think I want you to write Forth code that looks like this BASIC with line numbers.

I simply wanted you to see something more familiar. Now that you know what line numbers really do in BASIC look at how it could look in Forth if we used more descriptive names instead of line numbers:

 

: CLS CLEAR ;

: HI! C" HELLO WORLD" PRINT ;

: RUN CLS BEGIN HI! AGAIN ;

 

In fact we really don’t need the first line because CLS is just calling the word CLEAR. So we can use CLEAR by itself and the program would become:

 

: HI! C" HELLO WORLD" PRINT ;

: RUN CLEAR BEGIN HI! AGAIN ;

 

 

And if we were really in a hurry we could remove the word HI! And put what it does right in our RUN word so it would look like this:

 

: RUN CLEAR BEGIN C" HELLO WORLD" PRINT AGAIN ;

 

Did that make your head spin a little. It can when you are used to BASIC. Compared to FORTH, BASIC is like a straight-jacket, forcing you do things in very specific ways with few exceptions. Forth gives you much more freedom, which means you have to think a little more about what you want but the program can do almost anything.

I will post a simple version of CAMEL99 with Graphics words and training wheels to assist in the transition shortly.

Link to comment
Share on other sites

Great question Retrospect. If I understand correctly, you wouldn't. You'd store the number in the stack. If I'm correct, then I guess you'd need a way to efficiently retrieve that number from the stack. Which would be my next question, how? Perhaps assign that result to a word?

 

Yes. Generally, you'd try to avoid storing stuff in variables, but you can use them. Of course, we can't store all the variables of a program on the stack, your program would spend most of its time "juggling the stack" (stackrobatics) to get the value it needs to the top of the stack where it can work on it. Not good.

 

Variables in Forth are very easy to understand. Variables have a name, just like BASIC, but, as Brian mentioned, they are declared in advance using the VARIABLE keyword:

 

TurboForth:

VARIABLE X

fbForth

0 VARIABLE X

 

Note that fbForth requires an initial value for the variable, whereas TurboForth initialises new variables to 0. There are lots of subtle differences between the two systems as they built on different Forth language standards.

 

Now, as Brian said, variables push their address to the stack, not the value stored in the variable. Here's a demonstration (TurboForth)

 

 

VARIABLE X
X $.

 

TurboForth reponds with A2CE (on my system). A2CE is the address (in hex) of the variable x (the word $. prints whatever is on the stack as a hex value). You could also print it as an unsigned decimal number:

 

 

x u.
and see that it's at address 41678 decimal.

 

To write a value into a variable, you use the word ! ("store" or "poke", whichever you prefer). ! needs two values on the stack:

  • The value to store;
  • The address to store it in (as the top most stack item).

So, to store 100 in X we could simply write:

 

 

100 X !

 

So, 100 goes on the stack, then X executes (it sounds weird to say that a variable "executes" but it really does - executing it makes it push it address to the stack), then the word ! (store) takes the address, and the value, and stores them for you.

 

We can prove that by reading it back with @ ("fetch", or "peek"):

 

 

x @ .

 

And you'll see 100 displayed.

 

Ta dah!

 

Not so hard, eh?

 

So, to store a dice roll in a variable, called ROLL, we could have something very simple like this:

 

 

VARIABLE ROLL
: ROLL-DICE ( -- ) 6 RND ROLL ! ;

 

And test it with the following:

 

 

ROLL-DICE ROLL @ .

 

Can you see what that is doing?

 

Note: RND, just like RND in BASIC always chooses a random number between 0 and the value you give it minus 1. So if you give 6 to RND you get a random number between 0 and 5 (which is of course 6 possible values). To make it between 1 and 6, simply add to the value that RND gives us and then store it:

 

 

: ROLL-DICE ( -- ) 6 RND  1 +  ROLL ! ;

 

So, here, we execute RND with 6 on the stack (so we get a number between 0 and 5 on the stack) and then we add 1 to it, then we store it in ROLL.

  • Like 1
Link to comment
Share on other sites

More on control structures.

 

Remember that little program we wrote in the CAMEL99 Trainer?

: RUN   CLEAR   BEGIN  C" HELLO WORLD" PRINT   AGAIN ;

Well it loops forever because of BEGIN AGAIN.

 

BASIC builds the BREAK control right into the running interpreter but Forth does not to keep loops fast.

 

So how do we stop it?

 

We want it to loop UNTIL something happens right?

 

Forth has the word UNTIL, that will loop like AGAIN until it sees a non-zero value on the stack.

 

In fact the definition for AGAIN is equivalent to "0 UNTIL" meaning that zero will make it loop forever.

 

Here is how we use UNTIL

: TEST  
       C" I am waiting here! ..." PRINT 
       BEGIN  KEY?  UNTIL ;

Paste this into the CAMEL99 Trainer and type TEST <enter>

You will see the string print and then nothing, until you press a key and then Forth will reply with ok.

 

What's happening?

The word KEY? calls the TI-99 KSCAN routine in the console ROMs.

If nothing is pressed it puts a zero on the stack.

If something is pressed it puts HEX FFFF on the stack (all 16 bits set is Forth's "true flag")

 

UNTIL keeps jumping back to BEGIN until... it sees that true flag on the stack.

Then it exits the loop and returns to the Forth interpreter.

 

As you can see UNTIL does not care where that true flag came from.

It could be the result of a calculation, some logical operation or comparison.

Anything that gives us a non-zero result will cause the loop to stop.

Edited by TheBF
Link to comment
Share on other sites

For a full featured Forth system with all the bells and whistles you have to use Turbo Forth or FBForth.

 

But if you want to do a little exploring with something the feels kind of familiar (but a little bit reverse order)

download the CAMEL99 trainer here. https://github.com/bfox9900/CAMEL99

 

Just click on CAMEL99 and then click the download button.

 

It wakes up in 32 col graphics mode and you can try Forth with training wheels :)

 

You can't save your work, and it can't load a file (yet) so use notepad or your favourite editor and Classic99

and paste your code into it or type it at the keyboard.

 

Here are some of the training wheels that have been put it to make playing with Forth a little easier for the
TI-Basic programmer.
NEW starts a new session. All code added to the system is removed.
CLEAR clears the screen and puts the cursor on the bottom line where it belongs!
(no need to CALL it. It's smart enough to call itself)
STRING: lets you make string variables and use them similar to BASIC
50 STRING: A$ makes a string that can hold 50 bytes.
PRINT well it only works for strings, but it just feels correct so go ahead.
Try this:
50 STRING: A$
A$ =" Hello world!"
A$ PRINT
Hello world! ok
(mind the space after =" It is a Forth word )
And you have these string functions, which like BASIC can be used together.
Example: A$ 0 5 SEG$ CHAR
: LEN ( $ -- n )
: CPOS ( $ char -- position)
: LEFT$ ( $ # -- top$)
: RIGHT$ ( $ # -- top$)
: SEG$ ( $ start# char# -- top$)
: STR$ ( n -- top$)
: VAL$ ( adr$ - # )
: CHR$ ( ascii# -- top$ )
Concatenate 2 strings USAGE: A$ B$ & PRINT
: & ( $1 $2 -- top$) ( changed from ADD$. it's RPN. be careful)
: COMPARE$ ( $1 $2 -- flag)
: =$ ( $1 $1 -- flag)
: >$ ( $1 $2 -- flag)
: <$ ( $1 $2 -- flag)
And something that BASIC does not have
: SKIP$ ( $ char -- $ ) ( removes leading chars)
----------------------------
GRAPHICS stuff like TI-BASIC.
Screen coordinates are base 0. 0..23 Rows, 0..32 columns
*NEW*
Give it an ascii value and it returns the character set no.
no need to remember the color set# Yay!
SET# ( ascii -- set#) ( usage: CHAR A SET# 2 8 COLOR)
*NEW*
Change contiguous character sets at once
COLOR ( character-set fg-color bg-color -- ) Uses same color numbers as Basic.
COLORS ( set1 set2 fg bg -- )
SCREEN ( color -- )
GRAPHICS ( -- ) Changes to familiar CYAN screen

\ CHAR sub-program is renamed to CHARDEF
\ USAGE: HEX" 7C7C 7C7C 7C7C 7C00" 30 CHARDEF
: CHARDEF ( FFFF FFFF FFFF FFFF char# --)
*NEW*
CHARDEF@ ( char# -- FFFF FFFF FFFF FFFF) fetches a pattern from VDP RAM and puts the numbers on the stack
GCHAR ( row col -- char)
( NOTE: for HCHAR & VCHAR you must give the cnt. Forth requires 4 parameters always)
: HCHAR ( col row char cnt -- )
: VCHAR ( x y char cnt -- )
Edited by TheBF
  • Like 1
Link to comment
Share on other sites

Right. So, for example, would a "Dopewars" game be possible with Forth?

 

My way of doing mine was roughly this in Basic

 

! D$ is Items on the market / inventory

! P$ is the places (streets of new york for example)

! MP is the market price

! MA is the market amounts (quantities of each item)

! IP is the inventory prices paid

! IA is the inventory amounts carried

! HI represents a high price for items on the market

! LO represents the lower prices of items on the market

 

1 ! DOPEWARS TI

2 RANDOMIZE

10 DIM D$(6),P$(6),MP(6),MA(6),IP(6),IA(6),LO(6),HI(6)

20 FOR LOOP=1 TO 6 :: READ X$ :: READ Y$ :: READ X :: READ Y

30 D$(LOOP)=X$ :: P$(LOOP)=Y$ :: HI(LOOP)=X :: LO(LOOP)=Y :: NEXT LOOP

40 DATA COCAINE,BRONX,10000,19000

50 DATA HEROIN,GHETTO,50000,9000

60 DATA SHROOMS,"CENTRAL PARK",10000,4000

70 DATA HASHISH,MANHATTEN,5000,2250

80 DATA ACID,BROOKLYN,1500,500

90 DATA LUDES,"NEW JERSEY",100,10

 

100 CASH=2500 :: DEBT=5000 :: DAY=0 :: FOR LOOP=1 TO 6 :: IA(LOOP)=0 :: IP(LOOP)=0 :: NEXT LOOP

 

110 ! NEW DAY

120 DAY=DAY+1

130 DI=INT(DEBT/16) :: DEBT=DEBT+DI

 

140 ! GET MARKET AMOUNTS & PRICES FOR NEW DAY

150 FOR LOOP=1 TO 6

160 MA(LOOP)=INT(100*RND)+1 :: MP(LOOP)=INT(HI(LOOP)*RND)+LO(LOOP)

170 XX=INT(6*RND)+1 :: IF XX=LOOP THEN 171 ELSE 180

171 MA(LOOP)=0 :: MP(LOOP)=0 ! THIS IS SIMULATING THERE BEING NONE OF THAT PARTICULAR ITEM THAT DAY

180 NEXT LOOP

 

200 ! SHOW THE MAIN SCREEN

210 CALL CLEAR

220 PRINT "ITEM";TAB(12);"QTY";TAB(18);"COST";TAB(26);"NUM" :: PRINT

230 FOR LOOP=1 TO 6

240 PRINT D$(LOOP);TAB(11);MA(LOOP);TAB(17);MP(LOOP);TAB(26);LOOP

250 NEXT LOOP

 

......and so on, and so forth ..... :)

Edited by Retrospect
Link to comment
Share on other sites

I gotta run, but will try to give you a Forth version, with BASIC code as the comments.

 

The technical answer is that Forth is a Turing complete language so it can in theory code anything.

 

The real answer is Forth is close to Assembler so you end up building up from low level pieces,

some of the stuff you take for granted in BASIC.

 

But... that can be fun and once you do it, you have it forever if you need it again.

 

So those string arrays will need to be created, but I think we can do something simple.

 

And I should add some of the things that seem natural to do in BASIC, you just don't do that way in Forth.

 

 

B

  • Like 1
Link to comment
Share on other sites

So I have become slightly crazy since I started comparing BASIC to FORTH.

 

I posted a literal translation of deNile here: http://atariage.com/forums/topic/265231-in-denile/page-2

 

Now for anyone who is interested I took the same Forth code, which is very bad Forth style and re-organized in the way the Forth programs are written.

You can see this "factoring" of the program into small parts in Lee's code.

The stack stuff might make your head spin, so this shows Forth style but still using some Variables and strings like the original did.

All we did was re-organize how to think about it. That's important for programming in any language if you move from BASIC.

 

I think the thing to know is Chuck Moore believes every Forth WORD (sub-routine) should be so simple that there was no question what it does.

And the name should clearly let you know what it's going to do.

 

So with those to things in mind here is deNile using more "WORDS" which is how Forth programs are made.

 

*Look at how many lines were removed by creating a WORD to clip values and then using that clipping word to make automatic functions to control the water characters.

And hopefully we get more clarity when we are done because we have created a little language to solve the problem.

 

( Let me know if this is helpful or not

I can shut-up anytime, but with a venue to teach about Forth I can be chatterbox)

 

 

 

\ DENILE.BAS translate using Forth style factoring

\ dialect: CAMEL99 Forth V 0.9 TRAINER
\          with extensions to help TI-BASIC PROGRAMMERS

                                       \ un-rolled this loop
HEX" 0102 0709 1F24 7F92"  65 CHARDEF  \ 20 FOR L=65 TO 70 )
HEX" 8040 E090 F824 FE49"  66 CHARDEF  \ 30 READ Q$        )
HEX" FF92 FF24 FF92 FF49"  67 CHARDEF  \ 40 CALL CHAR(L,Q$)
HEX" AA55 4489 2002 4801"  68 CHARDEF  \ 50 NEXT L  )
HEX" 0002 1735 7CFC 44AA"  69 CHARDEF  \  Camel
HEX" 0008 081C 2A08 1414"  70 CHARDEF  \  little man

\ water waves
HEX" 0083 C7AE FBEF BDF7" 104 CHARDEF  \ 70 CALL CHAR(104,"0083C7AEFBEFBDF7")
HEX" 0007 8F5D F7DF 7BEF" 105 CHARDEF  \ 80 CALL CHAR(105,"00078F5DF7DF7BEF")
HEX" 000E 1FBA EFBF F6DF" 106 CHARDEF  \ 90 CALL CHAR(106,"000E1FBAEFBFF6DF")
HEX" 001C 3E75 DF7F EDBF" 107 CHARDEF  \ 100 CALL CHAR(107,"001C3E75DF7FEDBF")
HEX" 0038 7CEA BFFE DB7F" 108 CHARDEF  \ 110 CALL CHAR(108,"00387CEABFFEDB7F")
HEX" 0070 F8D5 7FFD B7FE" 109 CHARDEF  \ 120 CALL CHAR(109,"0070F8D57FFDB7FE")
HEX" 00E0 F1AB FEFB 6FFD" 110 CHARDEF  \ 130 CALL CHAR(110,"00E0F1ABFEFB6FFD")
HEX" 00C1 E357 FDF7 DEFB" 111 CHARDEF  \ 140 CALL CHAR(111,"00C1E357FDF7DEFB")

\ ALL variables must be defined before they are used
VARIABLE T
VARIABLE Y

32 STRING: A$    \ STRING: is not standard Forth. We made it to for you.
32 STRING: B$    \ *BTW Strings can have any name
32 STRING: C$

: TABS ( n -- ) 0 ?DO SPACE LOOP ;  \ we don't have a TAB word so make one

: PUSH-UPS ( n -- ) 0 ?DO CR LOOP ; \ do the same for new lines

\ build the program "in our own words"
: SAND  ( -- )  0 23 68 32 HCHAR ; \ 250 CALL HCHAR(24,1,68,32)
: CAMEL ( -- )  0 22 69 1  HCHAR ; \ 260 CALL HCHAR(23,1,69)
: MAN   ( -- )  1 22 70 1  HCHAR ; \ 270 CALL HCHAR(23,2,70)

: PEAK  ( -- )  CR 15 TABS ." AB" ; \ 180 PRINT TAB(X+1);"AB"

: BRICKS ( -- )
      C$ " CC" &       C$ $!    \ 190 C$=C$&"CC" ( " needs a space, '&' is RPN 
     " A" C$ &  " B" & B$ $! ;  \ 200 B$="A"&C$&"B"

: WALLS ( -- )
    0 13 ?DO
         BRICKS
         CR  I 1+ TABS B$ PRINT \ 210 PRINT TAB(X);B$
                                \ 220 C=C+1
                                \ 230 X=X-1
      -1 +LOOP                  \ 240 IF C=13 THEN 250 ELSE 190
;

: PYRAMID ( -- )
     A$ =""  B$ =""  C$ =""   \ reset the strings
                              \ 160 X=13
                              \ 170 C=1
    PEAK
    WALLS
    SAND  
    CAMEL MAN
    3 PUSH-UPS               \ 280 PRINT
                             \ 290 PRINT
                             \ 295 PRINT
    SAND                     \ 296 CALL HCHAR(24,1,68,32)
;

: WAVECLIP ( n -- n') DUP 111 > IF DROP 104 THEN ;  \ clip n to valid wave char

: NEXT-T ( -- n) T @ 1+ WAVECLIP DUP T ! ; \ inc T by 1 & clip
: NEXT-Y ( -- n) Y @ 2+ WAVECLIP DUP Y ! ; \ inc Y by 2 & clip

: INIT-WAVES  ( -- )        \ 300 T=104
    104 DUP T !  2+ Y ! ;   \ 310 Y=106

: RIVER    \ flow the river loop
    INIT-WAVES
    BEGIN                    \ 320 T=T+1
                             \ 330 IF T>111 THEN 340 ELSE 350
                             \ 340 T=104
                             \ 350 Y=Y+2
                             \ 360 IF Y>111 THEN 370 ELSE 380
                             \ 370 Y=104
       0 21  NEXT-T 32 HCHAR \ 380 CALL HCHAR(22,1,T,32)
       0 22  NEXT-Y 32 HCHAR \ 390 CALL HCHAR(23,1,Y,32)
       100 MS
    KEY?
    UNTIL                   \ 400 GOTO 320
;

: RUN   \ Forth doesn't have RUN so we make one
   CLEAR                  \ 10 CALL CLEAR
   13 6 5 COLOR           \ 150 CALL COLOR[10,6,5]
   PYRAMID                \ 500 GOSUB PYRAMID
   RIVER                  \ 600 GOSUB RIVERFLOW
;

 


Edited by TheBF
  • Like 2
Link to comment
Share on other sites

OK I promise this is the last one for Primer #2.

 

Now we have removed all the BASIC comments to make space on the page.

With that space we have shown you some alternative ways to format Forth code

to make it read nicely. Forth source code is free form so you can express

yourself in whatever way works for you.

 

We also introduce:

 

COLORS ( set# set# FG,BG -- )

SET# ( char -- set$)

VREAD ( vdp-addr cpu-addr cnt -- ) (also known as VMBR)

VWRITE ( cpu-addr vdp-addr cnt -- ) ( a.k.a VMBW )

 

With those words we create a way to save the all the patterns and restore them when the program ends.

And we include a little TI BASIC "HONK" so it's official.

 

 

 

\ DENILE #3 MORE factoring and Forth Style

\ dialect: CAMEL99 Forth V 0.9 TRAINER
\          with extensions to help TI-BASIC PROGRAMMERS

\ give names to the pattern data
\ Where BASIC uses strings, we will use HEX integers
\ Each word drops 4 numbers onto the stack for CHARDEF
\ to pickup
HEX
: RSLOPE      ( -- n1 n2 n3 n4)  0102 0709 1F24 7F92 ;
: LSLOPE      ( -- n1 n2 n3 n4)  8040 E090 F824 FE49 ;
: STONE       ( -- n1 n2 n3 n4)  FF92 FF24 FF92 FF49 ;
: SAND        ( -- n1 n2 n3 n4)  AA55 4489 2002 4801 ;
: CAMEL       ( -- n1 n2 n3 n4)  0002 1735 7CFC 44AA ;
: LITTLE-MAN  ( -- n1 n2 n3 n4)  0008 081C 2A08 1414 ;

: WAVE1       ( -- n1 n2 n3 n4)  0083 C7AE FBEF BDF7 ;
: WAVE2       ( -- n1 n2 n3 n4)  0007 8F5D F7DF 7BEF ;
: WAVE3       ( -- n1 n2 n3 n4)  000E 1FBA EFBF F6DF ;
: WAVE4       ( -- n1 n2 n3 n4)  001C 3E75 DF7F EDBF ;
: WAVE5       ( -- n1 n2 n3 n4)  0038 7CEA BFFE DB7F ;
: WAVE6       ( -- n1 n2 n3 n4)  0070 F8D5 7FFD B7FE ;
: WAVE7       ( -- n1 n2 n3 n4)  00E0 F1AB FEFB 6FFD ;
: WAVE8       ( -- n1 n2 n3 n4)  00C1 E357 FDF7 DEFB ;

\ ====================================================
\ *NEW* save & restore character set
\ we will use some low expansion RAM at address HEX 2000

HEX 2000 CONSTANT CHARBUFF     \ it's just that easy

\ PDT is the pattern descriptor table in VDP RAM
\ 800 is how many bytes it contains (2K bytes)
\ VREAD is VMBR in TI speak
\ VWRITE is VMBW in TI speak

: SAVE-CHARSET    ( -- )
         ( from) PDT ( to) CHARBUFF 800 ( bytes) VREAD ;

\ *NEW* "COLORS" restores mulitple charsets wow!
: RESTORE-CHARSET ( -- )
         ( from) CHARBUFF ( to) PDT 800 ( bytes) VWRITE
          0 16 2 1 COLORS ;

DECIMAL
\ ====================================================
\ assign patterns to characters
\ this is a big defintion but it is like a table of data
\ so it is reads easily but it is CODE.
\ *NEW* SET# calculates the set# for us

: CHANGE-CHARS ( -- )

   SAVE-CHARSET
\  pattern   char          set#      colors
\ --------   ----          ----     ----------
   RSLOPE      65 CHARDEF   65 SET# 11 1 COLOR
   LSLOPE      66 CHARDEF
   STONE       67 CHARDEF
   SAND        80 CHARDEF   80 SET# 2 12 COLOR
   CAMEL       88 CHARDEF   88 SET# 2  1 COLOR
   LITTLE-MAN  89 CHARDEF
   WAVE1      104 CHARDEF  104 SET# 5  6 COLOR
   WAVE2      105 CHARDEF
   WAVE3      106 CHARDEF
   WAVE4      107 CHARDEF
   WAVE5      108 CHARDEF
   WAVE6      109 CHARDEF
   WAVE7      110 CHARDEF
   WAVE8      111 CHARDEF
\ --------------------------------------------
;

\ ====================================================
\ wave control variables
VARIABLE T
VARIABLE Y

\ pyramid building strings
32 STRING: A$
32 STRING: B$
32 STRING: C$

: TABS ( n -- ) 0 ?DO SPACE LOOP ;

: PUSH-UPS ( n -- ) 0 ?DO CR LOOP ;

\ DOT is the Forth word to print a number.
\ These words begin with '.' to tell us they print something
\ A Forth style you can use if you want to
: .SAND  ( -- )  0 23 80 32 HCHAR ;
: .CAMEL ( -- )  0 22 88 1  HCHAR ;
: .MAN   ( -- )  1 22 89 1  HCHAR ;

: .PEAK  ( -- )  CR 15 TABS ." AB" ;

\ this part is still very BASIC style
\ We don't need to use strings for this
: .BRICKS ( -- )
      C$ " CC" &       C$ $!    \ 190 C$=C$&"CC"
     " A" C$ &  " B" & B$ $! ;  \ 200 B$="A"&C$&"B"

: .WALLS ( -- )
    A$ =""  B$ =""  C$ =""
    0 13 ?DO
         .BRICKS
         CR  I 1+ TABS B$ PRINT
     -1 +LOOP ;

\ ====================================================
\ Horizontal format code reads more like human language. 
\ Some people code Forth this way

: .PYRAMID ( -- )  .PEAK .WALLS .SAND .CAMEL .MAN  3 PUSH-UPS .SAND ;

: WAVECLIP   ( n -- n') DUP 111 > IF DROP 104 THEN ;  \ clip n to valid wave char
: NEXT-T     ( -- n)  T @ 1+  WAVECLIP DUP  T ! ;    \ inc T by 1 & clip
: NEXT-Y     ( -- n)  Y @ 2+  WAVECLIP DUP  Y ! ;    \ inc Y by 2 & clip

: INIT-WAVES ( -- ) 104 DUP T !  2+ Y !  ;

: .WAVES     ( -- )  0 21 NEXT-T 32 HCHAR    0 22 NEXT-Y 32 HCHAR ;

: .RIVER     ( -- ) INIT-WAVES  BEGIN  .WAVES  200 MS  KEY? UNTIL ;

: RUN        ( -- ) 
             15 SCREEN
             CHANGE-CHARS
             CLEAR  .PYRAMID  .RIVER
             HONK RESTORE-CHARSET ;

 

 

  • Like 1
Link to comment
Share on other sites

These Forth tutorials are great, @Willsy! Just when you decided to start #2, I was in surgery for a complete right knee replacement. Today is the first time I have felt up to doing anything much online. I am sorry I was not here to participate from the start. I still may be a little slow to get back in the saddle. Again, wonderful effort. :thumbsup: :thumbsup: :thumbsup:

 

...lee

Link to comment
Share on other sites

These Forth tutorials are great, @Willsy! Just when you decided to start #2, I was in surgery for a complete right knee replacement. Today is the first time I have felt up to doing anything much online. I am sorry I was not here to participate from the start. I still may be a little slow to get back in the saddle. Again, wonderful effort. :thumbsup: :thumbsup: :thumbsup:

 

...lee

 

I wondered why you were not here to correct all my mistakes. :-)

 

So happy that you are back.

 

Get back to 110% soon Lee.

 

You are in my thoughts.

 

B

  • Like 1
Link to comment
Share on other sites

Ah! Great to "see" you back here, Lee. I had wondered why you hadn't chimed in. Hope you're back up to full-speed soon!

 

#3 coming soon (I might write it up tonight). If you or Bruce want to add an article just go right ahead. Use "Forth: some title (#number)" as the format for the title, that way we can easily search for them later.

  • Like 2
Link to comment
Share on other sites

Thanks, Guys! It will not be long. I will have to think about starting one of these tutorials---I don't know. You fellows' teaching style is so much better than mine! I cannot seem to avoid too much detail, for one thing. Maybe I will limit my participation for a while to augmentation(!?). :grin:

 

...lee

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...