5 ms·
While back I wrote a Forth interpreter. (2.4kloc of portable C, passes nearly all the ANS Forth test suite.) I recommend this to anybody interested in programmi
by david-given 10y ago
While back I wrote a Forth interpreter. (2.4kloc of portable C, passes nearly all the ANS Forth test suite.) I recommend this to anybody interested in programming languages; you don't have to finish it, but once you get the core interpreter working (in mine, the inner loop is two lines of C!) there's this aha moment and you finally get what the fuss is about, and you can feel your brain expanding.
But as a programming language to work in? Well...
- Forth has no (well, very little) runtime error checking. If your stack underflows, and you're lucky, it'll halt and tell you. But get anything else wrong, and it won't notice, and it'll just silently and invisibly poison its internal state and then crash later in a way that's really hard to debug.
- The language is strongly typed. It just doesn't have any type checking, and if you get the types wrong, it'll silently and invisibly poison its internal state and the crash later etc. You have to remember what type of object you push onto the stack, because you need to use the right kind of word to consume the object again. Does this word push a float? You need to know, because you need to use FDROP instead of DROP to consume it.
- The main culprit here is the compiler, which passes internal state around is by pushing objects onto the compiler stack. e.g. the BEGIN word, which starts a loop, pushes a 'dest' object onto the compiler stack. You have to consume this with UNTIL, WHILE or AGAIN. If you use the wrong word, or exit the definition without consuming it, it'll silently and invisibly poison its internal state etc. So you're now mentally juggling three different stacks: the data stack, the return stack, and the compiler stack, and if you make a mistake anywhere you're going to have a bad day.
- There's a lot of trivia to remember, which you need to remember. EXIT is allowed inside a BEGIN..AGAIN loop. But it's not allowed inside a DO..LOOP loop, because DO..LOOP loops are permitted to store a state object on the return stack. I will retrieve the index of the innermost DO..LOOP loop, and J will retrieve the index of the next DO..LOOP loop... but not if you've pushed something onto the return stack yourself inside the outer loop. If you don't get it right, it will silently and invisibly etc.
- Forth's only really intended to work on bare metal, and this shows. e.g. data space is assumed to increase from HERE up to the top of memory, and there's no facility for disjoint data spaces. The way you define data is to take a pointer to HERE and then just write stuff. All that stuff is defined to be contiguous. There's no scope for, e.g., running out of heap and allocating another block from the operating system. Oh, and try not to run out of memory, because it will etc.
- Stack juggling gets old very, very quickly. (Although some Forths, not mine, support local variables.)
Where Forth really excels is as an assembler substitute, where you're will to focus down and concentrate on every byte of source and you're going to put up with a complete absence of error checking because you need the performance. It's certainly great there. Being able to fit a complete interactive environment into a tiny, tiny space is astonishingly useful for debugging. But I'd hate to have to write anything of any great complexity in it.
I have heard of type-checked Forths; here's one, although I notice it takes a lot of liberties with the language (like not really having a return stack): http://www.arestlessmind.org/2009/02/03/intro.html http://www.arestlessmind.org/2009/02/03/intro.html
But I've never used one. I should, one day; I'm willing to believe that it's a much more pleasant experience than raw Forth. In particular, an embedded Forth with strict type checking but no runtime overhead sounds amazing...
(Aside: the Jupiter Ace was a Z80-based home computer from the 1980s, about the same grade as the ZX81. It came with Forth. Javascript emulator! http://jupiler.retrolandia.net/ http://jupiler.retrolandia.net/ It's got games! In Forth! Load one, do SHIFT+SPACE to exit, then 'vlist' will dump the dictionary and 'list word' will decode a word.)
- astrobe_ 10y ago> Forth has no (well, very little) runtime error checking. I don't think the standard prevents you from implementing that. If you want it, do it. > The language is [WEAKLY] typed. It just doesn't have any type checking True. However, a linter that does some type checking seems possible, provided some annotation syntax for the hard cases where it's not possible to infer types. The thing is, despite its long existence one doesn't see many "Lint for Forth" around, so we can guess people managed to solve this problem in a different way. > So you're now mentally juggling three different stacks: the data stack, the return stack, and the compiler stack, and if you make a mistake anywhere you're going to have a bad day. The "compiler stack" is never an issue unless you overuse the "metaprogramming" capabilities of Forth like you'd abuse macros in Lisp. I understand you might have had that impression if you spent more time implementing the interpreter than implementing programs. > There's a lot of trivia to remember, which you need to remember. EXIT is allowed inside a BEGIN..AGAIN loop. But it's not allowed inside a DO..LOOP loop Well of course you're supposed to know that the loop index is stored on the return stack. For a Forth programmer, it's as trivial and obvious as taking care of freeing malloc'ed memory is for a C programmer. > There's no scope for, e.g., running out of heap and allocating another block from the operating system. Oh, and try not to run out of memory, because it will etc. That's because it's typically not needed for various reasons. However if you really need it you can: - make all of your addresses relative so that your system can reallocate the dictionary space; in other words, make your VM run Place Independent Code (PIC). - create a library that allows you to allocate memory from the OS or the heap. > Stack juggling gets old very, very quickly. (Although some Forths, not mine, support local variables.) It's like saying FP is a hoax because you use mutability all the time. The problem is not in the language, it's how you use the language. > While back I wrote a Forth interpreter. I recommend this to anybody interested in programming languages; you don't have to finish it You should really finish and actually use it because you don't understand Forth yet. Oh, and don't be afraid to discard standard compliance. It's a bad trade for you: you get all the constrains and will probably never benefit from the advantages.
- david-given 10y ago> You should really finish and actually use it because you don't understand Forth yet. It's interesting that you assume I never finished it.