Jump to content
IGNORED

Substantial Forth example programs (games)?


neglectoru

Recommended Posts

I've written several forth interpreters, I've read Brodie's books, and I have a lot of experience in the traditional Algol world of languages (Java, C, Pascal, etc.)  I have even used my forth as a scripting language in games I've written (small interaction scripts for an RPG, for example.)

 

I understand forth, but I honestly don't feel like I "get" it. I wrote some games for the Jupiter Ace, but the result was a mess I couldn't follow when I came back to it weeks later.

 

Every time I try to write something big in Forth, I feel like I hit a wall. I think part of that is that I've gained so much from reading code, which is in abundance in the above languages, and really isn't for Forth. I've studied far more forth interpreters than programs written in Forth! Maybe Forth is better for radio telescopes or device drivers or firmware, which is not really the kinds of programs I'm drawn to.

 

Are there good, idiomatic, substantially sized Forth programs that the experts recommend? I'd really like to see some non-trivial games, but really any highly interactive program would be interesting. I feel like if I read some good Forth programs, mine wouldn't be so awful.

 

(I'm definitely not trying to troll here. I love programming, and I'd love to try to take advantage of what Forth has to offer.)

  • Like 1
  • Thanks 1
Link to comment
Share on other sites

I think, if I May add my 2 cents here, that documentation is crucial. The small space provided within your code following The slash mark is definitely a joke on forth users. I mean, you can say general notes as in, \ magic here.

But I always keep seperate documentation that really tells what my values are for and what each piece of program is doing. I would think it doesn't become large after that, just a routine that takes care of a situation.

  • Like 1
Link to comment
Share on other sites

3 hours ago, neglectoru said:

I've written several forth interpreters, I've read Brodie's books, and I have a lot of experience in the traditional Algol world of languages (Java, C, Pascal, etc.)  I have even used my forth as a scripting language in games I've written (small interaction scripts for an RPG, for example.)

 

I understand forth, but I honestly don't feel like I "get" it. I wrote some games for the Jupiter Ace, but the result was a mess I couldn't follow when I came back to it weeks later.

 

Every time I try to write something big in Forth, I feel like I hit a wall. I think part of that is that I've gained so much from reading code, which is in abundance in the above languages, and really isn't for Forth. I've studied far more forth interpreters than programs written in Forth! Maybe Forth is better for radio telescopes or device drivers or firmware, which is not really the kinds of programs I'm drawn to.

 

Are there good, idiomatic, substantially sized Forth programs that the experts recommend? I'd really like to see some non-trivial games, but really any highly interactive program would be interesting. I feel like if I read some good Forth programs, mine wouldn't be so awful.

 

(I'm definitely not trying to troll here. I love programming, and I'd love to try to take advantage of what Forth has to offer.)

You clearly know how to code so the words below are just me talking. Maybe it's useful. If not flush it. (not to disk) :) 

 

As you know Forth is not a popular language these days so the amount of code you find  online is pretty small.  Forth is mostly used for niche applications where size is critical or very low level control is required but Assembler is not productive enough or not well enough known for the specific processor. The niche getting smaller in a world where tiny uPs have 256K of RAM. 

 

One of the things that is hardest to learn when trying to write a substantial program in Forth is the understanding that you really don't want to program in "Forth". (that's my opinion) 

To use it well you actually create a new language that helps you write the program. This makes the front-end of the project more challenging but can make the back end and modifications simpler.

 

I have been looking at a game that was published in 1982 in Byte. It was written in Forth, but clearly by someone who had a solid background in BASIC and then wrote the game like it was BASIC only using Forth statements. It is arguably the worst example of how to write Forth code I have ever seen.

I am writing some of my thoughts on that here: CAMEL99-V2/On Forth Coding Style.md at master · bfox9900/CAMEL99-V2 (github.com)

 

I think one the best examples I have of Forth "style" in the Vibe editor by Sam Falvo. It's not a game but it shows the point.

I have ported it to TI-99 for my own education with permission from Sam.

I removed the extra commands that I added in this listing so not to obscure Sam's style.

 

Interesting things to notice.

  • There are four variables. :)
  • The definitions are very short. 
  • The word definitions become part of how you describe to solution to make the editor.
  • There is no loop structure used to list the text on the screen. The loop is un-wound because calling overhead is less than the loop overhead. 
  • There is no case statement for the command control. What???  
    • The Forth interpreter's dictionary search mechanism is the case statement.
    • Each command is looked up by dynamically creating a short string from the key stroke pressed and passing the string to FIND. 

One thing I don't agree with in this example is the stack diagram comments have been omitted. 

Using them allows new people to understand your Forth code much faster and with a memory like mine they help me remember my own intentions. 

 

If you struggle to understand your code a few weeks later then you might be writing long definitions that make understanding the stack nearly impossible a few lines into it.

The secret to making Forth understandable from what I read from Chuck Moore and others is factor factor factor.

Even if you do that however you still have to work extra hard at creating the right names for things so that their purpose is implied by the name. That's more art than science in my experience.

 

 

Spoiler

\ VIBE Release 2.2
\ Copyright (c) 2001-2003 Samuel A. Falvo II
\ All Rights Reserved
\ * Use with written permission for Camel99 Forth *
\   Highly portable block editor --

\ USAGE: VI <filepath> opens BLOCK FILE,
\        VI (no parameter) goto last used block
\        VIBE ( n -- ) Edits block 'n'.  Sets SCR variable to 'n'.
\
\ 2.1 -- Fixed stack overflow bugs; forgot to DROP in the non-default
\        key handlers.
\
\ 2.2 Ported to CAMEL99 Forth B. Fox 2019
\     Removed some character constants to save space.
\     Changed TYPE for VTYPE.
\     Removed shadow block function
\     Added some block navigation commands

\ 2.3 Fixed keyboard bugs for TI-99/4A
\     VI command takes a filename parameter like real VI
\     simplfied wipe screen logic and saved bytes
\     Add $ command: goto end of line
\     Add PC delete KEY for Classic99

NEEDS DUMP   FROM DSK1.TOOLS
NEEDS 80COLS FROM DSK1.80COL
NEEDS RKEY   FROM DSK1.RKEY
NEEDS BLOCK  FROM DSK1.BLOCKS
NEEDS -TRAILING FROM DSK1.TRAILING
NEEDS MARKER FROM DSK1.MARKER

MARKER /VIBE

HERE
( Editor Constants )
CHAR i  CONSTANT 'i   \ Insert mode
CHAR c  CONSTANT 'c   \ Command mode
\ camel99 values
DECIMAL
 64 CONSTANT WIDTH
 80 CONSTANT MAXBLKS

( Editor State )
 VARIABLE SCR       \ Current block
 VARIABLE X         \ Cursor X position 0..63
 VARIABLE Y         \ Cursor Y position 0..15
 VARIABLE MODE      \ current mode: INSERT or command ( 'i OR 'c

\ CMDNAME the command string, is built, found and executed
CREATE CMDNAME    5 C,  CHAR $ C, CHAR $ C,  0 C, 0 C, 0 C,

( Editor Display )
 DECIMAL
: BLANKS   BL FILL ; \ BF add
: MODE.    63 0 AT-XY MODE @ EMIT ;
: VTYPE    ( addr len -- ) TUCK  VPOS SWAP VWRITE   VCOL +! ;
: SCR.     0 0 AT-XY
           S" Block: " VTYPE  SCR @ . ( S"      " VTYPE ) ;
: HEADER   SCR. MODE. ;
: 8-S      S" --------" VTYPE ;
: WIDTH-S  8-S 8-S 8-S 8-S 8-S 8-S 8-S 8-S ;
: BORDER   SPACE WIDTH-S CR ;
: ROW      ( addr -- addr') DUP 63 VTYPE  64 + ;   \ FAST
\ : ROW    ( addr -- addr') DUP 63 TYPE 63 + ;  \ SLOW
: LINE     ." |" ROW CR ;
: 4LINES   LINE LINE LINE LINE ;
: 16LINES  SCR @ BLOCK  4LINES 4LINES 4LINES 4LINES DROP ;
: CARD     0 1 AT-XY BORDER 16LINES BORDER ;
: CURSOR   X @ 1+  Y @ 2+ AT-XY ;
: SCREEN   HEADER CARD CURSOR ;

( Editor State Control )
: INSERT   'i MODE ! ;
: REPLACE   [CHAR] r MODE ! ;
: CMD       'c MODE ! ;

: BOUNDED   ( addr n -- ) 0 MAX MAXBLKS MIN SWAP ! ;
: PREVBLOCK  SCR DUP @ 1- BOUNDED ;
: NEXTBLOCK  SCR DUP @ 1+ BOUNDED ;
\ : TOGGLESHADOW 1 SCR @ XOR SCR ! ;

( Editor Cursor Control )
: FLUSHLEFT  0 X ! ;
: BOUNDX     X @  0 MAX 63 MIN X ! ;
: BOUNDY     Y @  0 MAX 15 MIN Y ! ;
: BOUNDXY    BOUNDX BOUNDY ;
: LEFT       X 1-! BOUNDXY ;
: RIGHT      X 1+! BOUNDXY ;
: UP         Y 1-! BOUNDXY ;
: DOWN       Y 1+! BOUNDXY ;
\ : beep     7 EMIT ;
: NEXTLINE   Y @ 15 < IF FLUSHLEFT DOWN THEN ;
: NEXT       X @ 63 = IF NEXTLINE EXIT  THEN RIGHT ;

( Editor Insert/Replace Text )
: 64*        6 LSHIFT ;  \  x64
: WHERE      SCR @ BLOCK SWAP 64* + SWAP + ;
: WH         X @ Y @ WHERE ;
: SOL        0  Y @ WHERE ;
: EOL        63 Y @ WHERE ;
: PLACE      WH C! UPDATE NEXT ;
: -EOL?      X @ 63 < ;
: OPENR      WH DUP 1+ 63 X @ - CMOVE> ;
: OPENRIGHT  -EOL? IF OPENR THEN ;
: INSERTING?  MODE @ 'i = ;
: CHR         INSERTING? IF OPENRIGHT THEN PLACE ;
: EOTEXT      SOL 63 -TRAILING NIP X ! ;
: NEXTWORD
     WH EOL OVER - DUP -ROT  ( len adr len)
     BL SKIP  \ skip spaces
     BL SCAN  \ find next space
     NIP - 1+ X ! BOUNDX WH ;

( Editor Keyboard Handler CMDWORD encoding)
\ CMD name key: $ $ _ _ _
\                    | | |
\ 'c'=command mode --+ | |
\ 'i"=ins/repl mode    | |
\                      | |
\ Key code (hex#) -----+-+
\
\ Called with ( k -- ) where k is the ASCII key code.

( Editor COMMANDS: Quit, cursor, block, et. al. )
( Modified for Ti-99 keyboard )
: $$c51  DROP 0 19 AT-XY R> R> DROP >R ; \ : -- quit main loop
: $$c30  DROP FLUSHLEFT ;         \ 0  goto start of line
: $$c24  DROP EOTEXT ;            \ $  goto end of line
: $$c69  DROP INSERT ;            \ i
: $$c49  DROP FLUSHLEFT INSERT ;  \ I
: $$c52  DROP REPLACE ;           \ R
: $$i0F  DROP CMD ;               \ (escape) GOTO command mode
: $$c68  DROP LEFT ;              \ h
: $$c6A  DROP DOWN ;              \ j
: $$c6B  DROP UP ;                \ k
: $$c6C  DROP RIGHT ;             \ l
: $$c5B  DROP PREVBLOCK ;         \ [
\ : $$c5C  DROP TOGGLESHADOW ;     \ \
: $$c5D  DROP NEXTBLOCK ;         \ ]
: $$c77  DROP NEXTWORD  ;         \ w

( Editor Backspace/Delete )
: PADDING  BL EOL C! UPDATE ;
: DEL      WH DUP 1+ SWAP 63 X @ - CMOVE ;
: DELETE   -EOL? IF DEL THEN PADDING ;
: BS        LEFT DELETE ;
: BACKSPACE  X @ 0 > IF BS THEN ;
( Editor Carriage Return )
: NEXTLN    EOL 1+ ;
: #CHRS     SCR @ BLOCK 1024 + NEXTLN - WIDTH - ;
: COPYDOWN  Y @ 14 < IF NEXTLN DUP WIDTH + #CHRS CMOVE> THEN ;
: BLANKDOWN NEXTLN WIDTH BLANKS UPDATE ;
: SPLITDOWN WH NEXTLN 2DUP SWAP - CMOVE ;
: BLANKREST WH NEXTLN OVER -  BLANKS ;
: OPENDOWN  COPYDOWN BLANKDOWN ;
: SPLITLINE OPENDOWN SPLITDOWN BLANKREST ;
: RETRN     INSERTING? IF SPLITLINE THEN FLUSHLEFT NEXTLINE ;
: RETURN    Y @ 15 < IF RETRN THEN ;

( Editor Wipe Block ) \ simplified by BFox
HEX
: >UPPER  ( c -- c')  5F AND ;
DECIMAL
: PROMPT      0 19 AT-XY ;
: MSG         PROMPT ." Are you sure? (Y/N) " ;
: CLRMSG      PROMPT  WIDTH SPACES ;
: NO?         MSG KEY >UPPER CLRMSG [CHAR] Y <> ;
: ?CONFIRM    NO? IF R> DROP THEN ;
: WIPE        ?CONFIRM SCR @ BLOCK
              1024 BLANKS UPDATE 0 X ! 0 Y ! ;

( Editor Commands: backspace, delete, et. al. )
: $$i04       DROP DELETE ;                 \ ^D
: $$i03       DROP DELETE ;                 \ PC delete key
: $$i08       DROP BACKSPACE ;              \ Backspace
\ : $$i7F       DROP BACKSPACE ;             \ DEL -- for Unix
: $$i0D       DROP RETURN ;                 \ Enter
: $$c5A       DROP WIPE ;                   \ Z
: $$c6F       DROP OPENDOWN DOWN $$c49 ;    \ o
: $$c4F       DROP OPENDOWN ;               \ O
: $$i15       DROP X OFF  Y OFF ;           \ "HOME" key INSERT mode
: $$c15       $$i15 ;

HEX
  0F CONSTANT $0F
  F0 CONSTANT $F0

: KEYBOARD    RKEY 7F AND ;  \ for TI-99 we need to mask upper bit
DECIMAL
: CMD?        MODE @ 'c = ;
: INS?        MODE @ 'i =   MODE @ [CHAR] r =  OR ;
: MODE!       INS? 'i AND CMD? 'c AND OR  CMDNAME 3 + C! ;
: >HEX        DUP 9 > IF 7 + THEN [CHAR] 0 + ;
: H!          DUP $F0 AND  4 RSHIFT >HEX  CMDNAME 4 + C! ;
: L!          $0F AND >HEX CMDNAME 5 + C! ;
: NAME!       MODE! H! L! ;
: NOMAPPING   DROP ['] HONK CMD? AND   ['] CHR INS? AND  OR ;
\ : .CMDNAME    68 0 AT-XY CMDNAME COUNT TYPE ; \ debugging
: HANDLERWORD
  NAME! CMDNAME ( .CMDNAME)
  FIND 0= IF NOMAPPING THEN ;
: HANDLER  DUP HANDLERWORD EXECUTE ;
: EDITOR  'c MODE !
   BEGIN  KEYBOARD HANDLER SCREEN ?BREAK  AGAIN ;

: VIBE ( n -- ) DECIMAL SCR ! PAGE SCREEN EDITOR ;
\ \\\\\\\\\\\\\\\\\\\\\\\\\\ VIBE ENDS \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\

 

 

vibe 80col.png

  • Like 3
Link to comment
Share on other sites

Thanks @neglectoru for sharing your thoughts. I would also be interested in seeing a substantial Forth program. Similarly to what I understood from you, I have written a couple of Forth implementations. One for MC68000 and another I ported/wrote for MC6811. I have used Forth to debug hardware and test low level things, but I haven't used it for anything I would call substantial. For me that would mean thousands of lines of code - I know that code size is not a good metric, but I routinely write C or C++ programs of thousands of lines (or tens of thousands), but have never done something similar in Forth. Maybe its just my unfamiliarity with Forth, combined with the lack of proper debugging tools (or knowledge of such tools). If I write something in C/C++, I can look at the code years later and understand what the code is doing quickly. If I look at some Forth code, it always takes some head scratching to figure out what's going on. Probably some of that has to do with library code, with C/C++ and I know what the common libraries do, but I don't have similar familiarity with Forth...

 

The example @TheBF provided above is interesting (and I have not read it yet, looks very compact) but would not qualify as a big program.

Link to comment
Share on other sites

I wrote a few simple games in Valforth on the Atari. One of my thoughts about Forth is that it's not really a 'language to write a program in'. It's more of an 'engine to define a language to write a program in'. In essence, it's kind of like using assembler - you build all of your own language constructs. If you do it in an haphazard, informal way, you'll probably hit a wall at a certain level of complexity. That's why you need to define your words in a consistent, highly factored, loosely coupled way...just like language designers do when designing a language.

  • Like 2
Link to comment
Share on other sites

Something else is worth noting regarding Forth tool chains.

The Forth's that people are exposed to or build themselves are many times using concepts from the 1970s and 80s.

All modern Forth systems are not using threaded code. It doesn't make much sense with 32bit or larger machines.

 

So if you want to see what commercial Forth tools are like you can download the two big systems for non-commercial use for free.

 

VFX generates faster code and some people like it better. Their compilers were used for some European space missions and for a 1 million line project called Candy.

Compile time is ~one minute I believe for 1 M LOC. 

They also have compilers for a Forth CPU (RTX2000) used in space missions like Galileo and  the one that visited the comet. (Rossetta?)

https://www.mpeforth.com/software/pc-systems/vfx-forth-for-windows/

 

SwiftForth is from Forth Inc. the first Forth company, headquartered in USA.

https://www.forth.com/download/

 

Forth Inc. has a long list of clients.

https://www.forth.com/resources/who-uses-forth/

 

 

SP Forth is also a native code generating compiler from some very clever folks from Russia. They developed the nnCron product and something called EServ.

The site has a lot Russian which I don't read unfortunately, but Chrome translates reasonably well.

http://spf.sourceforge.net/

 

Here is a listing of the rather significant library set for SP.Forth.

http://spf.sourceforge.net/docs/devel.en.html

 

All that to say these are not like the Forth's we see used on our legacy systems.

These may affect the quality of a project.

 

However using a legacy system didn't slow down @Vorticon in anyway with JetPack.  :) 

Nice work there.

 

Edited by TheBF
Fixed links to Forth Inc.
  • Like 2
  • Thanks 1
Link to comment
Share on other sites

I'd have to also say, using Forth repeatedly as a go to language will also make reviewing code a bit easier because of it's structure compared to other languages. But making those important notes and even video reviews of your code as your working and saving for another day helps refresh your thinking.

Sometimes we look at our old code and say, what was I thinking when I did that!

Link to comment
Share on other sites

I had not looked at MPE's web site for a long time. Stephen Pelc, the founder told me 10 years ago that a company with a "fruit" in the logo used their new VFX compilers.

I guess it's more public now. :)

 

From: https://vfxforth.com/

(I Bolded the fruit company name)

CUSTOMERS

On some consultancy jobs clients have asked us to sign non-disclosure agreements covering not only the work but the client name as well. They regard Forth as a competitive advantage for their work. The following is thus just a partial list from our customer base.

Apple, Astell Scientific, AWE, BAE, British Telecom, British Gas, Brown Root Vickers, CDS Advanced Technology bv, CEGB, Cementation, CMON Systems, Construction Computer Software, Department of Transport, Disarmco, Europay International, Farnell, GEC Plessey Avionics, Intersil, ISRO, ITT, JWK International, Lucas CAV, Marconi Space and Defence, Metropolitan Police, NASA, Nuclear Electric, Philips, Rolls Royce, Rushton Diesels, Rutherford Appleton Labs, Saab, Safer Systems, Shell UK Oil, Sun Microsystems, Thorn EMI, Trafalgar House, Yorkshire TV, and many universities and research labs.

On one space shuttle flight, three out of four experiments were programmed in Forth, each team having chosen Forth individually. Forth is approved by NASA for high reliability applications.

  • Like 2
  • Thanks 1
Link to comment
Share on other sites

Sam Falvo's editor reads amazing.

 

From my experience:


My career has been mostly C, C++, Objective-C.    I've learned (about myself) that I get good-looking code after rewriting it two, three times as I get better ideas.


The biggest Forth program I've done was a FAT32 file system reader for SD card/SPI (not even the writer..) I thought I was on the right track, defining focused words, but even after it worked, I had trouble seeing how next to build on it.


I used bottom-up programming: read/write a byte, send a command sequence, fill a buffer, derive some numbers, follow a linked list, manage buffers... At the same time I had a clear idea of what the high-level must be: find a file, read a file.  Those are pretty concrete goals without much gap in between.


Then I see other folks' FAT32 code in Forth, it's about a screenful, and it makes mine look crazy.


The other Forth I've written has been short unit tests for my V9958 card.


With Forth I know that as soon as I put a control structure FOR..NEXT or DO..WHILE..LOOP or even IF..ELSE..THEN, I had better get everything else out of that word! Like


 

: GO BOUNDS DO I ACT LOOP ;


has all the stuff moved into "BOUNDS" and "ACT". If I don't do that, the code will be a mystery later.


Imaginary monster word:


 

: MAIN
( initialize a bunch of variables )
( figure out what the task is )
( big case statement )
( prepare for the task )
( some conditional logic )
( some tricky loops )
( closing statements of tricky loops )
( more closing statements )
( even more nesting closing statements )
( results are written )
;


All it takes is one unbalanced stack event, and it's a hopeless mess to work on. With giant messy C programs, at least you create higher-level bugs (and the solution is to write tests, then break it up.)


I learn more about factoring, continuously. I find myself writing 2-3 times more code in tests, than in the working program.


Forth is said to take a "bottom up" approach, but early on, that idea left me wondering how to ever see the way to the top?  I felt a similar stupefaction when Chuck Moore said "today's computers are so powerful, you don't need an operating system" [besides Forth.]  What, build the whole thing from the bottom up?


It's just too many levels for one person (me) to get right.


So with bigger programs, I usually have no picture of what is going to be in between top and bottom. And I really struggle with "AI", in the sense of a game-playing algorithm.  Good thing that I get paid to code "read data, compute, write data, maybe build up some state, repeat", which is atop the real-world complexity (standards docs, hundreds of data types, client-server, transaction state, persistence, weird file formats...)


So I know that when I sit down to write a C program, I'm going to be chopping up my code until the good result is 100s of lines of code rewritten 2-3 times. I never got into that kind of groove with Forth. Maybe frustrated by the editor.


I sure wish I had had big, model Forth programs long ago.

 

  • Like 3
Link to comment
Share on other sites

Lot's of points in that post Erik. 

 

All it takes is one unbalanced stack event, and it's a hopeless mess to work on.

I'm thinking you didn't test the words in the interpreter as you went along. :) 

The console is the debugger. IMHO it's the compensation for the whacky stack language.

If you have a big definition that is messing up the data stack, cut out each line, name them and test them one at a time.

Then put them back into the definition.

 

Forth is said to take a "bottom up" approach,  

Forth let's you build bottom up. Design top down is still where you begin I think.

The actual process is circular because with Forth you can write 5 lines and test your assumptions even about hardware details, rather that waiting until the whole thing is finished and you realize that interrupts actually take 3 times longer than the design spec. ?

Maybe your design part was weaker than ideal?  ;) 

 

Maybe frustrated by the editor.

Yes that can happen with a block editor. Not for everybody.

That's why the commercial systems all let you set a path for where your editor is.

 

Here is some code that indicates where Forth goes in the hands of these full-time Forth guys. 

Below is code from VFX Forth. Doesn't look like my Forth code. :) 

 

These guys programmed Forth to become what they needed. That's the ticket IMHO but also why when you come from a conventional "this is what you get" compiler it takes some getting used to.

Is it easier? Probably not in the beginning, but it gets easier the more you make it what you need.

 

 

#define IDW_FORTHWINDOW $3801
IDW_FORTHWINDOW WINDOW 0, 0, 100, 100
BEGIN
  Caption     "Forth Console"
  Style       WS_CHILD | WS_VISIBLE
              | WS_CAPTION | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX
  ExStyle     WS_EX_MDICHILD
  ClassStyle  CS_HREDRAW | CS_VREDRAW
  Icon        IDI_APPLICATION
  Cursor      IDC_ARROW
  Brush       WHITE_BRUSH
  Menu        NULL
  WndProc     ForthWindowProc
END
DialogBox:  TextDlg
    50 10 110 50 Position&Size
    Modal
    Caption "Enter Name"

    WS_VISIBLE  Style
    WS_CAPTION WS_POPUP or Style+
    WS_BORDER Style+

    8 100 0 Font "MS Sans Serif"

     5 5 100 12 IDC_EditBox z" Hello"   EditBox
    10 25 40 15 IDC_OK      z" &OK"     PushButton
    60 25 40 15 IDC_CANCEL  z" &Cancel" PushButton

    WM_INITDIALOG   does TB-DoInit
    WM_COMMAND      does TB-DoCommand

end-dialog

 

  • Like 2
Link to comment
Share on other sites

Thanks for the detailed examples! I will definitely study them in more detail.

 

My big question is often about design, which just feels so foreign to me.

 

Here is a concrete example of a toy problem, and where I run into issues. Not game related, but easy to explain quickly.

 

The concrete problem:  You have a list of calendar events. Given a date (year, month, day), print all the events on that day.

 

A calendar event is a string (event name), date/time (year, month, day, hour, minute), a duration (minutes), a "repeating flag", and an end date.

 

For simplicity, if repeating flag = 0, no repeat, flag = 1, every day, flag = 2, every week. the end date (year, month, day) is ignored if 

 

e.g., a simple 30 minute non-repeating event:

 

("E1", 2021, 11, 23, 16, 30, 15, 0, 0, 0, 0) = An event that starts on 2021-Nov-23 at 16:30 and ends at 16:45

("E2", 2021, 11, 23, 16, 30, 15, 1, 2021, 11, 27) = An event at 2021-Nov-23 16:30, 2021-Nov-24 16:30, ... 2021-Nov-27 16:30

("E3", 2021, 11, 23, 16, 30, 15, 2, 2021, 12, 15) = Same idea, 2021-Nov-23, 16:30, 2021-Nov-30 16:30, 2021-7-Dec 16:30, 2021-Dec-14 16:30

 

The algorithm is straightforward to describe

  Given a target date:

    For each event:

      If event flag is 0 and target date is event date, print it

      If event flag is 1 and target date >= event date and target date <= end date, print it

      If event flag is 2 and target date >= event date and target date <= end date and day-of-week(target-date) equals day-of-week(event date), print it

 

I know exactly how I'd implement this in C, or Javascript, or Python, but Forth?

 

Let's start with representation.  For both C and Forth, we can have a count of events, and each event can be a fixed size number of words (10, plus however you represent the event string)

 

My first problem is that every Forth system handles strings differently, so let's drop that problem for now.

 

Problem 2, how do you get fields?  In C, this is straightforward:

  int[] allEvents = [........]

  // so year(event[2]) is allEvents[event * 10 + 0), month is allEvents[event * 10 + 1], etc

  int getDay(int event) { return allEvents[event * 10 + 2]; }

 

in Forth, I suppose you'd do something similar

  : GET-DAY (event-id -- day) 10 * 2 + allEvents + @ ;

 

or something like that. I find that hard to read, but maybe you get used to it.

 

Problem 3, you need to compare dates. As this point, I'm somewhat lost.

  int equalDates(int y1, int m1, int d1, int y2 int m2, int d2) { return (y1 == y2) && (m1 == m2) && (d1 == d2); }

 

in Forth, that becomes

  : EQUAL-DATES (y1 m1 d1 y2 m2 d2 -- f) (I have no idea) ;

 

Plus, consider the fact that we need to compare dates more than once, so the stack will always have the target date on the bottom, so you need to do something like 3DUP before you consume a date, because you need to preserve the date.

 

As for the final algorithm, my resulting C code would ultimately look like the description I gave above)

 

void printAgenda(int targetYear, int targetMonth, int targetDay) {

  for (int i=0; i < numDates; i++) {

    if ((getFlag(i) == 0) && equalDates(getYear(i), getMonth(i), getDay(i), targetYear, targetMonth, targetDay)) {

      // print the event

   ...

  ...

}

 

 

Is that pretty? It's not great. I could do better, but I can sit down and understand what it does later.

 

In Forth, I'm not at all sure how I'd write this in a way I could read it. I definitely, for example, need a Forth FOR loop because I need to access "I" all the time

  ... i GET-YEAR I GET-MONTH I GET-DAY (now stack has year, month, and day)

.... but what now? How do I get targetYear, month, and day to the top of the stack so I can compare them?

 

Again, maybe it's my Algol-language training, but I'm pretty sure I could write this in a few more minutes in C, and Forth would take me hours.

Edited by neglectoru
better formatting
  • Like 1
  • Thanks 1
Link to comment
Share on other sites

7 hours ago, TheBF said:

All it takes is one unbalanced stack event, and it's a hopeless mess to work on.

I'm thinking you didn't test the words in the interpreter as you went along. :) 

The console is the debugger. IMHO it's the compensation for the whacky stack language.

If you have a big definition that is messing up the data stack, cut out each line, name them and test them one at a time.

Then put them back into the definition.

It's a hopeless mess so long as it is one monster word. Your solution is entirely correct!

Then I got into situations where a word had different outcomes and I wanted to leave "0" or "d d 1", which could go wrong.

 

7 hours ago, TheBF said:

Forth is said to take a "bottom up" approach,  

Forth let's you build bottom up. Design top down is still where you begin I think.

The actual process is circular because with Forth you can write 5 lines and test your assumptions even about hardware details, rather that waiting until the whole thing is finished and you realize that interrupts actually take 3 times longer than the design spec. ?

Maybe your design part was weaker than ideal?  ;) 

I think my difficulty was thinking in JMP and GOTO.  I had not yet encountered so many C programs that are a big while() at the top level, but I get that now. Drawing the control flow on paper would help me see where to divide it into words. 

 

Now at work I have mastered the discipline of writing very short C and Objective-C functions!  I "optimize" less.  Instead of inlining some one-line (but complex) expression as I go, I write little methods, with verbose names. For instance just to test if an item exists a couple levels deep into a data structure. Then I have

if(thingExists(dictionary, thing)) 

and not

if (dictionary[thingAssets] && dictionary[thingAssets][thing]) { do something with dictionary[thingAssets][thing] }

The compiler will optimize all that for me. 

 

I have finally, finally absorbed the lesson from freshman comp sci: "premature optimization is root of all evil. (Knuth)" Small functions from here on out. 

 

 

 

 

 

7 hours ago, TheBF said:

Maybe frustrated by the editor.

Yes that can happen with a block editor. Not for everybody.

That's why the commercial systems all let you set a path for where your editor is.

I'm trying to make my favorite editor launch  gforth. I think I need a global dictionary entry "#! /usr/local/bin/gforth".

I was typing my pre-written FORTI code into fbForth, it does have an improved block editor. Looking forward to REQUIRE.

 

But where I used the most effort was launching meCrisp on BlackIce II, then pasting in all the words so far. (it doesn't have files... maybe I I finish that FAT32 system...) Then developing a little and resetting. I can re-compile meCrisp with my dictionary built-in, but I reserved that for when I was really sure I had words finalized. 

  • Like 4
Link to comment
Share on other sites

It could take me hours as well because I would have to work up the higher level words to create the calendar etc. and I am not that smart. 

 

Forth is actually a macro assembler for the Forth VM so low level.  Chuck Moore is a fanatic about simplicity. He doesn't believe in general solutions because "I have never seen the general problem" :) 

So you do have to build more foundation yourself than C.  The commercial systems come with a pile of library code. Forth Inc has a SQL library.

Most old hands have their own personal library that they customize as needed.  

 

---------------

Let me see what happens if "spitball" your questions: (this is dangerous) :) 

 

A calendar event is a string (event name), date/time (year, month, day, hour, minute), a duration (minutes), a "repeating flag", and an end date.

For simplicity, if repeating flag = 0, no repeat, flag = 1, every day, flag = 2, every week. the end date (year, month, day) is ignored if 

e.g., a simple 30 minute non-repeating event:

 

I would keep the records in a BLOCK file which makes the data look like virtual memory.

For what you show here in small system like TI-99 I would limit the record size to 32 bytes so 32 records per 1K file block.

With that record size could fit 366 days for a leap year, with 15 events per day max on a 180K DSSD floppy disk. :) 

On a modern system that could be much bigger.

\ returns the address of the record in virtual memory
: ]RECORD  ( rec# -- addr) B/REC *  1024 /MOD BLOCK + ; 

: [REC   ACTIVE @ ]RECORD ;

 

My first problem is that every Forth system handles strings differently, so let's drop that problem for now.

This can be true but the ANS has made some progress in making the primitives more standard. 

All the commercial systems provide libraries. So too, all the TI-99 Forths. 

        

Problem 2, how do you get fields?  In C, this is straightforward:

ANS Forth has structs

I my library I defined CELL: and CHAR: using the primitive word +FIELD. 

Even here Forth gives you primitives. You make the rest. 

\ We can access the fields using ANS Forth structures
 0   ( accumulator ) 
 9 CHARS: EVENT]
     CELL: YEAR]
     CHAR: MONTH]
     CHAR: DAY]
     CHAR: HOUR]
     CHAR: MINS]
     CELL: DURATION]  ( mins)
     CHAR: FLAG]
     CELL: YEAR2]
     CHAR: MONTH2]
     CHAR: DAY2]  
     CONSTANT REC-SIZE


 

So with the struct words and [REC  we can access a disk records like this:

 

5 ACTIVE !     ( sets the active record) 

[REC YEAR] @ .   ( fetch and print integer)

[REC FLAG] C@ .  ( fetch byte and print it) 

 

 

Since you have defined your event like that I would be temped to make the code look like that.  These lines put data to disk records.  :) 

0 REC# '( " E1"  2021,11,23,16,30,15,0,0,0,0 )EVENT
1 REC# '( " E2"  2021, 11, 23, 16, 30, 15, 1, 2021, 11, 27 )EVENT   
2 REC# '( " E3"  2021, 11, 23, 16, 30, 15, 2, 2021, 12, 15 )EVENT  
3 REC# '( " E4"  2021,11,23,16,30,15,0,0,0,0 )EVENT
4 REC# '( " E5"  2021, 11, 23, 16, 30, 15, 1, 2021, 11, 27 )EVENT   
5 REC# '( " E6"  2021, 11, 23, 16, 30, 15, 2, 2021, 12, 15 )EVENT  
6 REC# '( " E7"  2021,11,23,16,30,15,0,0,0,0 )EVENT
7 REC# '( " E8"  2021, 11, 23, 16, 30, 15, 1, 2021, 11, 27 )EVENT   
8 REC# '( " E9"  2021, 11, 23, 16, 30, 15, 2, 2021, 12, 15 )EVENT  

 

 

I know exactly how I'd implement this in C, or Javascript, or Python, but Forth?

To be fair that's a bit like saying "I know exactly how to write that in French, or German, or Dutch but Russian?"

 

Plus, consider the fact that we need to compare dates more than once, so the stack will always have the target date on the bottom, so you need to do something like 3DUP before you consume a date, because you need to preserve the date.

If you are doing that then something is wrong.  You should never be accessing deeper that 3 items 4 absolute MAX into the data stack.  Factor that sh*t out.  

If you really have a complex problem, then ANS Forth now has locals on a stack frame like C, but there is performance hit versus doing it without stack frames.


I have not written it yet but I would do:  TIME@ >SECONDS  returning a long integer value (32bit on TI-99) 

With that method you will just read the field from each record and reduce each one to seconds then do 32bit compare  (D=)  and that's it. 

There would 2 items per double on the stack but because you use 32bit operators you don't need to know that.

 

In Forth, I'm not at all sure how I'd write this in a way I could read it. I definitely, for example, need a Forth FOR loop because I need to access "I" all the time

Forth has the DO LOOP  with an index called I , (J , K) for nested loops.

 

 

So all that to say here is a very simple database that writes to disk for your data.  

I am an amateur here. I left engineering work over 25 years ago and worked on the business side of things so I am re-learning this stuff for my own amusement. 

And... trying to remember all the crap in the libraries that I wrote over the last 2 years. :) 

 

However you can see that you don't actually write Forth like C or any other language for that matter.

You can... but you will hate it. :) 

Maybe that's why so many people do? :)))

 

Spoiler

\ Example calendar data base

INCLUDE DSK1.TOOLS
INCLUDE DSK1.BLOCKS
INCLUDE DSK1.STRUC12
INCLUDE DSK1.UDOTR
INCLUDE DSK1.CASE

DECIMAL
\ 30 S" DSK5.CALENDAR" MAKE-BLOCKS

S" DSK5.CALENDAR" OPEN-BLOCKS 

32 CONSTANT B/REC 
VARIABLE ACTIVE 

: REC#  ( n -- ) ACTIVE ! ;

\ returns the address of the record in virtual memory
: RECORD  ( rec# -- addr) B/REC *  1024 /MOD BLOCK + ; 

: [REC   ACTIVE @ RECORD ; \ use with words that end with ']'

\ We can access the fields using ANS Forth structures
 0   ( accumulator ) 
 9 CHARS: EVENT]
     CELL: YEAR]
     CHAR: MONTH]
     CHAR: DAY]
     CHAR: HOUR]
     CHAR: MINS]
     CELL: DURATION]  ( mins)
     CHAR: FLAG]
     CELL: YEAR2]
     CHAR: MONTH2]
     CHAR: DAY2]  
     CONSTANT REC-SIZE


: " ( -- addr len) [CHAR] " PARSE-WORD ;
: PARSE-INT   ( -- n )   [CHAR] , PARSE-WORD  EVALUATE ;

HEX
: PARSE-BYTE  ( -- c ) 
  [CHAR] ,  PARSE-WORD  EVALUATE DUP FF00 AND ABORT" Bad byte value"  ;

DECIMAL
: ?EVENT  ( addr len -- addr len)
          DEPTH 2 < ABORT" Quoted string expected"
          DUP 9 > ABORT" String too long"
;

: '(   

   [CHAR] " PARSE-WORD  [REC EVENT] PLACE
    PARSE-INT  [REC YEAR] !
    PARSE-BYTE [REC MONTH] C!
    PARSE-BYTE [REC DAY] C!

    PARSE-BYTE [REC HOUR] C!
    PARSE-BYTE [REC MINS] C!
    PARSE-BYTE [REC DURATION] !

    PARSE-BYTE [REC FLAG] C!

    PARSE-INT [REC YEAR2] !
    PARSE-BYTE [REC MONTH2] C!
    PARSE-BYTE [REC DAY2] C!
;

: )EVENT
   UPDATE FLUSH   \ mark block updated and flush to disk
;  \ advance to next record

: DATE@  ( -- day month year)
   [REC DAY] C@  [REC MONTH] C@  [REC YEAR] @  ;

: END@   ( -- day month year)
   [REC DAY2] C@  [REC MONTH2] C@  [REC YEAR2] @  ;

: TIME@  ( --   mins hr ) [REC MINS] C@   [REC HOUR] C@  ;


: .YYYYMMDD  ( day month year --)  4 .R ." /"  2 .R ." /" 2 .R ;
: .hhmmss    ( sec mins hr --)     2 .R ." :"  2 .R  ;

: .REPEATS ( n --)
    CASE
      0 OF  ." No"     ENDOF
      1 OF  ." Daily"  ENDOF
      2 OF  ." Weekly" ENDOF
           ." ???"
    ENDCASE ;

:  PRINT] ( addr -- )
   CR ." Name: " [REC EVENT] COUNT TYPE  4 SPACES ." Date: " DATE@ .YYYYMMDD
   CR ." Starts:"  TIME@ .hhmmss
   CR ." Duration:" [REC DURATION] @ 4 .R  ."  mins"
   CR ." Repeats: " [REC FLAG] C@ .REPEATS
   CR ." Ends: "    END@ .YYYYMMDD
;

: ERASE]   B/REC 0 FILL ;

: LIST ( -- )  9 0 DO   I REC# [REC PRINT] CR    LOOP ;


\ ("E1", 2021, 11, 23, 16, 30, 15, 0, 0, 0, 0)
\ Since you have defined your event like that I would be temped to make the code
\ look like that and drop that data into a record. 
0 REC# '( " E1"  2021,11,23,16,30,15,0,0,0,0 )EVENT
1 REC# '( " E2"  2021, 11, 23, 16, 30, 15, 1, 2021, 11, 27 )EVENT   
2 REC# '( " E3"  2021, 11, 23, 16, 30, 15, 2, 2021, 12, 15 )EVENT  
3 REC# '( " E4"  2021,11,23,16,30,15,0,0,0,0 )EVENT
4 REC# '( " E5"  2021, 11, 23, 16, 30, 15, 1, 2021, 11, 27 )EVENT   
5 REC# '( " E6"  2021, 11, 23, 16, 30, 15, 2, 2021, 12, 15 )EVENT  
6 REC# '( " E7"  2021,11,23,16,30,15,0,0,0,0 )EVENT
7 REC# '( " E8"  2021, 11, 23, 16, 30, 15, 1, 2021, 11, 27 )EVENT   
8 REC# '( " E9"  2021, 11, 23, 16, 30, 15, 2, 2021, 12, 15 )EVENT  

 

 

 

 

DATADEMO.png

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

I asked for some examples of large Forth projects on comp.lang.forth.

 

This one came in that shows Forth working in the layer cake of languages:

 

https://ojs.library.queensu.ca/index.php/PCEEA/article/view/4004

 

From a quick read:

1. Forth was used to write the CNC interpreter.

2. C was used to write the Forth system called PFE.

3. Python was used to write the GUI

 

So using each tool to its strength.  Nice.

  • Like 1
Link to comment
Share on other sites

Thanks @TheBF for your examples, and for tinkering with my toy problem! I'll definitely check out the examples in more detail.

 

I think you put it well. The Algol-like languages are all very different than Forth, and I think they've influenced (poisoned?) my thinking for much of my adult life. :) 

 

That said, I think Forth's minimalism and low-level nature may contribute to its difficulty to read. In my example of having an event structure, I invented getter methods for the structure, and didn't know about ANS forth structs which solve basically the same problem. Things that are common in other languages (strings, objects, structures, literals) are up to each Forth environment to re-invent.

 

Every C implementation "agrees" on some concepts (structs, pointers, function calls, etc.). The only thing that Forth implementations really agree on is that every word evaluates left to right. Much of the time they operate on the parameter stack (or return stack), some of the time they define new concepts.

 

Forth systems agree on some common vocabulary (:, DUP, SWAP, +, etc.) but then differ widely when you leave this minimalistic set. This means that either your problem should be well suited to the minimalistic set (doing things easier than you would in an assembler, like a device driver), or you need to create your own custom language, or you need to rely on what your particular Forth environment gives you for higher level problems, or you need to build your own Forth interpreter that can address the kinds of problems you want to solve. Since it's so much easier to write a Forth interpreter than a C compiler, that happens a lot.

 

But it does mean that if you make poor choices of what your interpreter should look like, or what your new vocabulary should feel like, you will get yourself into a very deep pit.

 

That's why I wanted to see examples of successful big forth systems, and see what they do right. So thanks again for the examples and the discussion!

  • Like 4
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...