Jump to content
IGNORED

Forth Tutorials


matthew180

Recommended Posts

Thanks! This definitely does the trick.

Now another question: How does one make the address of a variable be the same as the address of another? What I'm trying to do is create a pointer to a matrix in a definition, and the only thing I could come up with was something like this:

0 VARIABLE MATRIX 16 ALLOT
0 VARIABLE POINTER
: TEST POINTER ! POINTER @ 18 + POINTER @ DO I @ . 2 +LOOP ;

then I would call TEST as such

MATRIX TEST

and get a listing of the matrix' contents. TEST obviously could be reused for any 18 cell matrix as long as the matrix address is passed on to it.

There has go to be a less clumsy way to do this.

Edited by Vorticon
Link to comment
Share on other sites

Yes. Have a look at my TF code further up this thread. Notice that the offsets are different for the assembly definition and Forth equivalent? That's because if you implement it in Forth, then the act of calling that word in your subsequent loop invokes a return stack push (the call to DOCOL) so that EXIT can return to where it was in your loop. Thus, everything has "moved down" by one stack position if using a colon definition. Should be the same in TIF I reckon as both systems are pretty much 'classical' Indirect Threaded implementations.

Link to comment
Share on other sites

Thanks! This definitely does the trick.

Now another question: How does one make the address of a variable be the same as the address of another? What I'm trying to do is create a pointer to a matrix in a definition, and the only thing I could come up with was something like this:

0 VARIABLE MATRIX 16 ALLOT
0 VARIABLE POINTER
: TEST POINTER ! POINTER @ 18 + POINTER @ DO I @ . 2 +LOOP ;

then I would call TEST as such

MATRIX TEST

and get a listing of the matrix' contents. TEST obviously could be reused for any 18 cell matrix as long as the matrix address is passed on to it.

There has go to be a less clumsy way to do this.

 

Yes. You don't need the variable at all. The stack is your friend. Only resort to variables when not doing so would result in 'stackrobatics' (i.e excessive and ugly (and time consuming) stack juggling to get you stack in the order you need it).

 

In this case, a variable is going over the top a bit. We just need the start address of the array and we can work everything else out using that:

0 VARIABLE MATRIX 16 ALLOT

: TEST ( addr -- ) DUP 18 + SWAP DO I @ . 2 +LOOP ;

Ta dah!

Link to comment
Share on other sites

Here's another way (I would argue a better way) to skin the cat. If TIF doesn't have CELLS then define it as follows:

 

: CELLS ( #cells -- #bytes) 2* ;

 

0 VARIABLE MATRIX 8 CELLS ALLOT
: .MATRIX ( addr -- ) 9 0 DO DUP I CELLS + @ . LOOP DROP ;

 

Here I've called the word .MATRIX. In Forth, as you know, . (dot) is associated with displaying numeric values. Therefore we use the dot in the name. Much better than the extremely generic and oft over-used TEST ;)

Link to comment
Share on other sites

Yes. You don't need the variable at all. The stack is your friend. Only resort to variables when not doing so would result in 'stackrobatics' (i.e excessive and ugly (and time consuming) stack juggling to get you stack in the order you need it).

 

In this case, a variable is going over the top a bit. We just need the start address of the array and we can work everything else out using that:

0 VARIABLE MATRIX 16 ALLOT

: TEST ( addr -- ) DUP 18 + SWAP DO I @ . 2 +LOOP ;

Ta dah!

 

Thanks Willsy, but I think I should clarify as I probably used too simple an example. I am aware of the above, but my real world needs involve multiple arrays being passed to a function without necessarily being part of a DO loop, Which is why I am looking for a way assign the address of a variable to that of matrix.

In other words, using my example above, POINTER would leave the address of the matrix on the stack instead of using POINTER ! and POINTER @ to do the same...

Link to comment
Share on other sites

Perhaps you want an array of matrix addresses? (Not tested in TIF)

0 VARIABLE MATRIX0 16 ALLOT
0 VARIABLE MATRIX1 16 ALLOT
0 VARIABLE MATRIX2 16 ALLOT
0 VARIABLE MATRIX3 16 ALLOT

CREATE MATRICES MATRIX0 , MATRIX1 , MATRIX2 , MATRIX3 ,
: SELECT ( n -- matrix_addr) 2* MATRICES + @ ;
: .MATRIX ( matrix_id -- ) SELECT DUP 18 + SWAP DO I @ . 2 +LOOP ;

Now you can select the address of a matrix by passing a value to SELECT. SELECT gives you the address of the matrix. So, if you want matrix 0:

 

0 SELECT

 

Leaves the address of matrix 0 on the stack.

 

1 SELECT

 

Leaves the address of matrix 1 on the stack.

 

.MATRIX above uses select. So, if you want to see matrix 3:

 

3 .MATRIX

 

Note that we still haven't used a variable as a pointer. SELECT gives you your pointer.

 

Is that kind of what you were getting at?

Link to comment
Share on other sites

Yes, you're right, it is the 5th value on the stack. I don't recall seeing I' mentioned in the TIF manual. I might be able to come up with a workaround to avoid having 3 nested loops, but it won't be as neat. Alternatively, I could create a word like this:

 

: INDEX3 R> R> R> R> R> J' ! >R >R >R >R >R ;

 

where J' is defined as a simple variable. I'm not sure what kind of performance hit using this scheme repeatedly will result in, but it's worth experimenting with. It's becoming clear to me that I will have to eventually learn to use the TIF assembler at some point...

 

A few things:

  1. You used up one of the values taken from the return stack with R> J' ! , so your code will cause a stack underflow and corrupt the return stack.
  2. J' as a variable will likely not be up to date when you check it. It is better to define it as a word that gets the current value every time it is executed.
  3. To keep the loop metaphor consistent, you should use a different letter for the third loop, something like K . J' implies the 2nd loop's limit, not the 3rd loop's index.
  4. You will want to leave a value on the stack when you execute INDEX3 or K if you define it as suggested in (2).

Perhaps (The following code does not work and will also corrupt the return stack! This is explained in later posts.)

 

: INDEX3
R> R> R> R>
R
>R >R >R >R
; or

:
K
R> R> R> R>
R
>R >R >R >R
;

Also, I' is not defined in TI Forth, it is mentioned (and defined) in "Appendix C" of the TI Forth Instruction Manual because the first edition of Brodie's Starting Forth explains it on pages 110 and 123.

 

...lee

 

I feel compelled to re-visit the above because I will have misled anyone who read it. @Vorticon, your suggested code will work with the addition of R to read the 6th item down, necessary due to DOCOL's use of the return stack as @Willsy explained; but, as I indicated, I would use K , not J' :

 

: INDEX3 R> R> R> R> R>
R
K !
>R >R >R >R >R ;

 

It is far messier to rescue my suggested code above:

 

:K R> R> R> R> R> R SWAP >R SWAP >R SWAP >R SWAP >R SWAP >R ;

 

As you can see from a few earlier posts, the TIF assembler solution is probably the best, with the use of the TIF return stack pointer's address (831Ch) in a high-level definition as a not too distant second.

 

Sorry for the confusion. |:)

 

...lee

Link to comment
Share on other sites

I tried CREATE in TIF but doesn't seem to do what I thought it would do. It doesn't give an error, but it doesn't seem to create a word either...?

 

CREATE does, in fact, create a word in the CURRENT vocabulary in TIF. The smudge bit (comparable to TF's hidden bit), however, is toggled on to prevent the word's being found. In TIF, CREATE is usually used by other defining words like <BUILDS . If you want to find the word you created with CREATE , you can toggle the smudge bit with SMUDGE (comparable to TF's HIDDEN ). CREATE creates the word's dictionary header with the code field pointing at the word's parameter field (actually HERE after creation) and the smudge bit set.

 

Even without toggling the smudge bit off, you can see that something was added to the dictionary by checking HERE before and after using CREATE .

 

...lee

Link to comment
Share on other sites

Perhaps you want an array of matrix addresses? (Not tested in TIF)

0 VARIABLE MATRIX0 16 ALLOT
0 VARIABLE MATRIX1 16 ALLOT
0 VARIABLE MATRIX2 16 ALLOT
0 VARIABLE MATRIX3 16 ALLOT

CREATE MATRICES MATRIX0 , MATRIX1 , MATRIX2 , MATRIX3 ,
: SELECT ( n -- matrix_addr) 2* MATRICES + @ ;
: .MATRIX ( matrix_id -- ) SELECT DUP 18 + SWAP DO I @ . 2 +LOOP ;

Now you can select the address of a matrix by passing a value to SELECT. SELECT gives you the address of the matrix. So, if you want matrix 0:

 

0 SELECT

 

Leaves the address of matrix 0 on the stack.

 

1 SELECT

 

Leaves the address of matrix 1 on the stack.

 

.MATRIX above uses select. So, if you want to see matrix 3:

 

3 .MATRIX

 

Note that we still haven't used a variable as a pointer. SELECT gives you your pointer.

 

Is that kind of what you were getting at?

Yes, this is exactly what I'm looking for. Thanks!

That said, I do have a couple of questions about this:

- Since the matrices were defined prior to create, wouldn't CREATE create different addresses for the items following it?

- Why is , needed?

 

I'll let Lee tell us why CREATE is misbehaving in TIF :)

Link to comment
Share on other sites

CREATE does, in fact, create a word in the CURRENT vocabulary in TIF. The smudge bit (comparable to TF's hidden bit), however, is toggled on to prevent the word's being found. In TIF, CREATE is usually used by other defining words like <BUILDS . If you want to find the word you created with CREATE , you can toggle the smudge bit with SMUDGE (comparable to TF's HIDDEN ). CREATE creates the word's dictionary header with the code field pointing at the word's parameter field (actually HERE after creation) and the smudge bit set.

 

Even without toggling the smudge bit off, you can see that something was added to the dictionary by checking HERE before and after using CREATE .

 

...lee

 

OK this is a little beyond my pay grade unfortunately, at least for now :? Nonetheless, how would Willsy's code be modified to make it work as intended?

Link to comment
Share on other sites

Yes, this is exactly what I'm looking for. Thanks!

That said, I do have a couple of questions about this:

- Since the matrices were defined prior to create, wouldn't CREATE create different addresses for the items following it?

- Why is , needed?

 

I'll let Lee tell us why CREATE is misbehaving in TIF :)

 

CREATE is not misbehaving in TIF. It is behaving differently as I explained in my last post. TIF, as you will remember, is written to the fig-Forth "standard", which predates even Forth-79. CREATE in TF is Forth-83 compliant. The difference (other than the smudge/hidden bit) is that TIF has the CFA pointing to the word's PFA, which will execute what's in the PFA when the word is executed unless changed by some other defining word that may use CREATE , and TF has the CFA pointing to a routine in TF that will leave the word's PFA on the stack when the word is executed. Clear as mud, right? :sad:

 

...lee

Link to comment
Share on other sites

OK this is a little beyond my pay grade unfortunately, at least for now :? Nonetheless, how would Willsy's code be modified to make it work as intended?

 

Simply this in TIF:

MATRIX0 VARIABLE
MATRICES
MATRIX1
,
MATRIX2
,
MATRIX3
,

 

...lee

Edited by Lee Stewart
Link to comment
Share on other sites

- Since the matrices were defined prior to create, wouldn't CREATE create different addresses for the items following it?

 

No. Take another look. It's much simpler than you think it is. Like most things in Forth.

 

0 VARIABLE MATRIX0 16 ALLOT

0 VARIABLE MATRIX1 16 ALLOT

0 VARIABLE MATRIX2 16 ALLOT

0 VARIABLE MATRIX3 16 ALLOT

CREATE MATRICES MATRIX0 , MATRIX1 , MATRIX2 , MATRIX3 ,

 

The line above two logically distinct sections/phases. Hence I've group them by colour.

 

So, the red part first: CREATE executes. It reads ahead in the input buffer (the stuff you typed in) and creates a dictionary entry. The behaviour of that word in the dictionary (MATRICES) is to return the address of it's body. Note also, that the compile address (called HERE) is also pointing at the body of matrices, but it's empty at the moment. CREATE has no further involvement. It's done.

 

Then MATRIX0 executes. It's a variable, so it returns its address on the stack. Then comma executes. Comma takes what's on the stack, and compiles it to memory (it gets compiled at the address pointed to by HERE) and advances HERE. So, the address of MATRIX0 gets compiled into MATRICES body address. They kind of overlap. I've shown that with an underlined section.

 

Next, the other variables are "comma'd" into memory in the same way. Thus, we have built up an array of addresses all next to each other. Executing MATRICES returns the address of it's BODY, which has the address of MATRIX0 in it. By adding an offset to this body address, we can access any of the addresses in the list.

 

Make sense?

 

- Why is , needed?

 

See above.

 

To see what comma does, start up TF in Classic99 and open classic99's debugger. In the CPU window, set the address to B000

 

Now, in TF, set HERE (the compilation address) to $B000 with this code:

 

$9000 H !

 

Now, type the following:

 

$994A , $BEEF , $FACE ,

 

See?

 

Now type HERE $.

 

See?

 

Stick around. We'll make a Forth guru of you yet. Part of the complexity of understanding Forth is understanding just how astonishingly simple it is. It takes a while to sink in! ;)

Link to comment
Share on other sites

Yes, this is exactly what I'm looking for. Thanks!

That said, I do have a couple of questions about this:

- Since the matrices were defined prior to create, wouldn't CREATE create different addresses for the items following it?

- Why is , needed?

 

I'll let Lee tell us why CREATE is misbehaving in TIF :)

 

Sorry, I missed your other questions:

 

A word created in TF with CREATE ( MATRICES here) will establish the word's header in the dictionary and advance HERE to the next dictionary position, which is the address the created word will leave on the stack when it is executed.

 

Words created with VARIABLE leave their parameter field addresses on the stack when they are executed.

 

Comma ( , ) compiles the number on the stack into the dictionary at HERE and advances HERE by one cell.

 

Because the definition of MATRICES consists of its header followed immediately by the addresses, in order, of MATRIX0 , MATRIX1 , MATRIX2 and MATRIX3 , executing MATRICES will push onto the stack the address of its parameter field, which contains the address of MATRIX0 . To get the other addresses, you simply add 2 to that address for each successive address. The address of MATRIX3 , for example, would be obtained by

 

MATRICES 6 +
@ <---I really should be more careful!

 

There's nothing stopping you, of course, from getting addresses deeper into MATRICES , but they will point to a part of memory beyond your setup of MATRICES and be meaningless---programmer, beware!

 

...lee

Edited by Lee Stewart
Link to comment
Share on other sites

Yes, this is exactly what I'm looking for. Thanks!

That said, I do have a couple of questions about this:

- Since the matrices were defined prior to create, wouldn't CREATE create different addresses for the items following it?

- Why is , needed?

 

I'll let Lee tell us why CREATE is misbehaving in TIF :)

 

 

I was a little quick on the trigger with Post #241. MATRICES contains a list of addresses, so to get the address of MATRIX3 in MATRICES , you would execute the following:

 

MATRICES 6 + @

 

I forgot the @ to fetch the address in Post #241.

 

...lee

Link to comment
Share on other sites

  • 3 weeks later...

So here's a thornier problem I have been battling with in TI Forth:

 

I'd like to request user terminal input for the name of a previously defined word, and then find a way to execute that word.

I can do the first part using EXPECT, storing the name in an array. What I can't figure out is how to get TIF to execute that name... At first I thought I'll place the ASCII codes of the name on the stack and then do an INTERPRET, but that did not work. I'm still not completely clear on what is meant by the input stream.

For example, I have the word TEST previously defined in a colon definition.

I use EXPECT to request the user to type the name TEST at the terminal, and the name TEST is stored in an array. I can get that far successfully.

Now how do I get TEST to execute from there without any further input from the user???

 

I know it's an odd request, but I assure you there is a perfectly good rationale behind it ( more on this later ) :)

Edited by Vorticon
Link to comment
Share on other sites

OK I'm going to answer my own question :-D

After scouring Starting Forth for some solution, it dawned on me that the input stream starts at S0. My error was storing the user input in an array when I could simply do a S0 @ EXPECT and have that input right at the beginning of the input stream.

From there, re-initializing IN with 0 IN ! followed by INTERPRET executed the user input. voila! I'll give myself a thumbs up :thumbsup: :-D

I still think assembly is simpler...

Link to comment
Share on other sites

I can't answer for TI Forth, I'm afraid.

 

However, you probably need to look up the words WORD and FIND.

 

In TF you could do it like this: (it is probably very similar in TIF).

 

: RUN-WORD ( -- )

CR ." Enter a word name:"

TIB @ 30 EXPECT

CR

BL WORD FIND 0= IF

." Word not found" DROP

ELSE

EXECUTE

THEN

;

 

Check out the glossary for an explanation of the words.

 

 

But in the very latest build of TF (which I haven't posted yet I don't think... can't remember) there is a word called EVALUATE. You simply give it a string (i.e. an address and length on the stack) can call EVALUATE and it will execute *whatever* it finds in there. There are no restrictions. For example, you could do:

 

S" : TEST 1 2 3 ;" EVALUATE

 

And you will end up with a word in dictionary called test, just as you would expect!

 

Nice!

 

Link to comment
Share on other sites

OK I'm going to answer my own question :-D

After scouring Starting Forth for some solution, it dawned on me that the input stream starts at S0. My error was storing the user input in an array when I could simply do a S0 @ EXPECT and have that input right at the beginning of the input stream.

From there, re-initializing IN with 0 IN ! followed by INTERPRET executed the user input. voila! I'll give myself a thumbs up :thumbsup: :-D

I still think assembly is simpler...

 

In TI Forth, it is the terminal input buffer (address always in TIB ) that starts at the address in S0 , not necessarily the input stream. The input stream can come from a block, as well. The system knows which by the value in BLK . Only if BLK contains 0, is the terminal input buffer the input stream. S0 is also the address where the parameter stack starts and grows down in memory away from the TIB. TIB and S0 happen to contain the same address at system startup; but, that situation doesn't have to persist. You can change S0 to put the stack somewhere else. For that reason, I would use TIB to pass the TIB address.

 

Another thing you might be interested to know is that the TIB is only 82 bytes long at the top of high memory. That length is why that TI Forth won't let you type a longer line than 80 characters before reaching around you and tapping <Enter> for you, so to speak.

 

I don't mean to challenge your solution. I think it's great! I would just change to using TIB . Oh, and don't forget that you need a number of characters to EXPECT and that it should never exceed 80 in TI Forth if you want to use the TIB and you might as well stick to 31 since that's the maximum size a word in TI Forth can be.

 

...lee

Link to comment
Share on other sites

I can't answer for TI Forth, I'm afraid.

 

However, you probably need to look up the words WORD and FIND.

 

In TF you could do it like this: (it is probably very similar in TIF).

 

: RUN-WORD ( -- )

CR ." Enter a word name:"

TIB @ 30 EXPECT

CR

BL WORD FIND 0= IF

." Word not found" DROP

ELSE

EXECUTE

THEN

;

 

Check out the glossary for an explanation of the words.

...

 

Well, EXPECT and EXECUTE work the same way, but WORD and FIND do not. In fact, there is no FIND in TI Forth. The equivalent in TI Forth to FIND is probably (FIND) . The TI Forth word -FIND actually calls WORD and then calls (FIND) twice, once to search the CONTEXT vocabulary and, if that fails, the CURRENT vocabulary.

 

WORD in both Forths parses the input stream; but, whereas in TurboForth, WORD leaves an address and text-length, in TI Forth, it leaves nothing on the stack. It does, however, copy the text of the word to HERE with its length as the first byte without updating HERE .

 

(FIND) expects 2 addresses on the stack: the address of the length byte of the word to search for (immediately followed by the word just as WORD leaves it at HERE ) and the name field address (NFA) of the first word in the dictionary to start the search, which also starts with the length byte. If the word is not found, FALSE is left on the stack. If the word is found, its parameter field address (PFA), length byte and TRUE are left on the stack. These post-execution stack effects also obtain for -FIND . If you wish to execute the word that was found, you will probably have already popped TRUE off of the stack with an IF and DROPped or otherwise processed the length byte to get at the PFA; but, you can't use EXECUTE on the PFA---you need the code field address (CFA). It's easy: You just issue CFA to convert the PFA on the stack to the word's CFA.

 

The TI Forth equivalent of the above RUN-WORD is:

 

: RUN-WORD ( -- )

CR ." Enter a word name:"

TIB @ 30 EXPECT

CR 0 IN !

-FIND 0= IF

." Word not found"

ELSE

DROP CFA EXECUTE

THEN

;

 

The " 0 IN ! " are necessary because the pointer to the next character of the TIB has moved to a point immediately after the just-entered word, so what gets copied to HERE will not be what the user typed unless the pointer contained in IN is reset.

 

...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...