[parenscript-devel] Patch: added JS label/break to PS

Red Daly reddaly at gmail.com
Wed Aug 18 12:12:38 UTC 2010


I added RETURN-FROM and BLOCK without too much effort using the
implicit return functionality and try/catch. In my view this is the
most reasonable way to implement this in the general case, since
BLOCK/RETURN-FROM require non-local exit much in the same way that
lisp's TRY/CATCH do.

The alternative to this approach is to exit from each function in the
call stack via a Javascript `return' statement.  Unfortunately, the
call stack can contain many functions code over which the Parenscript
compiler exerts little control, requiring throw as the control
transfer mechanism.  Thus, in the general case of unknown code on the
call stack, there is no means to exit without a throw.  I do not view
throwing as an ugly solution at all, since try/catch was designed for
non-local exits of all sorts.

Nonetheless, using try/catch to implement Parenscript features
deserves some attention.  Programs will need to ensure that they do
not use try/catch in a way that interferes with the Parenscript
convention.  Generally, try/catch blocks should only catch specific
exceptions and re-throw PS's exceptions.   I'm happy to also implement
a safe TRY/CATCH wrapper that re-throws Parenscript errors and catches
everything else, too.  However, we may want to make an official
interface change to try/catch if any lisp-style non-local exit code
becomes part of the language.

I present an example of why try/catch is unavoidable inline below:

On Fri, Apr 9, 2010 at 3:42 PM, Vladimir Sedach <vsedach at gmail.com> wrote:
> Makes sense to me. I'll add this to my todo list (which I'll publish
> in an email as soon as I'm done my current work on the PS compiler).
>
> Vladimir
>
> 2010/4/9 Daniel Gackle <danielgackle at gmail.com>:
>> I just pushed a patch (authored by Scott) to implement JS's LABEL and BREAK
>> in PS. (Note that this patch deprecates LABELED-FOR since you can get the
>> same effect by combining LABEL and FOR. Was anybody using LABELED-FOR?)
>> Here's an example:
>> (label scope
>>     (foo)
>>     (when (bar)
>>       (break scope))
>>     (blee))
>> =>
>> scope: {
>>     foo();
>>     if (bar()) {
>>         break scope;
>>     };
>>     blee();
>> };
>> I was astonished to discover recently that JS has supported this ability all
>> along in the form of labeled statements and labeled breaks. I'd always
>> assumed that to get explicit returns from an arbitrary scope, you'd have to
>> resort to the ugly hack of muscling TRY/CATCH to do it, thinking that this
>> was the closest JS counterpart.
>> (See http://news.ycombinator.com/item?id=793092 for a thread in which
>> several people believe this.) But it appears we were all wrong.
>> What's not clear yet is how far this can be taken. Can you use it inside a
>> nested local function to return immediately from the top-level function?
>> That is one thing I've wanted for a long time.
>> In the ideal case, LABEL/BREAK could be used as a base for implementing a
>> proper BLOCK and RETURN-FROM in PS, something which we'd long believed to be
>> impossible. One challenge is that in CL, RETURN-FROM can take a value, which
>> becomes the value of BLOCK. In other words, BLOCK in CL is an expression
>> while LABEL in JS is not. It seems, though, that most of this challenge has
>> already been conquered with the development of implicit return in PS. The
>> only thing you'd need to add is detecting when BLOCK is being used as an
>> expression, declaring a gensymed variable and assigning whatever is
>> happening inside BLOCK to that variable (much like implicit return already
>> does with e.g. CASE), then put the variable in the expression position that
>> BLOCK was in. It seems like this ought to work. It would also make things
>> like this possible in PS:
>> (1+ (case foo
>>         (:eleven 11)
>>         (:twelve 12)))
>> Vladimir (and everybody), is the above clear? What do you think of it?

As stated above, I think try/catch is the way to go.  There is no
other way to exit a stack of functions in the general case otherwise.

For example, I can write a Javascript function that calls its argument
infinity times and never returns.

function mapForever(fn) {
while(true) fn();
}

Now consider some parenscript:

(block non-local
  (map-forever
    (lambda ()
      (return-from non-local "we got out!"))))

To extricate itself from map-forever, there is no alternative but JS's
throw statement.

Even if we had the ability to alter every function in the system, it
would be necessary to inspect nearly every function call's return
values to properly unwind the stack to the appropriate BLOCK.

Having said all that, there are cases when try/catch is not necessary
for BLOCK/RETURN-FROM, as you have described.  BLOCK should emit code
according to the contexts in which RETURN-FROM appears.  If there is a
RETURN-FROM inside the same function, BLOCK can use a label for a
local exit.  If RETURN-FROM appears inside a lambda, try/catch is
necessary (except in cases where you want to optimize this away by
inspecting how that lambda gets passed around).  If there are no
return-froms, just emit a PROGN.

My solution does not do the local optimization, but it does refrain
from putting try/catches around code with no return-froms.



Red

>> Daniel
>> _______________________________________________
>> parenscript-devel mailing list
>> parenscript-devel at common-lisp.net
>> http://common-lisp.net/cgi-bin/mailman/listinfo/parenscript-devel
>>
>>
>
> _______________________________________________
> parenscript-devel mailing list
> parenscript-devel at common-lisp.net
> http://common-lisp.net/cgi-bin/mailman/listinfo/parenscript-devel
>




More information about the parenscript-devel mailing list