On 10-09-2024 13:26, Paul Rubin wrote:
> Hans Bezemer <
the.beez.speaks@gmail.com> writes:
>> What bothers me most technologically is that parameters flow through
>> the stack undisturbed. You break that paradigm when using locals. With
>> locals you *HAVE TO* create some kind of stack frame that you have to
>> destroy when you exit.
>
> Forth programs very frequently end up juggling parameters and other data
> to and from the return stack, instead of using locals. Simple
> implementations of locals put them in the return stack too.
> "Destroying" the stack frame just means adjusting RP when the function
> exits. Usually a single instruction.
>
>> Needless to say this copying, releasing and stuff takes time.
>
> Similar to DUP (copy) or DROP (release).
>
>> In all honesty I must state that this overhead is not always
>> translated to a diminished performance
>
> Right, I don't think one can assert a performance hit without
> measurements supporting the idea.
>
>> TL;DR my objections are mostly based on pure architectural arguments,
>> rather than practicality.
>
> Sure, that's reasonable, it's a matter of what you prefer. That's
> harder to take issue with than claims about performance.
>
>> I also don't like Python, PHP and Perl for those very same reasons -
>
> Those are at a totally different level than Forth, in terms of layers of
> implementation and runtime libraries, overhead, etc. It's better to
> compare to something like C, or a hypothetical cleaned up version of C,
> or even to Forth with locals ;).
A lot depends on how solid you want to make your implementation. I got locals in uBasic/4tH.
: exec_local ( --)
[: get_exp 0 max 27 frame dup @ - + min negate cells frame + dup local <
if E.MANYLOC throw else frame @ over ! to frame then ;]
exec_function \ execution semantics for LOCALS()
;
This one reserves room for locals. You may use up to 26 locals per function since there are 26 letters in the alphabet (duh!).
: exec_param ( --)
frame exec_local frame \ allocate locals, save pointers
begin over over > while cell+ (pop) over ! repeat drop drop
;
If the reserved room has to be initialized by the stack, it calls EXEC_LOCAL and then copies the values there.
: exec_return ( --)
get_token paren? putback if ['] get_push exec_function then
gpop prog ! frame dup local #local 1- cells + >
if E.NOSCOPE throw ;then @ to frame
;
This one looks whether RETURN returns a value - and if it does, it pushes this value on the stack. Then it sets the return address. It checks for the sanity of the stack frame and if okay THEN it finally updates the stack pointer.
You comfortable left out the initialization of the stack frame. Agreed, if ALL values are transferred to the return stack the overhead is minimal. But how often happens that?
> Those are at a totally different level than Forth, in terms of layers of
> implementation and runtime libraries, overhead, etc. It's better to
> compare to something like C, or a hypothetical cleaned up version of C,
> or even to Forth with locals ;).
True - but that's not the level of abstraction I'm considering. I think a language should have a well designed core, surrounded by a constellation of extensions. Like C with its standard library and Forth with its word sets. For comparison - C got a few dozen keywords. PHP got at least two different ways to extend binary extensions alone. A full Python installation is scattered all over the filesystem, so you got a hell of a job to extract a single, transferable application. Not to mention the awkward syntax (although they fixed some of it in v3). In Perl you always have to wonder which prefix is fashionable today.
Now, I won't say Forth doesn't have its issues. I think IN ESSENCE recognizers are a beautiful idea. Extend it to strings and you could eradicate "parsing words" and have something like:
"lib/mylib.4th" include
"Square" : "the square is:" print dup * cr ;
But okay, we'll do with what we have ;-) And BTW, TURNKEY should be standard. Clean up the dictionary, pump out an executable.
Hans Bezemer