Adding CL:TAGBODY for SERIES support

Andrew Easton Andrew at Easton24.com
Sat Jan 28 13:16:35 UTC 2023


Hi Jason,

Thank you very much for raising the dynamic scope issue.
It seems like it is fixable, see below.  I will digest the example and see whether I can implement with a fix.

The blog post has been amended to document this bug. Search for “TODO BUG #1”.

;; TODO BUG #1 2023-02-28

 (defun foo (x) (funcall x))
 (tagbody
  (foo (lambda () (go x)))
  (format t "This does not happen~%")
  x)





(parenscript::ps*
  '(tagbody
  (foo (lambda () (go x)))
  (format t "This does not happen~%")
  x))

#|
Output:
"(function () {
    foo(function () {
        __PS_MV_REG = [];
        return go(x);
    });
    format(true, 'This does not happen~%');
    var switchVar243 = 1;
    while (true) {
        switch (switchVar243) {
        case 1:
            'tagbody-go-tag: X';
        };
        __PS_MV_REG = [];
        return;
    };
})();"


This output demonstrates a bug:
"return go(x);" is not what is supposed to happen.


So, what is supposed to happen?
The switch-var needs to be set.
Then the conceptual inner block within the loop
needs to returned from.
The loop will re-enter the switch-case and jump to the
last branch which should fall through beyond the end.
Falling through is implemented by breaking out of
the conceptual outer block surrounding the loop.
Note, that parenscript sometimes compiles this
outer block to a "function() {...}" with "try ... catch".
This seems useful and reasonable.  It is
pointed out to help with understanding the compiler's output.


Start by analyzing what parenscript
does when a (lambda ...) returns from a (block ...).
Perhaps it is sufficient to wrap (lambda ...) bodies
like so: `(macrolet ((go (...) ...)) , at lambda-body).
|#


(parenscript:ps*
  '(block foo (funcall (lambda () (return-from foo 3)))))

#|
The following output looks promising:

"(function () {
    try {
        __PS_MV_REG = [];
        return funcall(function () {
            __PS_MV_REG = [];
            throw { '__ps_block_tag' : 'foo', '__ps_value' : 3 };
        });
    } catch (_ps_err1) {
        if (_ps_err1 && 'foo' === _ps_err1['__ps_block_tag']) {
            return _ps_err1['__ps_value'];
        } else {
            throw _ps_err1;
        };
    };
})();"
|#




Cheers,
Andrew




> On Jan 27, 2023, at 14:54, Jason Miller <jason at milr.com> wrote:
> 
> Neat!  I'll take a closer look later.
> 
> Does it fully support the strange combination of lexical scope and dynamic
> extent that CL:TAGBODY does?
> 
> For example:
> 
>    (defun foo (x) (funcall x))
>    (tagbody
>     (foo (lambda () (go x)))
>     (format t "This does not happen~%")
>     x)
> 
> -Jason
> 
> On Fri, 27 Jan 2023 09:00:58 -0700 Andrew Easton <Andrew at Easton24.com> wrote:
>> Hello everyone, 
>> 
>> How’s it going?
>> 
>> There is now a working version of (parenscript:defpsmacro tagbody (&body body) …)),
>> See my blog post here: "https://dapperdrake.neocities.org/faster-loops-javascript <https://dapperdrake.neocities.org/faster-loops-javascript>” .
>> 
>> This adds nestable (tagbody … (tagbody …) …)) forms as a user-land library to parenscript.
>> 
>> Apologies for missing documentation.  It will be added gradually.  Also, hosting a tarball on
>> Neocities.org is planned in future as well as somehow getting the library into quicklisp. A name
>> like parenscript-tagbody-go seems useful.
>> 
>> This code is still missing the check of *DEFINED-OPERATORS*.  How is that supposed to look?
>> 
>> 
>> 
>> Cheers,
>> Andrew 
>> 
>> 
>> 
>> 
>> 
>> 
>>> On May 29, 2022, at 20:24, Andrew Easton <andrew at easton24.de> wrote:
>>> 
>>> Hi Philipp,
>>> 
>>> That sounds like a good plan.
>>> 
>>> From my current vantage point, this seems like step
>>> three.  I just got done with at step one.  Step two
>>> is for me to get acquainted with the parenscript
>>> codebase.
>>> 
>>> I will get a feel the existing code base and then we
>>> can take the next steps from there.
>>> 
>>> I already cloned the git repository to my local
>>> development machine.  The current commit for branch
>>> master seems to be:
>>> 
>>> commit 1fd720bc4e2bc5ed92064391b730b9d4db35462a (HEAD -> master)
>>> | Author: Vladimir Sedach <vas at oneofus.la>
>>> | Date:   Wed Jun 17 20:29:19 2020 -0700
>>> 
>>> 
>>> Regarding tail-call optimization in JavaScript:
>>> Jason Miller also recommended that.  Unfortunately, I
>>> dug up information indicating that it is unsupported
>>> in Google's V8 JavaScript implementation, see
>>> [stackoverflow.com (2017)].
>>> 
>>> Quoting part of my reply to Jason for the benefit of
>>> future readers of this specific email:
>>> 
>>>> [...] This seems to necessitate a (loop (case ...))
>>>> based approach, because SERIES may be used for loops
>>>> with iteration counts greater than the stack size.
>>>> Nevertheless, not all is lost.
>>>> 
>>>> PARENSCRIPT already compiles
>>>> (block nil ((lambda () (return 3)))) as catch/throw
>>>> correctly.  Note, the call in the body of the BLOCK.
>>>> So at least some dynamic ((lambda () (go ...))) calls
>>>> should be compilable; hopefully all of them.  Even if
>>>> it only captures 70% of all use cases, that is way
>>>> more than zero.
>>> 
>>> 
>>> 
>>> Cheers,
>>> Andrew
>>> 
>>> 
>>> 
>>> [stackoverflow.com (2017)], Answer by T.J. Crowder:
>>> TITLE:
>>> ES6 Tail Recursion Optimisation Stack Overflow,
>>> URL:
>>> https://stackoverflow.com/questions/42788139/es6-tail-recursion-optimisation-stack-overflow
>>> 
>>> 
>>> 
>>> On Sat, May 21, 2022 at 10:58:57AM +0200, Philipp Marek wrote:
>>>> Hi Andrew,
>>>> 
>>>> first of all -- how about registering on gitlab.common-lisp.net,
>>>> so that you can become a developer for [1] and work with a branch
>>>> using a Merge Request?
>>>> It would be much easier to track your progress (and individual changes)
>>>> that way.
>>>> 
>>>>> I have started to implement TAGBODY for PARENSCRIPT
>>>>> [A,B,C].  The general idea is to imitate a jump table
>>>>> by looping over a switch-case.  A GO (C-terminology:
>>>>> jump) then sets the switch-variable to the next jump
>>>>> destination.  The loop subsequently causes the switch
>>>>> to branch to the jump target in the switch-variable.
>>>>> Leaving the tagbody means leaving the loop.
>>>> 
>>>> Hmmm, okay.
>>>> My first thought would've been to use a function for each
>>>> part and just do tail recursion... but it seems that
>>>> this isn't really supported in Javascript?!
>>>> 
>>>> 
>>>> 
>>>>> There are complications.  Common Lisp allows nested
>>>>> tagbody-forms.  Common Lisp allows go-tags to be
>>>>> referenced within the lexical scope *and* the dynamic
>>>>> extent of a tagbody form.  This means that a LAMBDA
>>>>> can close over a go-tag and jump there, see an
>>>>> example in [B], of how inconvenient this can become
>>>>> for compilation to JavaScript.
>>>> 
>>>> Yeah... that would be a good reason for simple function
>>>> calls and tail recursion.
>>>> 
>>>> 
>>>>> 1. I need a code review of the algorithm.
>>>>>  The implementation in [B] seems to be
>>>>>  satisfactory.  There are some test cases and
>>>>>  examples.  Most there is the most hairy example I
>>>>>  could find up to now.  I may have missed crucial
>>>>>  details.
>>>> 
>>>> I'll take a look - but please let's try to get it into
>>>> the git repo first, so that any discussions have some
>>>> common state to refer to.
>>>> 
>>>> 
>>>>> 2. My understanding of the CL:TAGBODY definition in
>>>>>  the CLHS [4] may be wrong.  Which alternate
>>>>>  interpretations does anybody here know of?
>>>> 
>>>> What are your questions, or points of confusion?
>>>> 
>>>> 
>>>> 
>>>> Ad 1: https://gitlab.common-lisp.net/parenscript/parenscript
>>> 
>> 




More information about the parenscript-devel mailing list