how to print forewards a stack string

23 views
Skip to first unread message

p da

unread,
Jan 18, 2026, 10:37:59 AMJan 18
to 4th-co...@googlegroups.com
Let's say I save a string in the stack in a way end of string is in TOS-1 and string length is in TOS,  for example "Hello" is stored as:

H e l l o 5     (being 5 in TOS)

What I want is to print this string forewards defining a unique word (not using helping words) without recursion (i.e. using a loop)

Let's call that word pstr , so doing:

S: <6>  H e l l o 5 
pstr               Hello ok
S: <0>

defining the word using recursion is pretty simple:

: pstr swap >r 1- dup 0 > if recurse then r> emit ;

the challenge is to define it without recursion.

A related problem is to make the opposite word, one word that reads chars from keyboard and store it in stack in the order previously stated.  Doing this without recursion is also quite easy:

: sstr 0 begin key dup 32 > while dup emit swap 1+ repeat drop ;

the problem now is to define it using recursion but with only one word, without helper words

My best try is using a helper word just to init counter:

: rsstr key dup 32 = if drop else dup emit swap 1+ recurse then ;
: sstr 0 rsstr ;

but this obviously doesn't fit in the proposal challenge.

any idea to solve both?

greets

The Beez

unread,
Jan 18, 2026, 11:05:49 AMJan 18
to 4tH-compiler
: reverse
  0 begin over over > while rot >r  1+ repeat drop
  0 begin over over > while r> emit 1+ repeat drop drop
;

char H char e char l char l char o 5 reverse cr

And you already got the solution - just feed it "0 rsstr". Nobody told you you have the call the word without a parameter ;-) 
Truth is - you can initialize only once. That's hard when using a recursive word. Try a fibonacci or factorial (recursive) without providing a parameter.. But maybe I'll come up with something.

Reversing stuff is easy when you plug in a (second) stack. BUT: you either need a count or a terminator to successfully pull that off.

Hans Bezemer

The Beez

unread,
Jan 18, 2026, 11:23:33 AMJan 18
to 4tH-compiler
Got it:

: try 0 [: key dup 32 = if drop exit then dup emit swap 1+ recurse ;] execute ;

Hans Bezemer

try Hello ok 6
.s <6> 72 101 108 108 111 5  ok 6



On Sunday, January 18, 2026 at 4:37:59 PM UTC+1 euke...@gmail.com wrote:

p da

unread,
Jan 18, 2026, 12:17:20 PMJan 18
to 4th-co...@googlegroups.com
On Sun, Jan 18, 2026 at 5:23 PM The Beez <the.bee...@gmail.com> wrote:
Got it:

: try 0 [: key dup 32 = if drop exit then dup emit swap 1+ recurse ;] execute ;

nice trick using a quotation as a lambda to be able to initialize the counter in the same word, very clever

About previous message printing the stacked string without using recursion that's pretty good too but you have to traverse the string twice, once to stack it in order to reverse it and other to print in reversed order which is forward direction,   any way to avoid this double traversing?

thanks for your code! 

The Beez

unread,
Jan 18, 2026, 12:57:19 PMJan 18
to 4tH-compiler
Yes, possible - but it's not my style of programming:

: reverse begin dup roll emit 1- dup 0= until drop ;


char H char e char l char l char o 5 reverse cr

Thank you for making me lose my lunch :-(

The point is - you cannot grab that deep in the stack. You got to move those thing from position X to TOS. And since there is no way to randomly access the stack, you either reverse the order - or you use deep stack operators.
BTW, the second routine has ZERO advantages in 4tH, because ROLL is a library that transfers elements to the Return stack. Duh!

Hans Bezemer

p da

unread,
Jan 18, 2026, 2:26:32 PMJan 18
to 4th-co...@googlegroups.com
great solution! sorry for loosing your lunch, it was not my intention

I''m curious about this not being your style of programming, do you prefer something less optimized, preferring traversal twice rather than once?  if so, what is the value you prefer over performance?

greets

--
You received this message because you are subscribed to the Google Groups "4tH-compiler" group.
To unsubscribe from this group and stop receiving emails from it, send an email to 4th-compiler...@googlegroups.com.
To view this discussion visit https://groups.google.com/d/msgid/4th-compiler/a5b1437f-897f-4d2c-8736-21b0c7616536n%40googlegroups.com.

The Beez

unread,
Jan 18, 2026, 5:15:03 PMJan 18
to 4tH-compiler
Well, a stack is not an array - and using deep stack manipulators allow you to treat it like that. The effect is (almost) the same as with using local variables (which I think I addressed in one of my videos).
Forth requires you to think about your program - either explicitly (when implementing an foreign algorithm) or implicitly when you're designing an original program.

I've found that in the latter situation you tend to keep things (stack diagrams) simple and uncluttered becomes a second nature - even as your program grows. And you can because the hard stuff is (almost always) already hidden behind a solidly tested library.

Performance is almost always a secondary requirement. Nobody cares about 1s or 0.1s - or even 10s. I had a program that did a days work in 30s. Do you think someone would have been happier if that had been 3 seconds? Or even 300s?

Make sure you understand it. And can maintain it - not just fixing errors, but to add functionality - or change functionality. Be sure it is as free of bugs as possible. Those really count in the real world IMHO.
E.g. almost all extensions to uBasic/4tH - or even 4tH itself work first time. Catch my drift?

Hans Bezemer

p da

unread,
Jan 19, 2026, 10:52:35 AMJan 19
to 4th-co...@googlegroups.com
I see your point and I agree with you to some extent.

But dont you think that when programming in forth those things like performance, efficiency, minimal use of memory, etc are really important?  in fact, this is precisely the reason to use forth rather than any other high level and bloathed language. 

greets

On Sun, Jan 18, 2026 at 11:15 PM The Beez <the.bee...@gmail.com> wrote:
Well, a stack is not an array - and using deep stack manipulators allow you to treat it like that. The effect is (almost) the same as with using local variables (which I think I addressed in one of my videos).
Forth requires you to think about your program - either explicitly (when implementing an foreign algorithm) or implicitly when you're designing an original program.
...

The Beez

unread,
Jan 19, 2026, 1:27:07 PMJan 19
to 4tH-compiler
Of course, there are LOTS of metrics you can apply to a language - including how much fun - or (in case of Java, Rust and C++) how frustrating it is to use it.

- Minimal use of memory, that's where 4tH excels (and Forth doesn't). 4tH was designed with memory efficiency in mind. I know of few compilers that analyze the source code while tokenizing - and size their compilation environment accordingly. And also the runtime is sized with pinpoint accuracy. Forth just claims a blob of memory, and hopes it's sufficient;
- 4tH compiles incredibly fast, thousands and thousands of lines per second. I've never seen any benchmarks for Forth, but I think it's fast too;
- The 4tH compiler is very small, some 32 bit versions less than 50K, that is - runtime, compiler, C generator, bytecode generator, and decompiler. Important - one single file. Hardly bloatware.

So that should tell you what I find important. And note that 4tH still blows Ruby and Python out of the water, performance wise. But in this case, portability was more important to me than execution performance. I want to be able to use 4tH on every single platform I encounter.

At work, during lunch break, I would work on a FSQRT word on Windows, take the source home and continue to work on it seamlessly on my Linux machine. Parts of 4tH were developed on a Bull DPX/2 and Coherent. Can't tell you which parts, but who cares? It works. On Linux, on Windows, on bloody 1981 PC-DOS for that matter.

The point is, you can't have it all. Want safety? That'll cost you performance and size. Want portability? That'll cost you performance. Want features? That'll cost you size. So in general, you got two choices: either have a little of both - and wake up in mediocrity town, or sacrifice one for the other.

Portability is important to me. Versatility is important to me. Ease of use is important to me (defined as: as few steps as possible to achieve your goal - not intuitively).

And finally, note that 4tH is not Forth. All they share is a language - NOT an architecture. It's a miracle we got this close IMHO. Which makes your statement "in fact, this is precisely the reason to use forth rather than any other high level and bloathed language" ambiguous at best. Because - if I develop a program in 4tH and merely port it to gForth, have I actually been "using Forth"?

Hans Bezemer

p da

unread,
Jan 19, 2026, 3:43:15 PMJan 19
to 4th-co...@googlegroups.com
What I take into consideration when using "not very famous or used" languages is fun fundamentally and also easy of use defined more or less the way you define it (few steps to achieve the goal) but also simple concepts and interfaces to the language (small, coherent...)  There're several languages than bring me that while feeling comfortable (tcl, scheme, rebol-like, forth...)  each one with advantages and disadvantages.

I appreciate the work in 4th to make it powerful and also small and easy to understand, I like to program in it even when it hasn't got a REPL with is something I usually use and appreciate.

On Mon, Jan 19, 2026 at 7:27 PM The Beez <the.bee...@gmail.com> wrote:
Of course, there are LOTS of metrics you can apply to a language - including how much fun - or (in case of Java, Rust and C++) how frustrating it is to use it.

- Minimal use of memory, that's where 4tH excels (and Forth doesn't). 4tH was designed with memory efficiency in mind. I know of few compilers that analyze the source code while tokenizing - and size their compilation environment accordingly. And also the runtime is sized with pinpoint accuracy. Forth just claims a blob of memory, and hopes it's sufficient;
- 4tH compiles incredibly fast, thousands and thousands of lines per second. I've never seen any benchmarks for Forth, but I think it's fast too;
- The 4tH compiler is very small, some 32 bit versions less than 50K, that is - runtime, compiler, C generator, bytecode generator, and decompiler. Important - one single file. Hardly bloatware.

So that should tell you what I find important. And note that 4tH still blows Ruby and Python out of the water, performance wise. But in this case, portability was more important to me than execution performance. I want to be able to use 4tH on every single platform I encounter.

 It's interesting to know the care and knowledge put in building 4th .  Even when I know is a different beast and so you shouldn't compare it to Forth, I can't avoid to compare both at least in the syntax and expressiveness which I think is very similar.  

In terms of porting from each other I always wonder what concessions you decided to do in order to improve the language. For example, since Forth has a unified memory dictionary that you can arrange and play with but 4th has segmented memory I suppose there're typical Forth words that are implemented in a different way , for example @-like and ,   in a Forth you can play with dictionary contents and word contents in a way you cannot in 4th I suppose.   In some Forth interpreters I coded for fun assuming different memory arrangement I end up with more @-like words to deal with different memory areas and thus introducing incompatibility with Forth.
 


At work, during lunch break, I would work on a FSQRT word on Windows, take the source home and continue to work on it seamlessly on my Linux machine. Parts of 4tH were developed on a Bull DPX/2 and Coherent. Can't tell you which parts, but who cares? It works. On Linux, on Windows, on bloody 1981 PC-DOS for that matter.


That's a good point and a good and useful characteristic.  You can get the same in other languages like tcl for example but there're always corner cases or points where incompatibility arise.
 
The point is, you can't have it all. Want safety? [...] Portability is important to me. Versatility is important to me. Ease of use is important to me (defined as: as few steps as possible to achieve your goal - not intuitively).

I agree with you and also taking into account the plethora of languages you can find the right language for the job
 
And finally, note that 4tH is not Forth. All they share is a language - NOT an architecture. It's a miracle we got this close IMHO. Which makes your statement "in fact, this is precisely the reason to use forth rather than any other high level and bloathed language" ambiguous at best. Because - if I develop a program in 4tH and merely port it to gForth, have I actually been "using Forth"?

well, in my opinion a language is both the syntax and the architecture, and the closer to the baremetal the more it is. What I mean with my sentence is if you choose to use a language like forth or 4th or tcl or even scheme is because you put into value the easy of language meaning the way the language let you think about the problem, sometimes it is done by abstraction sometime it is done by simplification and minimalism. Also if the language is small and close to the hardware or the architecture it is better, for that reason I'm not a big fan of current Forth standard I tend to take a position close to the C.H. Moore's point of view about this., as far as I know.

In my humble opinion if you develop in 4th and port to gForth you are using 4th but since the mental model is quite similar probabbly I can argue you are using both because you are thinking in stacks, words, modularization, etc

greets

 

Hans Bezemer
On Monday, January 19, 2026 at 4:52:35 PM UTC+1 euke...@gmail.com wrote:
I see your point and I agree with you to some extent.

But dont you think that when programming in forth those things like performance, efficiency, minimal use of memory, etc are really important?  in fact, this is precisely the reason to use forth rather than any other high level and bloathed language. 

greets

On Sun, Jan 18, 2026 at 11:15 PM The Beez <the.bee...@gmail.com> wrote:
Well, a stack is not an array - and using deep stack manipulators allow you to treat it like that. The effect is (almost) the same as with using local variables (which I think I addressed in one of my videos).
Forth requires you to think about your program - either explicitly (when implementing an foreign algorithm) or implicitly when you're designing an original program.
...
Performance is almost always a secondary requirement. Nobody cares about 1s or 0.1s - or even 10s. I had a program that did a days work in 30s. Do you think someone would have been happier if that had been 3 seconds? Or even 300s?

Make sure you understand it. And can maintain it - not just fixing errors, but to add functionality - or change functionality. Be sure it is as free of bugs as possible. Those really count in the real world IMHO.
E.g. almost all extensions to uBasic/4tH - or even 4tH itself work first time. Catch my drift?

--
You received this message because you are subscribed to the Google Groups "4tH-compiler" group.
To unsubscribe from this group and stop receiving emails from it, send an email to 4th-compiler...@googlegroups.com.

The Beez

unread,
Jan 20, 2026, 8:47:04 AMJan 20
to 4tH-compiler
Well, this is turning out a very interesting discussion. Until I address the points you brought up, allow me to do some preliminaries in this regard.

The Forth community is not one of great ideas. It's very pragmatic. On one hand that's great, it produces very usable products, on the other hand it's disastrous, because it tends to become an incoherent collection of quirks, dongles and bad decisions. AKA Forth-83. With ANS Forth they had found out it's not too easy to reverse all that because there is code involved. Code, which you have to patch in order to make it work. There are plenty of "bad decisions" in Forth, linguistically speaking:

  1. DO..LOOP, we've been fighting that one for years now - and still, nothing has significantly improved. I find myself using BEGIN..REPEAT more and more, because I can either put the count on the Return stack or leave it TOS, it doesn't save me any space - and the count remains as accessible either way;
  2. VALUE, all data is treated like an address - EXCEPT VALUE. Now why is that and what does it add? Nothing. An initialization? That's how Forth-79 worked. They got rid of that - for what? As a matter of fact, it just adds behavior to an address (i.e. retrieving the value). In 4tH, such defining words are not allowed an additional DOES> definition, because implicitly, they already got one;
  3. No clear separation between single and double words. There is a cell, and there is a double cell. There are words that act on double words and words that act on single words. Some functionality is missing, like picturing words is only available as a double cell words. You have to convert to a double number to use there. Worse, for that reason they were added to the CORE wordset - instead of the DOUBLE wordset, making the mess even greater. Save with 2DROP, 2DUP, 2OVER and 2SWAP.
And that's just a small collection of the idiosyncrasies that populate Forth. And nobody has the courage to correct these fundamentally. Worse, in ANS Forth, they even added a few more (like the horrible FILE wordset). And instead of abstracting away the mess that Forth string support is, they declared counted strings "obsolete" without fixing anything. Leaving the entire discussion in limbo.

Talking of ANS Forth, contrary to C, it is not a language standard. It is an architecture standard. For the love of God, you cannot make an ANS compliant language. You simply can't. You have to use the classical Forth VM. That's why the 4tH manual states: "According to the ANS-Forth standard, section 5.2.2, this system is capable of compiling:" I quote, section 3.3 (all of chapter 3 are hard requirements for a Forth system): "Forth words are organized into a structure called the dictionary. While the form of this structure is not specified by the Standard, it can be described as consisting of three logical parts: a name space, a code space, and a data space. The logical separation of these parts does not require their physical separation."

Ever heard a C standard asking for a symbol table? With a specific layout? That's why there are C interpreters. C is a language standard, not an architecture standard. So, that was what I was working with - a badly designed architecture standard:
  1.  Now, I rarely dabbled with double cell words, so these had to go. That meant that picture words (<# #S #>) had to become single cell words. In order to "fake" that, S>D and D>S became dummies;
  2.  Some of the things I hated in Forth-83 had to go - like SIGN and negative +LOOP's. To this day they behave like Forth-79;
  3.  The 4tH Code Segment is one long parameter field. Now how do you handle subroutines? Simple. You jump over them like with AHEAD;
  4.  The words in the String Segment are a combination of opcode and operand - also for those opcodes without an operand. Yeah, it's overhead, but it's faster and simpler;
  5.  Where do you put constants? You inline them in the code with a NOOP runtime. Yeah, they need a special word to access, @C (unofficial CROSS wordset);
  6.  Where do you put string constants? And how much space do they take up? Well, we got the source in memory, if we move strings up front, we end up with a Segment holding all string constants. A short resize() and we're in business;
  7. Note most words are assigned to a specific datatype, like C@ and C! for characters, ! and @ to integers, so we can divy up the data space into dedicated segments. No alignment problems, addressing each data segment is dead easy, because 1 CELLS is always one and 1 CHARS is always one. And it solves a lot of problems with strict C data typing.
Now the next thing was to bring a bit of consistency to the thing. Having no data types doesn't mean there are no implicit types. That became more urgent when addr/count strings were introduced, making 2DROP, 2DUP, 2OVER and 2SWAP viable concepts. The idea of the compiler auto-expanding words like that made that possible, without introducing new bytecode opcodes. But - to this very day if I need to copy two successive, but unrelated stack items, I still use OVER OVER. I'm signaling to my future self "These have nothing to do with each other. They're not a double word or string". That's also why I'm reluctant to use a flag as anything but a string. Not a bit mask. Not an integer. It's a flag. And that's why a flag in 4tH is either "1" or "0". Thou shalt not be tempted to use a flag as anything but a flag.

As you might have notices, 4tH does not have a cascade of @-like words. One. For the Code Segment - although it's a bit more intelligent than you think, because string constant addresses are compiled with a different bytecode than the true integer constants. And it makes @C act differently. You see, @C will always return one value. I hate words that have different stack diagrams. That's why ?DUP is not supported out of the box. But if that value is a string address, that string will have been secretly copied to PAD, returning its address there.

C, works differently. It forces you to define an OFFSET. It creates a word with that name that - when invoked - takes an offset from the stack and return the contents of the string constant there. Talking of strings, CMOVE>, CMOVE and MOVE all do the same thing - it shifts bytes in the Character Segment in whatever direction you want, overlapping or not.

Files in 4tH are dead easy. Open it, if you treat it like a text file, it is a text file. Treat it like a binary file, and it is a binary file. You don't have to learn any special input or output words, just use the ones you use for the terminal. They'll do fine. Another thing - use the Windows version and it will write Windows text files. Use the Linux version and it will write Linux text files. You can read Windows files under Linux and it will do the same thing as it will do when reading Linux files under Windows. That's all handled for you in the background.

Use EXCEPT as you would in "Starting Forth" (on the terminal) and it will work as expected. Use it to fill a binary file buffer - and it will work as expected. Try to blow it up, and (most likely :-) it will just return an error message message, instead of bombing inelegantly. That's a lengthy story of how I tried to make Forth friendlier and more consistent.

However, IMHO it still helps if you know what's happening under the hood. I never think in terms of immediacy, vocabularies, dictionaries, CFA, PFA, or LFA. I think in segments and operands. Not in dictionary entries, but symbols. It's the translations of abstractions that make it actually work. AKA everything still has its place - but it's a different place. So I can wholeheartedly agree with your: "in a Forth you can play with dictionary contents and word contents in a way you cannot in 4tH". E.g. in 4tH you have no idea of the name of a word at runtime. That was in the symbol table - and that baby is gone.

I agree Chuck is a very wise and clever man - and I wish he would communicate in much greater and more fundamental detail than he already does. Because - I don't think the choice of stack manipulation words over a more symbolic implementation was an accident. I think it was a very deliberate choice. Why? Because he didn't implement anything like that in colorForth, although it was more than obvious how to do that, after that avalanche of papers on the subject. Also an interesting detail - often he expresses "performance" in terms of time to deliver ;-)

One last thing about the REPL and then I'm gone. I don't feel like the REPL was such a good thing. In 1985, if you FAFO'd it was a blank screen and the copyright message. So, I never found that one that useful. Nowadays, if I work witrh gForth, I either need to set a MARKER, so I can clear things later, or exit and reenter (if I want a blank slate). With 4tH I usually invoke it from either Kate or gEdit. That one has a terminal window. So, I run it and when it bombs, I use the window above to edit it. Click the window below and do a quick <Arrow up><Enter> - and it executes again. In gForth it's hardly any different from 4tH - not counting the additional BYE.

Hans Bezemer
Reply all
Reply to author
Forward
0 new messages