+TheBF Posted May 15, 2022 Author Share Posted May 15, 2022 The reason I was interested in looking at Wycove Forth because I had heard that it had 24x40 column editor, in other words, the editor fits the TI-screen. I had written an editor that let you cursor around the screen and edit and interpret lines from the VDP memory. It was a short journey to save the VDP screen to a Forth block. The result is a very efficient editor for the TI-99 if you are ok with block files and 40 columns of width. This version takes 1694 bytes of system memory. I decided to play with the idea of treating the screen like one big line buffer so you can insert/delete text and affects the entire screen. Not always ideal. It should probably have a way to enable and disable that feature. Of course the problem is that regular 64 column Forth source code does not look correct on this editor so it's one or the other. It was an interesting exercise. I formatted the editor code for this editor and it looks good to me. Spoiler \ Experimental VDP Screen editor \ with block files Oct 21, 2021 NEEDS DUMP FROM DSK1.TOOLS NEEDS CASE FROM DSK1.CASE NEEDS RKEY FROM DSK1.RKEY NEEDS BLOCK FROM DSK1.BLOCKS NEEDS VOCABULARY FROM DSK1.WORDLISTS VOCABULARY EDITOR ONLY FORTH ALSO EDITOR DEFINITIONS HERE \ cursor characters HEX 201F CONSTANT BOX 201E CONSTANT BAR VARIABLE INSERT VARIABLE SCR \ cheap & dirty dynamic memory : RESERVE ( n -- addr) HERE ALLOT ; : CANCEL ( n -- ) NEGATE ALLOT ; \ CREATE IBUFF C/L@ ALLOT DECIMAL L/SCR 1- CONSTANT BOT C/L@ 1- CONSTANT WIDTH : BLANK BL FILL ; : CLRLN ( X Y ) >VPOS C/L@ BL VFILL ; \ text manipulation : SCREND ( -- Vaddr) VPG @ [ C/SCR @ 1- ] LITERAL + ; \ track row/col when cursors moves \ more than C/L length : R/C-UPDATE VCOL @ C/L@ > IF VCOL @ C/L@ /MOD VROW ! VCOL ! THEN ; \ cursor movement controls : CRIGHT VPOS SCREND < IF VCOL 1+! THEN ; : LIMIT1+! ( addr limit --) OVER @ 1+ MIN SWAP ! ; : CDOWN VROW @ 23 < IF VROW C/SCR @ 1- LIMIT1+! THEN ; : CUP VROW @ 1- 0 MAX VROW ! ; : CLEFT VCOL @ 1- 0 MAX VCOL ! ; : NEWLINE VCOL OFF CDOWN ; HEX : INS/DEL INSERT @ -1 XOR INSERT ! INSERT @ IF 201E ELSE 201F THEN CURS ! ; \ Screen text functions DECIMAL : BYTES-BELOW ( Vaddr -- Vaddr len) C/SCR @ OVER - ; : RIGHTSIDE ( -- Vaddr len) VPOS BYTES-BELOW ; : LEFTSIDE ( -- Vaddr len) VPG @ VPOS ; : VCOPY ( vaddr len addr -- ) SWAP VREAD ; : /CHARS ( addr n) /STRING TUCK HERE VCOPY HERE VPOS ROT VWRITE BL SCREND VC! ; : PUSHRIGHT ( Vaddr len --) TUCK HERE VCOPY BL VPUT HERE VPOS 1+ ROT VWRITE ; : DELCHAR ( -- ) RIGHTSIDE 1 /CHARS ; : INSCHAR ( -- ) RIGHTSIDE PUSHRIGHT ; : DEL-LINE 0 VROW @ 1+ >VPOS BYTES-BELOW ( Vddr len) DUP>R HERE VCOPY HERE 0 VROW @ >VPOS R> VWRITE 0 23 CLRLN ; : INS-LINE 0 VROW @ 2DUP ( col row col row) >VPOS BYTES-BELOW C/L@ - DUP>R HERE VCOPY HERE 0 VROW @ 1+ >VPOS R> VWRITE ( col row) CLRLN ; DECIMAL : LASTXY ( -- col row) 0 BOT ; : SAVELN HERE C/L@ BLANK ; LASTXY >VPOS HERE C/L@ VREAD ; : PROMPT: SAVELN LASTXY 2DUP CLRLN AT-XY ; : GETXY ( col row) VROW 2@ ; : BLIT ( addr --) VPG @ C/SCR @ VWRITE ; : GET-SCR SCR @ BLOCK BLIT ; \ read VDP screen into BLOCK buffer : (SAVE) VPG @ SCR @ BLOCK C/SCR @ VREAD UPDATE FLUSH ; : SAVE-SCR ( -- ) (SAVE) GETXY PROMPT: ." Saved" 700 MS GET-SCR AT-XY ; : .BLK# GETXY PROMPT: ." Blk " SCR @ . 100 MS GET-SCR AT-XY ; : PGDN SCR 1+! .BLK# GET-SCR ; : PGUP SCR @ 1- 0 MAX SCR ! .BLK# GET-SCR ; \ screen colors + black letters HEX 17 CONSTANT CYAN 12 CONSTANT GREEN 1E CONSTANT GRAY : SCREEN ( C --) 7 VWTR ; : ESCAPE (SAVE) [CHAR] _ CURS ! E4 SCREEN PROMPT: ." Forth" FLUSH ABORT ; : PUTCHAR ( c --) INSERT @ IF INSCHAR THEN VPUT CRIGHT ; HEX : KEYHANDLER ( char -- ) CASE 0D OF NEWLINE ENDOF 0C OF PGUP ENDOF 02 OF PGDN ENDOF 06 OF INS-LINE ENDOF 07 OF DEL-LINE ENDOF 08 OF CLEFT ENDOF \ 0B OF CUP ENDOF 0A OF CDOWN ENDOF 09 OF CRIGHT ENDOF 03 OF DELCHAR ENDOF 04 OF INS/DEL ENDOF 10 OF PAGE UPDATE ENDOF \ ^P 13 OF SAVE-SCR ENDOF \ ^S 15 OF GET-SCR ( undo) ENDOF \ ^U 0F OF ESCAPE ENDOF HONK ENDCASE \ remove spurious output KEY? DROP ; HEX \ user interface : EDIT ( BLK# -- ) PAGE GRAY SCREEN SCR ! GET-SCR BOX CURS ! LINES OFF BEGIN RKEY 7F AND DUP BL [CHAR] ~ 1+ WITHIN 0= IF KEYHANDLER ELSE PUTCHAR THEN AGAIN ; : USE PARSE-NAME OPEN-BLOCKS ; DECIMAL \ added LOAD so we can compile code from BLOCKS VARIABLE SCR : LINE ( n -- addr) C/L@ * SCR @ BLOCK + ; : LOAD ( n -- ) SCR ! 23 0 DO I LINES ! ( keep track of line number for errors) I LINE C/L@ EVALUATE LOOP ; DECIMAL HERE SWAP - . Block editor 24x40.mp4 2 Quote Link to comment Share on other sites More sharing options...
+Lee Stewart Posted May 15, 2022 Share Posted May 15, 2022 1 hour ago, TheBF said: The reason I was interested in looking at Wycove Forth because I had heard that it had 24x40 column editor, in other words, the editor fits the TI-screen. I had written an editor that let you cursor around the screen and edit and interpret lines from the VDP memory. It was a short journey to save the VDP screen to a Forth block. The result is a very efficient editor for the TI-99 if you are ok with block files and 40 columns of width. This version takes 1694 bytes of system memory. I decided to play with the idea of treating the screen like one big line buffer so you can insert/delete text and affects the entire screen. Not always ideal. It should probably have a way to enable and disable that feature. Of course the problem is that regular 64 column Forth source code does not look correct on this editor so it's one or the other. It was an interesting exercise. I formatted the editor code for this editor and it looks good to me. [Because I did not fully analyze your code, I apologize in advance if you are already implementing some of the suggestions that follow.] Maybe it would be useful to manage your screen-editor buffer as 1 KiB of VRAM, with the actual screen image as 15 of the 16 64-byte lines of a Forth block. You could then load one entire block there. You might need to relocate the Value Stack by changing >836E (Value Stack Pointer) to point to an unused 128-byte VRAM area. It might also be convenient to use VRAM >0400 as a second, 1024-byte screen/block buffer to allow switching screens by simply changing VR02 (and your screen variables, of course). ...lee 1 Quote Link to comment Share on other sites More sharing options...
+TheBF Posted May 15, 2022 Author Share Posted May 15, 2022 6 hours ago, Lee Stewart said: [Because I did not fully analyze your code, I apologize in advance if you are already implementing some of the suggestions that follow.] Maybe it would be useful to manage your screen-editor buffer as 1 KiB of VRAM, with the actual screen image as 15 of the 16 64-byte lines of a Forth block. You could then load one entire block there. You might need to relocate the Value Stack by changing >836E (Value Stack Pointer) to point to an unused 128-byte VRAM area. It might also be convenient to use VRAM >0400 as a second, 1024-byte screen/block buffer to allow switching screens by simply changing VR02 (and your screen variables, of course). ...lee I am using the 960 byte screen at V>0000 as the editing buffer and just blast that into a BLOCK when you press ^S (SAVE) Your ideas are all good. Not sure how much farther I will push this editor. I was really trying see what I thought of the Wycove 24x40 screen format editor concept. I might even go on to make ~10K buffer in VDP RAM for editing text files. The code I have created literally gives me an "editline" function for VDP RAM so I could do it on each line of a Text file. I have never used that value stack pointer. I will read up on it to see if I need to touch it. I use the top of VDP RAM at >8370 as the start of the PAB stack. I have a Forth variable that I used to keep track of the bottom of VDP RAM. Maybe I should be using 836E instead. ?? 1 Quote Link to comment Share on other sites More sharing options...
GDMike Posted May 15, 2022 Share Posted May 15, 2022 (edited) 16 minutes ago, TheBF said: I am using the 960 byte screen at V>0000 as the editing buffer and just blast that into a BLOCK when you press ^S (SAVE) Your ideas are all good. Not sure how much farther I will push this editor. I was really trying see what I thought of the Wycove 24x40 screen format editor concept. I might even go on to make ~10K buffer in VDP RAM for editing text files. The code I have created literally gives me an "editline" function for VDP RAM so I could do it on each line of a Text file. I have never used that value stack pointer. I will read up on it to see if I need to touch it. I use the top of VDP RAM at >8370 as the start of the PAB stack. I have a Forth variable that I used to keep track of the bottom of VDP RAM. Maybe I should be using 836E instead. ?? This reminds me of what I was supposed to ask Lee one day regarding the FB forth editor. Because I was wishing for a history hot key and if it could be added at some point. Something that would allow recall of last 4 or so command line entries... anyway.... let me get back to work.. Edited May 15, 2022 by GDMike Quote Link to comment Share on other sites More sharing options...
+TheBF Posted May 15, 2022 Author Share Posted May 15, 2022 Just now, GDMike said: This reminds me of what I was supposed to ask Lee one day regarding the FB forth editor. Because I was wishing for a history hot key and if it could be added at some point. Something that would allow recall of last 4 or so command line entries... anyway.... let me get back to work.. Bf's two rules of software always apply: 1. Anything is possible ? 2. Nothing is easy ? 1 1 Quote Link to comment Share on other sites More sharing options...
+TheBF Posted May 15, 2022 Author Share Posted May 15, 2022 3 minutes ago, GDMike said: This reminds me of what I was supposed to ask Lee one day regarding the FB forth editor. Because I was wishing for a history hot key and if it could be added at some point. Something that would allow recall of last 4 or so command line entries... anyway.... let me get back to work.. That sounds more like changing the command line interpreter rather than the editor. (?) GForth has that with the arrow keys I think. Is that what you want? 1 Quote Link to comment Share on other sites More sharing options...
GDMike Posted May 15, 2022 Share Posted May 15, 2022 Oh..btw. I can't upload videos anymore, well, videos with any type of length. Seems Albert says server can't handle it and so really didn't support it to begin with. But that's why my videos showing SNP weren't going through. Makes sense. But photos are just fine. Quote Link to comment Share on other sites More sharing options...
GDMike Posted May 15, 2022 Share Posted May 15, 2022 1 minute ago, TheBF said: That sounds more like changing the command line interpreter rather than the editor. (?) GForth has that with the arrow keys I think. Is that what you want? Definitely. Quote Link to comment Share on other sites More sharing options...
+TheBF Posted May 16, 2022 Author Share Posted May 16, 2022 1 hour ago, GDMike said: Definitely. I can think of ways to do that. I may need some help from Lee but basically we just need to create a circular buffer of say 8 "terminal input buffers" that switch on the up-arrow/dn-arrow keys. Might even be able to keep those text lines in VDP RAM so they don't take up program memory. I will noodle on it. 1 Quote Link to comment Share on other sites More sharing options...
GDMike Posted May 16, 2022 Share Posted May 16, 2022 (edited) 1 hour ago, TheBF said: That sounds more like changing the command line interpreter rather than the editor. (?) GForth has that with the arrow keys I think. Is that what you want? This is wonderful. Wishing for TF had it too. I can't wait to finish SNP... really.. I know I've said this before, but I feel SNP isn't done yet. Getting close. Because I'll end up in Forth. Just not the rt time yet. BECS I figure, if I go into forth I don't want something outside forth on my mind in the background trying to pull me away from a Forth project (mindset). Edited May 16, 2022 by GDMike Quote Link to comment Share on other sites More sharing options...
+Lee Stewart Posted May 16, 2022 Share Posted May 16, 2022 3 hours ago, TheBF said: I have never used that value stack pointer. I will read up on it to see if I need to touch it. The Value Stack is used by functions in the console. I never use it directly, but I always try to stay out of its way. You can change it to point to any 128-byte block of VRAM—just be sure it is free VRAM! 3 hours ago, TheBF said: I use the top of VDP RAM at >8370 as the start of the PAB stack. I have a Forth variable that I used to keep track of the bottom of VDP RAM. Maybe I should be using 836E instead. ?? That won’t work because there are other intervening blocks of VRAM—notably, the Pattern and Sprite Descriptor Tables. ...lee 1 Quote Link to comment Share on other sites More sharing options...
Willsy Posted May 16, 2022 Share Posted May 16, 2022 On 5/14/2022 at 12:28 AM, TheBF said: Or in some parts of the world they can do this: TASK: WIFE#1 TASK: WIFE#2 : CHILDREARING BEGIN DIAPER FEED DIAPER BATH DIAPER ROCK-TO-SLEEP AGAIN ; : HOUSEHOLD BEGIN BREAKFAST CLEANUP SHOPPING LUNCH SUPPER RUB-MY-FEET LAUNDRY AGAIN ; ' CHILDREARING WIFE#1 ASSIGN ' HOUSEHOLD WIFE#2 ASSIGN MULTI WIFE#1 WAKE WIFE#2 WAKE Tut tut! This code is woefully incomplete. You have failed to note the important contribution of the male of the family! TASK: WIFE#1 TASK: WIFE#2 : CHILDREARING BEGIN DIAPER FEED DIAPER BATH DIAPER ROCK-TO-SLEEP AGAIN ; : HOUSEHOLD BEGIN BREAKFAST CLEANUP SHOPPING LUNCH SUPPER RUB-MY-FEET LAUNDRY AGAIN ; : BLOKE BEGIN 5 HOURS 0 DO DRINK-BEER WATCH-FOOTBALL FART LOOP EAT SLEEP AGAIN ; ' CHILDREARING WIFE#1 ASSIGN ' HOUSEHOLD WIFE#2 ASSIGN MULTI WIFE#1 WAKE WIFE#2 WAKE BLOKE There you go!!! Note that bloke runs in the main thread, since he requires the most attention and maintenance 1 4 Quote Link to comment Share on other sites More sharing options...
GDMike Posted May 16, 2022 Share Posted May 16, 2022 (edited) Im really interested in the rub my feet code. That's going to be one of those stare at the screen moments. Edited May 16, 2022 by GDMike 2 Quote Link to comment Share on other sites More sharing options...
+TheBF Posted May 16, 2022 Author Share Posted May 16, 2022 9 hours ago, Lee Stewart said: The Value Stack is used by functions in the console. I never use it directly, but I always try to stay out of its way. You can change it to point to any 128-byte block of VRAM—just be sure it is free VRAM! That won’t work because there are other intervening blocks of VRAM—notably, the Pattern and Sprite Descriptor Tables. ...lee I seem to have empty VDP RAM from >1000 up to the value in >8370. So my VP variable (VDP pointer) inits to >1000. I think because I have my own DSR code and only use KSCAN and ?TERMINAL in console ROM I have been un-affected. I have yet to do anything in bit-map mode so that will consume almost everything I believe. I will have to pay more attention to this. Thanks. Quote Link to comment Share on other sites More sharing options...
Willsy Posted May 16, 2022 Share Posted May 16, 2022 12 hours ago, GDMike said: This is wonderful. Wishing for TF had it too. I can't wait to finish SNP... really.. I know I've said this before, but I feel SNP isn't done yet. Getting close. Because I'll end up in Forth. Just not the rt time yet. BECS I figure, if I go into forth I don't want something outside forth on my mind in the background trying to pull me away from a Forth project (mindset). You can substitute the built-in TF interpreter for one of your own. Just write it in normal Forth code using colon definitions, get the address of your new interpreter using tick, and load that address into address $A000 et viola. TF is now running YOUR interpreter. Your new interpreter would include the code to store the last 4 commands entered. The more useful vectors for TF are as follows: intvec bss 2 ; vector for INTERPRET >a000 blkvec bss 2 ; vector for BLOCK >a002 numvec bss 2 ; vector for NUMBER >a004 fndvec bss 2 ; vector for FIND >a006 We now return you to your regular Camel99-based programme! 2 2 Quote Link to comment Share on other sites More sharing options...
GDMike Posted May 16, 2022 Share Posted May 16, 2022 Well well well. Isn't that something. Blowing my mind again with this creature. 1 2 Quote Link to comment Share on other sites More sharing options...
+TheBF Posted May 16, 2022 Author Share Posted May 16, 2022 4 hours ago, Willsy said: You can substitute the built-in TF interpreter for one of your own. Just write it in normal Forth code using colon definitions, get the address of your new interpreter using tick, and load that address into address $A000 et viola. TF is now running YOUR interpreter. Your new interpreter would include the code to store the last 4 commands entered. The more useful vectors for TF are as follows: intvec bss 2 ; vector for INTERPRET >a000 blkvec bss 2 ; vector for BLOCK >a002 numvec bss 2 ; vector for NUMBER >a004 fndvec bss 2 ; vector for FIND >a006 We now return you to your regular Camel99-based programme! I just might have to steal that idea myself. 3 Quote Link to comment Share on other sites More sharing options...
+TheBF Posted May 17, 2022 Author Share Posted May 17, 2022 As I continued to learn how to code efficiently on this project, the size of the Camel99 kernel slowly got smaller. I had removed about 200 bytes so it was time to add some value back. Something I always wanted to add, that the original Camel Forth code did not have, was the ability to check for duplicate names when you compile your code. I had lots of extra room now so I am taking some back for this feature. I am doing it in the style of F83, GForth and others where you can turn warnings on/off with this complicated syntax: WARNINGS ON ( turns the warnings on ) WARNINGS OFF ( turns the warnings off) It seems to work although with warnings on the compile times are about about 28% slower because of searching the dictionary on every new definition. Thus the WARNINGS flag to turn them off on a big compile. This now has me wondering if I can duplicate the 4 way hashed dictionary like F83 to speed up the searching. It means adding that look up mechanism to my cross-compiler first... That's a bit daunting. Here is the relevant code in from the HILEVEL.HSF file. It will get to Github eventually. Spoiler \ ====================================================================== \ D I C T I O N A R Y C R E A T I O N TARGET-COMPILING : HEADER, ( addr len --) ALIGN CURRENT @ @ , \ get last NFA & compile in this LFA field 0 C, \ compile the precedence byte (immediate flag) HERE >R \ save HERE (ie: new NFA location) S, \ compile (addr len) as counted string WARNINGS @ IF R@ FIND ( xt ?) NIP ( ?) IF SPACE R@ COUNT 1F AND TYPE T." isn't unique " THEN THEN R@ LATEST ! \ HERE now is the last word defined R> CURRENT @ ! \ Also store in the current 'WID' ; : HEADER ( <TEXT> ) BL PARSE-WORD HEADER, ; 2 Quote Link to comment Share on other sites More sharing options...
Willsy Posted May 17, 2022 Share Posted May 17, 2022 12 hours ago, TheBF said: I just might have to steal that idea myself. Yeah, it seemed sensible at the time. Being able to re-vector/patch FIND has proved the most useful. I use that in the local variables library IIRC. I patch the FIND vector such that it runs the locals version of FIND first, and if it doesn't find a local, it then calls the internal FIND to do a normal dictionary search. I've also used the interpreter vector for a peephole optimizer that I was playing with. Patching BLOCK would allow one to store blocks in SAMS etc... Just a matter of adding the appropriate logic: Search SAMS for the block in question, if not found, call the regular in-ROM BLOCK routine instead etc. There's an ISR vector too for calling machine code routines. I would have liked to be able to call Forth words with it, but I couldn't figure it out at the time! 2 Quote Link to comment Share on other sites More sharing options...
Willsy Posted May 17, 2022 Share Posted May 17, 2022 (edited) 5 hours ago, TheBF said: This now has me wondering if I can duplicate the 4 way hashed dictionary like F83 to speed up the searching. It means adding that look up mechanism to my cross-compiler first... That's a bit daunting. That's a good feature. TF also has that via WARN: 0 WARN ! ( turn off warnings) 1 WARN ! (turn 'em on) In the locals library that I did some years back I hash the names of the locals into a 16-bit word. Since locals are only in existence during the compilation of a word (after compilation they exist as an offset into a locals stack) it doesn't make sense to refer to them by name. Instead, I hash their names during declaration, and during compilation of the word that contains a local, if the regular word isn't found I hash the word currently being compiled and search the 'locals dictionary', which is nothing more than an array of integers. If I find a match, then it's ordinal position in the array determines the offset it will have into the locals stack at run-time. Simples. There's a risk of a hash-collision, particularly when using a 16-bit hash (I actually stole the MODBUS 16-bit CRC algorithm as I think it's quite a good candidate - after all, a CRC algorithm is concerned with detecting bit changes so theoretically a single bit difference will generate a completely different CRC/hash) - though I don't know much of the 16-bit 'space' the MODBUS CRC actually covers. That's one for the mathematicians. Are you reading this, @Lee Stewart. In practice though, I have not yet encountered a name collision and I've used the library quite a lot. Edited May 17, 2022 by Willsy added links 3 Quote Link to comment Share on other sites More sharing options...
+TheBF Posted May 17, 2022 Author Share Posted May 17, 2022 (edited) That's a neat usage of a hash table. The n-way hashing I am thinking about was invented in PolyForth (Forth Inc) I believe. Polyforth changed the dictionary from a single linked list to eight different entry points. To determine the list a word will be added to, the first letter of the new word is hashed to derive a 0,1,2,3,4,5,6, or 7, or 8. So each search times are potentially 8 times faster. I am going with a 4 way linked list like F83 to save memory. Each word list requires a 4 cell array to hold the "latest" word in each list and memory is valuable on the old 99. I am also thinking that when I do this I have made enough changes to stop calling this Camel Forth. Edited May 18, 2022 by TheBF Off by one error :-0 3 Quote Link to comment Share on other sites More sharing options...
+Lee Stewart Posted May 17, 2022 Share Posted May 17, 2022 5 hours ago, Willsy said: There's a risk of a hash-collision, particularly when using a 16-bit hash (I actually stole the MODBUS 16-bit CRC algorithm as I think it's quite a good candidate - after all, a CRC algorithm is concerned with detecting bit changes so theoretically a single bit difference will generate a completely different CRC/hash) - though I don't know much of the 16-bit 'space' the MODBUS CRC actually covers. That's one for the mathematicians. Are you reading this, @Lee Stewart. Ha! Don’t give me too much credit here. Unless I miss your point, I should think that a 16-bit hash would cover all of the 16-bit space if the hash function can generate all 16-bit values. That would be a problem for our TI-99/4A if you were using the hash as an index into a table, but using it as a shortcut for parsing each name in a search should work just fine. Of course, some method of handling collisions might be warranted. The current collision check might be sufficient, viz., the message given by CREATE when a word already exists with the same name as the current definition attempt. As with hashing names, name collisions can occur in the current system with different names that have more characters than CREATE stores, but fewer than allowed as with a system that stores, say, only the first 3 name characters plus the actual name length. ...lee 3 Quote Link to comment Share on other sites More sharing options...
+TheBF Posted May 18, 2022 Author Share Posted May 18, 2022 Just when you think you have this Forth thing in the bag ... Over on Reddit Forth, Rick Carlino (the fella that ported Apple ][ Cosmic Conquest to GForth) found and posted a Github repository that has the Game of Life in APL, which is a one liner in APL. The APL dialect is written in Forth! If you want to tear your brain open from the inside here is the Github page. https://github.com/chmykh/apl-life The readme file has a complete explanation of how APL was implemented in Forth. Anybody know where I can buy a few more IQ points? Here is the final program code in APL in Forth. \ The Game of Life : life { _ } ←{ ↑ 1 ⍵ ∨ . ∧ 3 4 = + / , -1 0 1 ∘ . ⊖ -1 0 1 ∘ . ⌽ ⊂ ⍵ } ; 2 1 Quote Link to comment Share on other sites More sharing options...
+TheBF Posted May 23, 2022 Author Share Posted May 23, 2022 (edited) Over in another thread a comment was made about the difference in speed when accessing VDP RAM versus CPU RAM. These differences are very large when running Assembly language but I noticed that when you insert an interpreter's overhead the differences become less significant. I first saw this when I wrote up an adventure game and had all the strings compile into VDP RAM to save program space. At a glance the game seemed to respond the same as strings in CPU RAM. The little benchmark in the video shows that getting the strings to print out does not make a huge difference in Forth where the interpreter and looping constructs take up significant time and that washes out the time to read the VDP strings. These strings are being read one char at a time from VDP into the Forth top of stack register and then the register is written back to the VDP screen. About the slowest way there is. text VDP vs RAM.mp4 Next I create identical variables and constants in CPU RAM and VDP ram and stuck them in loops for 10000 iterations. The difference is only about 50%. (To be fair my kernel variables and constants are faster than these because I write them without using CREATE/DOES> but this was the easiest way to do an Apples to Apples comparison) My conclusion is that putting strings in VDP RAM is a viable use. Other data types may need evaluation as to whether the speed difference is a problem. In most cases it is probably not a problem. Here is the code for the test. Spoiler \ Forth DATA in VDP memory NEEDS ELAPSE FROM DSK1.ELAPSE NEEDS VHERE FROM DSK1.VDPMEM ( VHERE VALLOT VC, V, VCOUNT VCREATE ) \ VDP RAM const and var : VCONST ( n -- ) CREATE VHERE , V, DOES> @ V@ ; : VVAR ( -- ) CREATE VHERE , 0 V, DOES> @ ; \ make the same construct for vars and const in CPU RAM ( apples to apples) : CONST CREATE HERE , , DOES> @ @ ; : VAR CREATE HERE , 0 , DOES> @ ; \ compile stack string into VDP memory : VS, ( $adr len-- ) VHERE OVER CHAR+ VALLOT VPLACE ; \ Print a VDP stack string : VDPTYPE ( vdp_addr len -- ) BOUNDS ?DO I VC@ (EMIT) LOOP ; \ Compile a VDP string, that types itself : V." ( <text> ) ?COMP \ Abort if not compiling VHERE \ free memory location in VDP RAM [CHAR] " PARSE VS, \ compile <text> into VDP RAM upto quote \ Compile this code after the string so it prints itself ( vhere) POSTPONE LITERAL POSTPONE VCOUNT POSTPONE VDPTYPE ; IMMEDIATE \ TESTS DECIMAL VAR X VAR Y : INT-TEST 99 X ! 10000 0 DO X @ Y ! \ transfer x -> y (RAM->RAM) LOOP ; VVAR VX VVAR VY : VINT-TEST 99 VX ! 10000 0 DO VX V@ VY V! \ transfer vx -> vy (VDP ->VDP ) LOOP ; HEX 994A CONST FTI 994A VCONST VTI DECIMAL : CONST-TEST 10000 0 DO FTI DROP LOOP ; : VCONST-TEST 10000 0 DO VTI DROP LOOP ; : TEXT-TEST PAGE 60 0 DO ." Testing 1 2 3 " LOOP ; : VTEXT-TEST PAGE 60 0 DO V." Testing 1 2 3 " LOOP ; Edited May 23, 2022 by TheBF small mistake in CONST definition 5 Quote Link to comment Share on other sites More sharing options...
+TheBF Posted May 31, 2022 Author Share Posted May 31, 2022 (edited) After I tested the VDP strings above I started re-working a string library that lives in VDP RAM. By using the stack string concept (Vaddr,len) there is no penalty to cutting these strings except for the byte fetch operation and the standard Forth word /STRING work just fine even though the addresses are in VDP RAM. Concatenation uses free memory in low RAM as a buffer but writes the resultant string back into VDP RAM in unallocated space. It's a bit awkward but seems fast enough. Here is the code. I can only promise that it works in V2.69 which is not release yet. Spoiler \ VDP STRINGS Forth style stack strings in VDP Memory 31May2022 Brian Fox \ This saves lots of dictionary space in your program \ Method: \ Strings are stored in VDP RAM as counted strings. (1st byte is length) \ When we invoke the string's name, it returns a VDP stack string. \ that is a VDP address and a length. \ With stack strings we don't cut strings to make a sub-strings. \ We simply adjust the address and length on the Data stack. Very fast! \ \ Concatenation uses a temp buffer in Low RAM but writes the result back \ to VDP RAM in unallocated space. The result can be assigned to string variable \ if that is required. NEEDS DUMP FROM DSK1.TOOLS \ For debugging NEEDS ELAPSE FROM DSK1.ELAPSE \ for speed testing NEEDS VHERE FROM DSK1.VDPMEM HERE DECIMAL 255 CONSTANT MXLEN \ 255 bytes is longest string : HEAP ( -- addr) H @ ; \ returns free memory location in LOW RAM \ ==================================== \ VDP string primitives \ : VPLACE ( addr len vaddr -- ) *THIS WORD IS IN THE KERNEL* \ convert a VDP counted string to a stack string \ : VCOUNT ( vdp$adr -- vdpadr len ) DUP 1+ SWAP VC@ ; ( in DSK1.VDPMEM) \ Print a VDP stack string : VTYPE ( vdp_addr len -- ) BOUNDS ?DO I VC@ EMIT LOOP ; \ compile stack string into VDP memory : VS, ( $adr len-- ) VHERE OVER CHAR+ VALLOT VPLACE ; \ read VDP string into temp memory in HEAP, return HEAP address : VGET$ ( vaddr len -- addr) TUCK HEAP DUP>R CHAR+ SWAP VREAD R@ C! R> ; \ concat Vstring to CPU string : V+PLACE ( vaddr len addr -- ) 2DUP 2>R COUNT + SWAP VREAD 2R> C+! ; : >VHERE ( addr len -- Vaddr len) VHERE VPLACE VHERE VCOUNT ; \ a bit complicated but it works : & ( Vaddr len Vaddr len -- Vaddr len ) 2SWAP VGET$ ( -- heap) \ get 1st Vstring into HEAP V+PLACE \ concat 2nd Vstring HEAP DUP C@ MXLEN > ABORT" VDP string >255 bytes" COUNT >VHERE ; \ ==================================== \ Replicate TI-BASIC string functions : VASC ( vaddr len -- c) DROP VC@ ; : VLEN ( vaddr len -- vaddr len len) DUP ; \ makes a copy of length : VAL$ ( vaddr len - # ) VGET$ COUNT NUMBER? ?ERR ; \ These words DO NOT CUT THE STRING; they return a new (Vaddr,len) pair : VLEFT$ ( vaddr len len' -- vaddr len') NIP ; : VRIGHT$ ( vaddr len len' -- vaddr len') 1- /STRING ; : VSEG$ ( vaddr len n1 n2 -- vaddr len) >R VRIGHT$ R> VLEFT$ ; \ STR$ & CHR$ return a stack string in VDP memory \ you need to store the result in a VDP string to keep it : VSTR$ ( n -- Vaddr len) (.) >VHERE ; \ uses ascii# as an offset into unallocated VDP RAM \ ie: each char has it's own 1 byte buffer :-) : VCHR$ ( ascii# -- Vaddr len) DUP VHERE + TUCK VC! 1 ; \ =============================== \ Extra string functions HEX CODE RDROP 05C7 , NEXT, ENDCODE \ used 4x DECIMAL : VTRIM ( Vadr len char -- Vadr len') \ remove ALL trailing char >R 1- BEGIN DUP WHILE 2DUP + VC@ R@ = \ compare last character WHILE 1- \ while char is a match, decrement length REPEAT THEN 1+ RDROP ; : VSKIP ( Vadr len char -- Vadr' len') \ remove ALL leading char >R BEGIN DUP WHILE OVER VC@ R@ = WHILE 1 /STRING REPEAT THEN RDROP ; : VSCAN ( Vadr len char -- Vadr' len') >R \ remember char BEGIN DUP WHILE ( len<>0) OVER VC@ R@ <> WHILE ( R@<>char) 1 /STRING \ advance to next char address REPEAT THEN RDROP ; \ : VCLEAN ( V$ len -- v$' len') BL VSKIP BL VTRIM ; \ add a char onto the end of a VDP stack string : V+CHAR ( char Vaddr len -- ) OVER 1- >R \ compute address of length byte, rpush DUP 1+ R> VC! \ incr len, store new length in VDP + VC! ; \ compute address of end of string, store char : VPRINT ( vaddr len -- ) CR VTYPE ; \ assign text to a VDP stack string in memory : V$! ( addr len Vaddr len -- ) DROP 1- VPLACE ; : :=" ( vaddr len -- ) DROP 1- [CHAR] " PARSE ROT VPLACE ; \ Neil Baud's toolbelt compare, modded for VDP RAM \ 0 means adr1 = adr2 \ -1 means adr1 < adr2 \ 1 means adr1 > adr2 : VCOMPARE ( Vadr1 n1 Vadr2 n2 -- -1|0|1 ) ROT 2DUP - >R ( a1 a2 n2 n1) ( R: n2-n1) MIN ( a1 a2 n3) BOUNDS ?DO ( a1) VCOUNT I VC@ - ( a1 diff) DUP IF NIP 0< 1 OR ( -1|1) UNLOOP RDROP EXIT THEN ( a1 diff) DROP ( a1) LOOP DROP ( ) R> DUP IF 0> 1 OR THEN \ 2's complement arith. ; : =V$ ( adr len adr len -- ? ) VCOMPARE 0= ; : (V") ( Vaddr -- Vaddr' len) POSTPONE LITERAL POSTPONE VCOUNT ; IMMEDIATE \ compile a VDP string literal into a Forth word. : V" ( <text> -- Vaddr len) ?COMP VHERE [CHAR] " PARSE VS, POSTPONE (V") ; IMMEDIATE \ ================================ \ VDP string data structures \ create a VDP buffer of size n : VBUFFER: ( n -- vaddr) VHERE CONSTANT VALLOT ; : VDIM ( n -- ) CREATE VHERE 0 VC, \ init length to zer0 SWAP VALLOT \ allocate space , \ remember the address DOES> @ VCOUNT ; : V$ARRAY ( items len -- ) \ CREATE array of fixed length strings \ compile time action CREATE VHERE , 2DUP ( len) , ( #) , * VALLOT \ runtime action DOES> ( n -- vaddr len) 2@ ( -- n vaddr len) ROT * ( -- vaddr offset) + ( -- vaddr[n] ) VCOUNT ; HERE SWAP - DECIMAL . Here is some test code to show how this can be used. Spoiler \ VDP STRING LIB DEMO code \ create string variables. Assignment at compile time DECIMAL 80 VDIM A$ A$ :=" This is A$ in VDP RAM :-)" 80 VDIM B$ B$ :=" This B$ IN VDP RAM" 80 VDIM C$ C$ :=" 1234 " 80 VDIM D$ 80 VDIM E$ \ create string literals (cannot change these) : Q$ V" The Vquote word creates a string literal in VDP memory" ; : DIRTY$ V" <<CORE>> " ; \ You can also assign Forth strings to VDP strings with V$! S" Now is the time for all good men " D$ V$! S" to come to the aid of their country." E$ V$! : PRINT-TEST 10 0 DO A$ VPRINT B$ VPRINT Q$ VPRINT LOOP ; : .EQ/NE ( ? --) IF ." EQUAL " ELSE ." Not equal " THEN ; : =TEST 100 0 DO Q$ Q$ =V$ .EQ/NE LOOP ; : <>TEST 100 0 DO DIRTY$ Q$ =V$ .EQ/NE LOOP ; : TRIMTEST 100 0 DO DIRTY$ BL VTRIM 2DROP LOOP DIRTY$ BL VTRIM VPRINT ; : SKIPTEST 100 0 DO DIRTY$ BL VSKIP 2DROP LOOP DIRTY$ BL VSKIP VPRINT ; : CLEANTEST 100 0 DO DIRTY$ BL VSKIP BL VTRIM 2DROP LOOP DIRTY$ BL VSKIP BL VTRIM VPRINT ; : CONCAT-TEST 100 0 DO D$ E$ & 2DROP LOOP D$ E$ & VPRINT ; Edited June 1, 2022 by TheBF Changed code: VCOUNT is in LIB file, error fixed in VASC 2 Quote Link to comment Share on other sites More sharing options...
Recommended Posts
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.