Computer One FORTH

Anything QL Software or Programming Related.
User avatar
polka
Trump Card
Posts: 196
Joined: Mon Mar 07, 2011 11:43 am

Computer One FORTH

Post by polka »

Computer One FORTH

If you have Computer One Forth (release 2.0), you need this :

Code: Select all

.( tests and patch for some bugs in ComputerOne FORTH rel 2.0 )

hex
: PATCH
671A 0684 ! 3400 0688 ! B342 068C ! 6A0C 068E ! 4A41 0690 !
6708 0692 ! D240 0694 ! 4841 0696 ! 5341 0698 ! 4841 069C !
4E75 069E ! 0A56 09F8 ! FFFF 09FA ! 60B0 09FC ! D1B5 0DFC ! ;
decimal

: /mod. 8 -8 do i . i over /mod . . cr loop drop ;

: not0=. 8 -8 BINARY do i u. i not u. i 0= u. cr loop DECIMAL ;

Comments (do not compile this !) :
you may use the test words /mod. and not0=. before and after
using PATCH ; this way :

n /mod. (n a one digit integer) will give a list of quotients
and remainders, and before PATCHing you will notice irregular
behaviour of the flooring around 0 (zero). This bug is serious

not0=. (nothing on the stack is needed) will show that before
PATCHing the words NOT and 0= yield the same results ; they
should not ! 0= yields a boolean with 0 meaning FALSE and -1
TRUE, whereas NOT is bitwise negation of a 16 bits integer.

To test for another PATCHed bug with word D+!, you may type :
2VARIABLE foo
OK 1. foo 2!
OK foo 2@ D.
1 OK 2. foo D+!
OK foo 2@ D.
3 OK  (you get '3' after patching ; before, you get garbage !)
When some twenty years ago, I started to program "seriously" with Forth on the QL, I discovered a few bugs in my Computer One release 2.0 of this very elegant (and frugal) development tool.

The most serious bug (that I found first) affected all the words that implemented some kind of integer division ( / mod /mod ... etc). Somehow, around zero, the "flooring" seemed not right.

In such cases, the Forth programmer has an alternative : either, he programs "high level words" that check for the error condition and work "around" it, giving the new words the same name as the old buggy ones, thus hiding them ; or he digs into the machine code and looks for a way to "patch" the bug at the lowest level.

The first way adds some exec time penalty each time you call the functions, and takes some room.

The second way is more radical but also more difficult : you have to disassemble "by hand" machine code, find the bug, and invent a way to correct it by using no more than the memory space of the original code. But then, you are not done ! after that, you have to check all the higher level routines that use the code, to ascertain that your correction does not affect adversively their execution (that may have been "twitched" to accomodate the bug). Fortunately, I could do it, and the correction has no influence on higher level words, because the flooring (and remainder) was affected only for division of negative numbers that yielded a quotient of 0 (should have been -1). Integer division is seldom used for negative numbers (because programmers obviously are not familiar with how flooring will be handled, so they usually avoid these situations).

Afterward, I discovered two minor bugs affecting only two words :

"NOT" in Forth83 is defined as "bitwise negation" of a 16 bits number, whereas is this release, it behaved like "0=", a boolean test. In Forth (like in C) there is no special boolean type : instead, any non zero value is taken as TRUE and zero is equated to FALSE (by the testing words like "IF" etc). But the "boolean" functions (like "0=" etc) always return -1 for TRUE. So, obviously, if NOT behaves like 0=, it has no use. I corrected this too, but was very uncertain that this one would not have adverse influence of a lot of higher level words. Fortunately, it did not !

"D+!" is a word that nobody would never use but once I did, and had some hard time finding that it produced garbage. To correct it, I had only to patch one instruction.

I used Forth so intensively that I am almost sure that no other bug will remain after you patched these three. You should use the "test words" to see the bugs before patching and after, to see if they were corrected. If everything is OK, FORGET PATCH and SAVE mdv2_FORTH, your corrected version (that will not replace the old one, unless you want).

Paul


May the FORTH be with you !
POLKa
RWAP
RWAP Master
Posts: 2839
Joined: Sun Nov 28, 2010 4:51 pm
Location: Stone, United Kingdom
Contact:

Re: Computer One FORTH

Post by RWAP »

Thanks Paul - I have run the test and patch here as well.

The original D+! example output 131072 at the end

However, the patched version gives 1 (not 3 as stated in the example).

I have confirmed the issues with the /mod and not0=

Rich


User avatar
polka
Trump Card
Posts: 196
Joined: Mon Mar 07, 2011 11:43 am

Re: Computer One FORTH

Post by polka »

The last patch (for D+!) would only replace at address 0DFC the 16 bits instruction D175 by D1B5. You should check with your "old" (original) version of the Computer One microdrive that at address 0DFC, you find D175 indeed. If not, maybe your original is not release 2.0 and the instruction is a few bytes beside the given address ? or maybe you mistyped (D185 instead of D1B5 ?).
And yes, I agree with the value you get before patching (this means perhaps that you patched the right instruction but that you mistyped it). You can try also with other operands than 1. + 2. (not giving 3.) to see if you always get 1. ? or always get the first operand ? or what else ?

Paul


May the FORTH be with you !
POLKa
RWAP
RWAP Master
Posts: 2839
Joined: Sun Nov 28, 2010 4:51 pm
Location: Stone, United Kingdom
Contact:

Re: Computer One FORTH

Post by RWAP »

Thanks Paul, yes, I had misread that final value - it is now up and running :D


User avatar
programandala.net
Chuggy Microdrive
Posts: 74
Joined: Mon Dec 13, 2010 12:41 pm
Location: Spain
Contact:

Re: Computer One FORTH

Post by programandala.net »

Thank you Paul for the good information and the good memories. Forth is my favourite programming language. I used it on the QL too, both SuperForth and Computer One's, but not so deeply to find out those bugs!

The Forth I use the most nowadays is Gforth. But for a new project I'm trying the combination of SP-Forth (that can easily create Windows and Linux executables) with Forth Foundation Library.


Marcos Cruz (programandala.net)
User avatar
polka
Trump Card
Posts: 196
Joined: Mon Mar 07, 2011 11:43 am

Re: Computer One FORTH

Post by polka »

Hi all FORTH lovers... and other (normal) people !

Here is a little bit of FORTH code...

Code: Select all

Screen # 50

.(  Bucket brigade displaying roman format of a decimal number )
 : ROMAN
         LATEST NAME> CREATE , ,
   DOES>
         DUP @ ROT ROT DUP BODY> >NAME ROT ROT 2+ @ SWAP
         BEGIN
                 2DUP <=
         WHILE
                 OVER - 2 PICK .NAME BACKSPACE
         REPEAT
         ROT ROT 2DROP SWAP EXECUTE ;
 : THE-BUCKET-STOPS-HERE CR DROP   ;
  1 ROMAN |   4 ROMAN |V    5 ROMAN V    9 ROMAN |X   10 ROMAN X
             40 ROMAN XL   50 ROMAN L   90 ROMAN XC  100 ROMAN C
            400 ROMAN CD  500 ROMAN D  900 ROMAN CM 1000 ROMAN M
 : .ROMAN M ;      : .ROMANS 1+ 1 DO I .ROMAN LOOP ;
that does what ?

Given a (decimal) integer, it translates it to ROMAN notation, for instance typing :
12 .ROMAN
you get :
XII
ok

Or if you type
12 .ROMANS
you get it counting from 1 to 12 in roman notation.

There are two interesting facts about this little program :

1/ its compactness : Only 16 lines of source code, compiled in a little more than 300 bytes (50+ "forth instructions")

2/ The weird way it is done :
Look at the problem : given a decimal number you have to decompose it into the right sequence of the following symbols :

M meaning 1000
CM meaning 900
D meaning 500
CD meaning 400
C meaning 100
XC meaning 90
L meaning 50
XL meaning 40
X meaning 10
IX meaning 9
V meaning 5
IV meaning 4
I meaning 1

Look at the last lines of the source code : here you see 13 ROMANs called I IV V IX etc.
associated with their corresponding values. Now what does the .ROMAN command ? it essentialy calls M, which is the ROMAN that will display first the correct sequence of "M"s.

Then, this ROMAN will pass control to CM, which will pass control to D, etc. and finally, I will do his job and pass control to THE-BUCKET-STOPS-HERE : the code runs like a bucket brigade !

How is it designed ? In FORTH you may define "defining words" of any new type yourself. Here I defined the defining word "ROMAN". To create a new ROMAN, you have to give it a value, for instance 4 and a name, for instance IV, this way :

4 ROMAN IV

Then, when IV (for instance) is called for execution it "does" what is written in the definition of ROMAN after instruction DOES> ; in this case, it first stacks some parameters, then enters a BEGIN...WHILE...REPEAT loop to display its own name (for instance IV) eventually zero or several times, and finally passes control to the next ROMAN with what remains in the bucket.

What about coding this in SuperBasic and compare execution speeds ?

BYE (a FORTH command !) Paul
Last edited by polka on Tue Jul 19, 2011 7:26 am, edited 1 time in total.


May the FORTH be with you !
POLKa
User avatar
dex
Gold Card
Posts: 286
Joined: Thu Dec 23, 2010 1:40 pm

Re: Computer One FORTH

Post by dex »

Nice!


User avatar
polka
Trump Card
Posts: 196
Joined: Mon Mar 07, 2011 11:43 am

Re: Computer One FORTH

Post by polka »

Here is another little bit of FORTH code doing exactly the opposite, but using the same principle of the "Bucket Brigade"...

Code: Select all

Screen # 51

.( Bucket Brigade doing the opposite )   VARIABLE K
: ROMAN
        LATEST NAME> CREATE , ,
  DOES>
        DUP @ ROT ROT DUP BODY> >NAME SWAP 2+ @ ROT ROT
        BEGIN   DUP C@ 3 AND 1+ 1 DO
                OVER I + C@ OVER I + C@ <>
                IF DROP 0 LEAVE THEN LOOP DUP
        WHILE   DUP C@ 3 AND ROT + SWAP 2 PICK K +!
        REPEAT
        ROT 2DROP SWAP EXECUTE ;
: THE-BUCKET-STOPS-HERE 1+ C@ BL = IF K @ ELSE 0 THEN ;
1 ROMAN |   4 ROMAN |V    5 ROMAN V    9 ROMAN |X   10 ROMAN X
           40 ROMAN XL   50 ROMAN L   90 ROMAN XC  100 ROMAN C
          400 ROMAN CD  500 ROMAN D  900 ROMAN CM 1000 ROMAN M
: ROMAN BL WORD 0 K ! M ;    : .DECIMAL ROMAN ?DUP IF . THEN ;
How use this one ? You have a character string representing a number in roman notation and you want it converted to a digital number on the stack and eventually displayed as a decimal.

For instance you type :
roman XII
and FORTH will put 12 on the stack, which you may display simply by typing :
.
to get on the screen :
12 ok

Or you may type :
roman XII .
to get immediately on the screen :
12 ok

But if you type a string that is not a number with the correct grammar for roman notation, you will get :
0 ok

Thus, I dedfined also a "display" function .DECIMAL which you use this way :
.decimal XII
will display :
12 ok

But if you type :
.decimal foo-bar
(or anything not a correct roman notation of a number), it will display nothing ; actually only :
ok

What if you type :
roman MCMXLVIII .
is this correct or not ? and how much is it ?


Now, if you look at the code, you will notice that the definition of ROMAN differs only after DOES>, meaning that the ROMAN data structures (words) are exactly the same, but they behave differently. The conversion code, inside the BEGIN...WHILE...REPEAT is a little more complex, because in screen #50 I could use some functions already defined for the FORTH interpreter, that I had to program anew as lower level sequences for the reverse case in screen 51.

You may also notice that I defined ROMAN a second time in the last line of the screen : the first definition of this word was used to instanciate all the "basic" ROMANs needed, but then it was no longer useful, and the second definition should hide (you may even say : lock away) the first - so that it is no longer "free" to define any new ROMANs. You just get a warning message when loading screen #51, telling that ROMAN is redefined.

Moreover, if you load screen #50, and then #51, you will see a lot of such messages ; however .ROMAN will still convert correctly a number to its roman notation ; and .DECIMAL (or ROMAN) will correctly do the reverse. The only thing is that all the "basic" ROMANs are defined twice with two different behaviour.

How can FORTH let them be defined only once, with a behaviour automatically selected by the job to be performed at runtime ? This I will show next...

(if someone is interested ?)

May the FORTH be with you !

Bye, Paul

Guessing game : suppose you type :

ROMAN MCMXLVIII ROMAN LXIII + .ROMAN

What will FORTH answer ?


May the FORTH be with you !
POLKa
User avatar
polka
Trump Card
Posts: 196
Joined: Mon Mar 07, 2011 11:43 am

Re: Computer One FORTH

Post by polka »

Although it does not seem to arouse much enthousiasm, I continue my little FORTH games :

I promised to show a way to have the ROMAN words behave differently when used for conversion from roman notation to decimal and when used for the reverse conversion. Here I first present a general way to implement such "vectored" execution : the DEFER..IS construct. This construct was defined as "facultative" in the FORTH83 standard, thus it may not be part of all FORTH83 compliant systems (actually, it is not part of the ComputerOne FORTH) but when present, its syntax and semantics have to follow the standard. And it is really easy to add :

Code: Select all

Screen # 52 

.( DEFER..IS a construct for vectored execution and WHAT_IS) cr
: DEFER CREATE ['] NOOP , DOES> @ EXECUTE ;
: (IS) R@ @ >BODY ! R> 2+ >R ;
: IS STATE @ IF COMPILE (IS) ELSE ' >BODY ! THEN ; IMMEDIATE
: WHAT_IS ' DUP >BODY @ >NAME .NAME ." is " >NAME .NAME cr ;
.( to see it work : if FORTH83 compliant, this should be OK) cr
cr DEFER POLITE .( defining DEFERed POLITE : ) WHAT_IS POLITE cr
    : SAY_HELLO ." hello" cr ;
.( defining SAY_HELLO ; a word that says : ) SAY_HELLO cr
' SAY_HELLO is POLITE .( now we decide : ) WHAT_IS POLITE cr
.( and thus, POLITE says : ) POLITE cr
    : SAY_GOOD_BYE ." good bye" cr ;
.( defining SAY_GOOD_BYE ; a word that says : ) SAY_GOOD_BYE cr
    : LATER ['] SAY_GOOD_BYE is POLITE ; LATER
.( after defining LATER and executing it : ) WHAT_IS POLITE cr
.( and thus, POLITE now says : ) POLITE cr
In the upper lines of this code "screen #52", we define
four words : DEFER (IS) IS and WHAT_IS

The words to be used for vectored execution are DEFER and IS ; (IS) is an ancillary word, and WHAT_IS a word that I added for tests and demos. How does it work ?

Suppose (follow the demo at the end of the code screen) you want to define a word called POLITE that sometimes says "hello" and sometimes "good bye". You fisrt define this word as defered, with no behaviour at all :

DEFER POLITE

adds the word POLITE but at this time, as you can see in the definition of DEFER, it does NOOP (nothing).

Now you can define the usual way (with : ... ; ) two words :

: SAY_HELLO ." hello" CR ; (displaying hello on the screen and doing carriage return)
: SAY_GOOD_BYE ." good bye" CR ; (same thing but good bye)

To tell POLITE to say hello from the keyboard, type :

' SAY_HELLO IS POLITE

The ' command puts the execution address of the word that follows (SAY_HELLO) on the stack and IS stores this address inside POLITE to replace there the address of NOOP. Now, if you call :

POLITE

it will answer "hello"

When you want to change POLITE from inside a definition, you have to do somewhat different :

: LATER ['] SAY_GOOD_BYE IS POLITE ;

You have to use ['] instead of ' inside a definition.

Just after defining LATER, POLITE still says hello, but when you execute LATER typing :

LATER

You change the behaviour of POLITE and from then, it will say "good bye".

Simple and straitforward, isn't ? This DEFER..IS construct adds less than 100 bytes to ComputerOne FORTH, and each time you use IS to change the behaviour of a DEFERed word, it adds a runtime penalty of 6ms (on a original QL, not a speedy card or emulator).

The word WHAT_IS that I added to the construct displays the word that is actually executed by a defered word :

WHAT_IS POLITE will initially answer NOOP but after it may answer SAY_HELLO or SAY_GOOD_BYE

On your ComputerOne FORTH (or any other FORTH83 compliant system) load this screen, and watch the demo of the DEFER..IS construct.

May the FORTH be with you ! BYE Paul

By the way : for the demo, I used a lot the .( ..... ) construct ; these are "comments" that are displayed during loading
Whereas ( ....... ) are comments that are not displayed : take note of ( instead of .(
And whereas ." ........ " are messages inside word definitions that will be displayed when the word is executed : take note of ." instead of .( and " instead of )
Last edited by polka on Mon Sep 19, 2011 12:17 pm, edited 1 time in total.


May the FORTH be with you !
POLKa
User avatar
polka
Trump Card
Posts: 196
Joined: Mon Mar 07, 2011 11:43 am

Re: Computer One FORTH

Post by polka »

And Noooooow...

I will rewrite the ROMAN little software component with DEFER..IS to have the ROMAN words do different things depending on the context of their use. First let us compare Screen #50 and Screen #51 and delimit the differences in the definitions of ROMAN :

These two defnitions are same up to ...DOES> DUP @ ROT ROT BODY> >NAME and then again from 2DROP SWAP EXECUTE ; at the end. Thus we will replace the sequence that is different in between by a defered word and provide two implementations to swap from one behaviour to the other :

Code: Select all

Screen # 53
 
  VARIABLE K        : DEFER CREATE ['] NOOP , DOES> @ EXECUTE ;   
  DEFER CONVERSION  : ROMAN LATEST NAME> CREATE , , DOES>
  DUP @ ROT ROT DUP BODY> >NAME CONVERSION 2DROP SWAP EXECUTE ;
: 2D SWAP 2+ @ ROT ROT BEGIN DUP C@ 3 AND 1+ 1 DO
  OVER I + C@ OVER I + C@ <> IF DROP 0 LEAVE THEN LOOP DUP
  WHILE DUP C@ 3 AND ROT + SWAP 2 PICK K +! REPEAT ROT ;
: 2R ROT ROT 2+ @ SWAP BEGIN 2DUP <= WHILE OVER - 2 PICK .NAME
  BACKSPACE REPEAT ROT ROT ;     : (IS) R@ @ >BODY ! R> 2+ >R ;
: IS STATE @ IF COMPILE (IS) ELSE ' >BODY ! THEN ; IMMEDIATE
: THE-BUCKET-STOPS-HERE 1+ C@ BL = IF K @ ELSE 0 THEN ;
1 ROMAN |   4 ROMAN |V    5 ROMAN V    9 ROMAN |X   10 ROMAN X
           40 ROMAN XL   50 ROMAN L   90 ROMAN XC  100 ROMAN C
          400 ROMAN CD  500 ROMAN D  900 ROMAN CM 1000 ROMAN M
: ROMAN BL WORD 0 K ! ['] 2D IS CONVERSION M ;
: .ROMAN ['] 2R IS CONVERSION M DROP CR ;   : .DECIMAL ROMAN
  ?DUP IF . THEN ;   : .ROMANS 1+ 1 DO I .ROMAN LOOP ;
You see that after defining DEFER, we use it :

DEFER CONVERSION

And then we define the two implementation words :

: 2D ........ ; for converting to decimal

: 2R ........ ; for converting to roman

As these are two ancillary words, they may have short and cryptic names !

Then you see the definition of IS, and of THE-BUCKET-STOPS-HERE from screen #51, because it it almost compatible. The defered behaviour is set inside (the second definition of) word ROMAN, where you notice the ['] 2D IS CONVERSION sequence added, and inside the definition of .ROMAN where you notice the ['] 2R IS CONVERSION sequence added, but also after M the DROP CR sequence that was in THE-BUCKET-STOPS-HERE of screen #50

After loading this screen #53, everything should work exactly as when you loaded Screen #50 and Screen #51, but you did not get all these warnings and actually the compiled code takes only 538 bytes instead of 704 !

You can enter numbers giving their roman notation

roman MCMXLVIII (this will use the 2D implementation)

Or display a number in its roman notation

1948 .roman (this will use the 2R implementation)

However don't forget that executing IS each time you ask for a conversion you add 6ms runtime.

May the FORTH be with you, BYE Paul


May the FORTH be with you !
POLKa
Post Reply