Implementing a Forth

15 comments

Problem with Forth and Lisp is that you can freely start to improve language, so it sort of destroys the usefulness in a company of full of Salarymen and Office Droids.

Here is Grok's scheme to make Forth more readable:

  Below is a Forth implementation of a simple parser that transforms
  (. (+ 1 2)) into 1 2 + . and executes it. This assumes the input is
  provided as a string or entered interactively, and the parser outputs
  the transformed Forth code to the input stream for execution.
  
  
  \ Buffer to store transformed output
  20 CONSTANT BUF-SIZE
  CREATE BUF BUF-SIZE ALLOT
  VARIABLE BUF-PTR
  
  : INIT-BUF ( -- )
    BUF BUF-SIZE ERASE
    0 BUF-PTR ! ;
  
  : ADD-TO-BUF ( c -- )
    BUF-PTR @ BUF-SIZE < IF
      BUF BUF-PTR @ + C!
      1 BUF-PTR +!
    ELSE
      ." Buffer overflow" ABORT
    THEN ;
  
  : PARSE-EXPR ( addr u -- )
    INIT-BUF
    BEGIN
      DUP 0> WHILE
      OVER C@ CASE
        '(' OF DROP 1 /STRING ENDCASE  \ Skip (
        ')' OF DROP 1 /STRING ENDCASE  \ Skip )
        '.' OF BL ADD-TO-BUF '.' ADD-TO-BUF BL ADD-TO-BUF
               DROP 1 /STRING ENDCASE
        '+' OF BL ADD-TO-BUF '+' ADD-TO-BUF BL ADD-TO-BUF
               DROP 1 /STRING ENDCASE
        BL OF BL ADD-TO-BUF DROP 1 /STRING ENDCASE  \ Handle spaces
        ELSE
          DUP ADD-TO-BUF  \ Copy number characters
          DROP 1 /STRING
        ENDCASE
      REPEAT
    2DROP ;
  
  : EVAL-EXPR ( addr u -- )
    PARSE-EXPR
    BUF BUF-PTR @ EVALUATE ;  \ Execute the transformed string
  
  \ Example usage
  : TEST ( -- )
    S" (. (+ 1 2))" EVAL-EXPR ;
  
  TEST

Bloody Grok does not seem tobe able to make generic parser, so that Forth whitespace requirements can be ignored. When said I want "Pascal-type" tokenizer, it started to print Pascal-language compiler, which was amazingly complex and long. I cannot imagine many humans can produce that much Forth and survive.

I adore the `Threaded Interpretive Languages` book! It very clearly describes the internals of a real language, and variations on how to get it all done.

https://archive.org/details/R.G.LoeligerThreadedInterpretive...

Yes, it's a lovely book and led me to my first paid project over 40 years ago! Using the book I built a Forth to monitor the local water authority systems, with water-level tracking and customer accounting & billing, implemented on a home-brewed network of North Star PCs. Iirc there's a minor bug in one of the base subroutines as written though now I don't remember what it was. But I still have the book :-)

I am under impression that more people implement Forth than use it for programming...

we used FORTH extensively in the bring-up of the Atari ST. it was easy for the hardware engineers to come up with little fragments of FORTH so they could exercise their chips without needing much support from the software team

FORTHs are fun to write. they'll teach you a lot about ruthless simplicity

i don't think FORTH is useful for large programs, but that's not the point of the language

Why is it not useful for large programs (if you keep your words small)? What about Factor?

Large programs mean lots of word definitions and essentially writing a DSL, most are not going to document that DSL well enough to make the code readable to anyone else and how many people want to learn a weird adhoc language myopically designed for building one program? I love Forth but it mostly died for good reason. Once you learn its workflow and develop your dictionary it is incredibly quick and easy to use but it will be a chore for anyone else to understand and work with.

So if it is documented well, then it would be suitable for large-enough programs? Or do you have any tips?

I think it can work but it takes a different way of looking at things. The main thing is that you need to take the time to design that DSL from the start and stick to it, if you come to a place where the DSL can not do what you want you need to address the issue with the DSL instead of just falling back onto the underlying Forth. It is a very different way to program and at times you find yourself have to make massive changes to the DSL you designed which also means changes to the program you are using it to write.

DSL is probably not quite the right word, it could be but generally I think it is still going to be fairly Forth like, but you need to think of it that way. You layout what your program does and then reduce it to a few dozen words which work as a description language for the program, then maybe a 100 more words which those words require along with any glue and together all constitute a reasonably sane language. Then you start from the bottom up and implement all those words. The big trick is having the foresight required to pick the function of each of those hundred odd words well enough that no one will ever have to dig into the the 1000 odd words below that it took to implement them. It really forces you to think about how your program may evolve and the bugs which may crop up, you can't change one of those low level words without affecting all that are built on it and if you do the easy work around things start to get messy.

Not sure how well I explained that, I am no Forth expert but I am getting better.

That's a nice take. Implicitly though, that implies treating words not as functions but as coupled routines, possibly because forth is untyped.

If anything goes wrong during development you need that model of the stack in your head to debug- I think that's the main differentiator, and if you had a typed forth with local variables, it would enable greater readability ie. I can scan the individual words/ functions without needing that stack model.

Just my POV, not sure if that resonates?

Forths can have local variables, both ANS Forth and gforth have them and if your forth does not have it you can make your own or possibly just include gforth's if your forth is compatible. Keeping track of the stack becomes second nature and all but the simplest implementations have great tracing and debugging if you get lost.

Forths can also have types but it is slightly different, gforth has a float stack with its own commands and you can implement others and any other feature you want.

Once you get used to working global and without type it gets surpringly easy, it is more difficult for me to work with type and scope than without and when I use something like C I am constantly fighting it and feel like erything is needlessly complicated.

There's no compile-time error checking and words can push/pop arbitrary amounts of data to/from the stack. This means that if a program grows beyond the point where you can keep the whole thing in your head, it's a nightmare to maintain since bugs like giving a word the wrong number of parameters can cause a cascading series of errors that's challenging to unravel. I don't think it's a coincidence that Forth advocates have a philosophy of "if a problem's too complex, redefine the requirements so you can fit it all in your head". Personally, while I find Forth fun to write trivial programs in, I would rather write nontrivial programs even in assembly than in Forth.

It's a great exercise as a programmer to implement a language, Forths and Lisps are popular choices.

It greatly helps conceptualizing computation and what's truly essential at it's core.

there's a myth I know where the true Forth enthusiasts end up making their programs so pure that they start needing their own special hardware (in one version, I heard that machine words were all 13 bits), their own networking stack, their own communication apps. Until eventually they transcend into a second internet, populated only by forth hackers, where everything is optimally elegant.

and that's why we don't hear from them anymore

[Drinks] O true apothecary! Thy drugs are quick. Thus with a KISS I die. [Dies]

((One W. Shakespeare Romeo and Juliet.))

That's okay, it's a fun thing to do!

That's kind of by design, and it's why the standardized ANSI forth sucks. It will often be simpler to rebuild the language on your system the way that works for you, than to hammer a generic forth into what you need.

It is educational thing to do. Like Lisp as well.

With Lisp you have beasts like Guile and Guix with Scheme and with Common Lisp, Maxima, Nyxt and even weird stuff like K3D or whatever was called where you could build 3D scenes while programming. A la PovRay, but with Common Lisp and in-place editing instead of batch rendering.

I have made a living with Forth since 1981. I have written very few new Forth kernels, but have ported them to many CPU architectures. Forth is no longer fashionable, but it works.

Writing a new Forth may be an interesting project, but you will not write a good one until you have a few applications under your belt. Forth is a very subtle language. The internet is full of abandoned Forth kernel projects. Many of these may well have succeeded if they had a purpose that was not yet satisfied.

It is perfectly possible to write large applications in Forth. One of our clients has an application of 1.4 million lines of Forth source code. It is hosted on VFX Forth, which compiles Forth to native code. The VFX version runs at least ten times faster than the previous threaded code version, built on MPE's ProForth compiler.

I'm really curious, how many people collaborate on that 1.4MLOC application? I'd think you might end up with "too many cooks in the kitchen" sort of problems because of how much power the language gives everybody.

It's a sort of silly trivial example... but I've watched multiple python codebases slowly become unmaintainable because Python has no way to enforce what can be called or overridden by instances or subclasses, and the org grew beyond its ability to enforce discipline about it in code review.

I feel like Forth would offer orders of magnitude more "temptations" for somebody who just needs to get something done, but I've never seen it done at scale.

I went down the "make your own Forth" rabbit hole about 45 years ago.

In January 1979, Byte Magazine's Language Forum contained the article, "IPS, An Unorthodox High Level Language."[1] The article described IPS, a language based on Forth, but with the word names translated to German. Thus, Forth's SWAP became VERT, short for vertauschen. The intriguing article concluded with a reference to Charles Moore's 1974 paper, "FORTH, a New Way to Program a Minicomputer,"[2] which was discussed on HN in 2022.[3]

I had recently assembled a Quest Electronics Super Elf computer[4] with an expanded memory size of 4 KB. The IPS article mentioned implementations under 6 KB for 8080, 6502, and RCA COSMAC microprocessors, so I thought Forth might fit. The Super Elf included an 1861 video display controller chip, with a resolution of 64 x 128 pixels (big pixels!). I designed a font of 3 x 5 pixel characters to provide 21 lines of 16 characters. Good luck distinguishing M, N, H, U, and W without some context. I bought a (possibly surplus) keyboard from Radio Shack and screwed it onto a wedge of wood to achieve a usable typing angle.

Moore's paper described about 75 Forth words. I wrote them on index cards and jotted down Forth definitions or RCA 1802 assembly code. I wrote an 1802 assembler in Fortran to ease the conversion into 1802 machine code. I still have the printouts and punch cards in storage.

Development proceeded slowly. During a break at work, I would punch my assembly code for a few Forth words, run the assembler, and bring home the printout. That night I would load previous work into the Super Elf from cassette tape, key in the new words (and changes) via the Super Elf's hex keypad, and save back to another cassette tape. Then I would test the new Forth words and note any changes needed on the printout. Lather, rinse, and repeat until, at last, it all worked.

The finished Forth system consumed a little more than 3 KB of the 4 KB of memory. User programs, data, and Forth stacks occupied the remaining memory. The R key stuck. The @ symbol was a 3 x 5 pixel blob (this is the very important Forth memory fetch operator). But it worked!

I demonstrated the system at the local personal computer club--spun off from the local ham radio club. Based on their enthusiasm, I advanced to developing tinyForth[5] for the TRS-80, a story for another day.

[1] https://archive.org/details/BYTE-MAGAZINE-COMPLETE/197901_By...

[2] https://articles.adsabs.harvard.edu/pdf/1974A%2526AS...15..4...

[3] https://news.ycombinator.com/item?id=33134663

[4] https://www.oldcomputermuseum.com/super_elf.html

[5] https://archive.org/details/80-microcomputing-magazine-1980-...

Wow! What an amazing story!

I started writing forth few months ago, wrote few interpreters, with jit or with types etc, and its just amazing, I think anyone should do it. TBH I don't think any other exercise has thought me as much about programming as this.

I also notice the "return" of Forth, as it is probably the easiest high level language to make for computers with addressable memory and fetch execute cycle. The parser is just few lines of assembly and of course you can write the parser in the inner interpreter's bytecode, you don't even need assembly :) So hobbyists can just "make it" and make their own tiny operating systems with it. Of course everyone makes their own dialect, but I think thats OK. Things like https://github.com/howerj/lfsr LFSR CPU/VM running Forth, or UXN or duskos/collapseos.

Now you can also use language models to help you onboard into the language, it do some practice programs and rewrite one program in many ways.

So if you are young or old and never tried to Forth, don't miss out, its super fun.

Howerj has a really easy subleq/muxleq interpreter. Eforth under Muxleq runs fast enough (perfectly usable) under an ATOM n270. Subleq too, but Muxleq it's must faster.

What kind of programs would one naturally reach for Forth as the optimal solution? It has always struck me as a very low level language but I rarely hear this caveat from its advocates.

I highly doubt it's useful these days on general purpose computers, where even the very smallest ones can easily implement C (or better) or even run a whole operating system like Zephyr or Yocto.

However if you're creating your own computer from scratch or have other artificial constraints (like it needs to fit into a boot sector[1]), and if you want to really understand precisely how your language works down to the lowest level detail while still having high-ish level constructs, then Forth is the ideal small language for that.

[1] https://github.com/cesarblum/sectorforth

Arduino's, ESP32 based minicomputers, TRS100 like handheld computers...

It's useful for programming with functions to return many elements at once.

I program in forth for a living, and that has been one of my favourite advantage, multiple returns without unecessary re-bindings.

I thought you fixed a sailboat for a living :)

That's where all my savings are going, same difference Kragen ;)

They say a boat is a hole in the water into which you pour

It made more sense in the 8 bit home computer era, where you could do a more high level coding with more performance than a BASIC interpreter, without having to mess with hexdumps for DATA segments, or Assembly opcodes.

And specifically this is because compiled Forth uses a simple kind of compression. (This is explained a bit better in jonesforth).

I would put it into these three categories:

1. Assembly coding within a REPL. Forth supports "load-and-store" without the additional bookkeeping steps of assembly. Once the program works, it can be incrementally rewritten into the assembly if needed, or used to bootstrap something else. Historically this is probably the single biggest usage, because the language works as a blunt instrument for that within the standard wordsets. Lots of programs on the early micros shipped with code that was developed with Forth, but with the Forth interpreter discarded at the last step; and where there is novel hardware and novel applications, Forth tends to come up as the bootstrap.

2. Minimal-dependencies coding. For the same reason that it's a good bootstrapping tool, Forth ends up being portable by assuming nothing. While different Forth systems are all subtly incompatible, the runtime model is small enough to wrangle into doing what you want. Stack machine VMs basically are "Forth with more sandbox and less human-readability".

3. "Big ideas" coding. The "human-readable stack machine" aspect means it's a useful substrate for language design - being programmable, you can shift the imperative interpreter model in the direction of new syntax and new general-purpose data structures, while still retaining a way to drop all the way down to assembly - the biggest downside is that this doesn't let you easily introduce existing library code, so bootstrapping from within Forth would take a long time and you would most likely get stuck on trivial string processing. But Forth as the second of a two-step process where you "compile to Forth" using something more batteries-included is actually pretty reasonable as an alternative to generating a binary or designing an original VM.

I have yet to use Forth for anything serious, but it's worth pointing at an example for how expressive you can be dealing with hardware. From https://www.forth.com/embedded/#Embedded_Programming_Example the top level control for a washing machine could be:

    : WASHER ( -- ) WASH SPIN RINSE SPIN ;
I won't repeat all the rest, even though it's short, but let's look at a few other definitions that build up to that:

    : RINSE ( -- ) FILL-TUB AGITATE DRAIN ;
    : DRAIN ( -- ) PUMP ON 3 MINUTES ;
    : ON ( mask -- ) PORT C@ OR PORT C! ;
    4 CONSTANT PUMP
    01 CONSTANT PORT
All very high level and fairly straightforward to understand word-by-word until you get to the bottom, that being the last 3 lines I pasted. (Though in a file this would be written the other way around with WASHER being the final line.) The PORT is a memory mapped hardware address, PORTB on a 68HC12 chip, and various other constants like PUMP are defined as bitmasks to get at individual bits on the port. The ON word takes the current value at the PORT address, ORs it with the stack-passed mask, and sets it back.

In C this might look like (again in reverse order):

    void washer(void) {
        wash();
        spin();
        rinse();
        spin();
    }
    void drain(void) {
        on(PUMP);
        minutes(3);
    }
    void on(uint8_t mask) {
        PORT |= mask;
    }
    #define PUMP (1 << 2)
    #define PORT (*(volatile uint8_t *)0x0001)
That is, if you program the C in a similar procedural style that closely follows the physical function of the device. It's maybe more common to express similar C code in a state machine style, in which case it'd look a lot different.

Is the Forth version actually worth it? To me the C is "good enough", and more explicit or at least clearer as I'm familiar with C. Where Forth could win me over is in its ease of having a truly interactive environment to iteratively develop the software via a REPL, much like Lisp. (But then I'd rather just use Lisp.) My embedded systems hobby work for the last 10+ years has all been really simple, though, so interactivity isn't a big enough advantage to just using C. (When working with hardware, it's somewhat exciting to stick a pin in a breadboard and move it between power and ground to toggle a motor. It'd be even more exciting to do this interactively via REPL commands poking at memory.)

It's used in a number of EFI implementations. Its good when you have no environment at all, hence bootloaders. Factor is a more general purpose forth-like language.

[deleted]

Not sure if this counts as an implementation, but this is my Forth compiler, written in the high-level language Go [0]. This is also a challenge, as Forth usually requires machine language, call stack access, and is naturally written in assembler. Took several tries until I found working data structures.

The funniest thing is, you can easily write and invent your own control structures in Forth, as well as change the behavior existing ones such as the do/while loop.

    \ alter the behavior of do by printing the current index in each loop.
    : do immediate
        [COMPILE] do   \ first compile previous do impl.
        ' i ,          \ then print current index
        ' . ,
    ;

[0] https://github.com/s-macke/Forthly

> This is also a challenge, as Forth usually requires machine language, call stack access, and is naturally written in assembler.

Not really. Many implementations indeed do that, but only in pursuit of performance - the weakness of interpreted languages is speed. Otherwise one can just implement it as a bytecode interpreter, like e.g. Lua.

My code uses some kind of very abstract byte code in a global

    heap  []any
structure (threaded code). "any" can be different types including Go functions. But most of them are pointers as usual in Forth.

I think for most people they would be better off developing their Forth in a high level language and writing their own software stacks and the core words. Once you have that start implementing other features in that high level language instead of in yourth Forth, add functions and scope, OOP, data structures, anything you can think of, you will learn a great deal about programming languages, their design and how they work. Doing things the Forth way of a minimal Forth and building everything out in Forth is useful for those who want to learn the low level stuff but once you have implemented that minimal Forth, most of what you learn is Forth.

I am currently implementing a stack based language as an intermediate language for a C compiler that I am developing. I have developed a relatively straight forward compiler for the stack language to the M1 assembler from live-bootstrap. This stack language has local variables. It actually makes use of two stacks: One in the traditional sense and one for the local variables (and the return address for calls). It reads as a kind of very primitive post-fix C kind of language. See: https://github.com/FransFaase/MES-replacement and https://www.iwriteiam.nl/D2506.html#2b

I've messed around with Forth before, and even created my own one. I could do cool things with it, and gotten a DSL-like language.

I used words like IMMEDIATE and POSTPONE to create words that create words. I quickly get lost doing that, though.

Forth is very very cool to play with. Pragmatically, virtually any other language is better.

I recently implemented a Forth, compatible with the Forth Haiku dialect used by https://forthsalon.appspot.com/ -- it uses a tail call/continuation-passing dispatching method, and performs some rudimentary optimizations. At some point it also spit out some C but I decided to give this feature the axe until it has a better infrastructure for optimizations and codegen.

The idea is to use it to drive an LED matrix and have a simple web UI to develop "fragment shaders" in Forth. It's developed as part of the Lwan project, although currently it generates GIF files on the fly rather than drive a LED matrix.

The source code is here for those that want to play with it: https://github.com/lpereira/lwan/tree/master/src/samples/for...

My first interpreters were Forth-derivatives, it's a great way to start designing your own languages.

I've recently been working on a project to lower the barrier to entry for budding language designers. The language is strictly prefix, but could easily be turned into postfix with minor changes.

https://github.com/codr7/shi

Is writing a Forth in a high-level language feasible/useful as a learning exercise? Almost all the Forth discussion I’ve seen has been about implementing in assembly.

Could you use the Crafting Interpreters book as a guide to build a Forth interpreter?

https://craftinginterpreters.com/

You can start way simpler than that, as forth won't need an AST or a complex parser.

See this repository for a tutorial-approach to building a minimal forth-like language, in golang:

https://github.com/skx/foth

Sure, but all you really need is the Forth spec and some basic programming skills in any language, even Bash[0] will work. Get a working Forth up and running with the core words and then start improving it and expanding it, develop that basic core into a higher level interpreted language. You will learn far more than you would following a guide.

[0] One of my odder Forths was fs4th, FileSystemForth. It was implemented with Bash aliases and variables, the stacks were just paths, there was a directory called stack and in it there were numbered directories to represent the stack and what you pushed and popped onto the stack were files. It was a weird way to work with files but I could see it having niche uses. I also made a more normal Forth in Bash as a quick project to refresh my Bash skills.

With AWK an RPN calculator it's dumb easy; and from that, a Forth it's just a matter of time.

One of the best articles on tiny Forth interpreters that I've seen on HN for a long time, with many excellent links for further study!

[dead]