`break` used as value for detecting broken loops, simpler global ref resolution
break expression
Semantics:
breakused as a value (not a statement) turns into a magic variable which is set totrue/falseaccording to previous (or not yet assigned/undefinedif used before any loops occured).- The magic variable has
varsemantics (hoisted to function block), which is important because you need to use it outside the loop. We could imagineletsemantics in the block containing the loop, but that seems confusing as everything is implicit...
Possible Extension
The examples make me wonder whether you'd like to break with value (or break value) to override the true value, so that you could actually get and use that value via return break or value := break. This in particular is a neat way to get the iterated item out of the loop even if it's declared const:
for item of list
if match item
break with item
found := break
---
var broke
{ broke = false; for (const item of list) {
if (match(item)) {
{ broke = item; break }
}
} }
const found = broke
The question at this point is, should broke be initialized to false in the loop (as in this PR currently), or should it be initialized to undefined to leave room for this feature?
Simpler global ref resolution
Previously, populateRefs was far more complicated and made some assumptions about adjacent refs with the same name being able to use the same name. But this messed up when the ref was used both inside and outside a loop and had a conflict only outside the loop.
Eventually we should do something smarter, perhaps similar to
https://github.com/jashkenas/coffeescript/pull/5398, to only add numbers as necessary according to scoping (but it's trickier here because of refs declared both var and let at varying block levels).
One thing to note is that this will prevent a possible alternative break expression analogous to throw in expression position.
while true
x = if b then break else 1
Good point. CoffeeScript doesn't allow those, because it's hard to do (can't do it with iife), but that doesn't mean it's not a good idea to support (with exceptions or symbols, as discussed in #202).
I wonder if it would be better to use a different identifier like broke, which becomes magic if it isn't assigned by the user in that function scope... Thoughts?
I think we can still use break as a special condition inside if/unless immediately following an iteration expression. I'm wary of allowing break as an expression to have indefinite scope.
While I liked if break I'm now less happy with it if if cond then break means something completely different. Also the if break alone would prevent the break with value extension.
What about some meta-like incantation like break.did or break.ed or break.value or break() for the value form?
Maybe break? for the value
Oh, break? is a neat idea; can't mean anything else. It does read a little like a Boolean, which is fine for this PR but less good for the break with value extension. Though maybe not terrible, it might lead to weird things like break?? to test whether it's non-null. 😅
Another idea that I had was to use labels:
:foo for item of list
...
if foo.break ...
This feels better "contained" (it's clear which loop you're talking about). But it conflicts if there's a variable foo in scope, and it's inconvenient to always label loops. Maybe for.break could work for unlabeled for loops, but then there's the ambiguity issue (multiple for loops would share a variable).
That said, I guess for.break is another possible spelling for break?. I think I like break? best so far.
break.value could be used for the value passed from break with.
I'm in favor of the idea to allow us to create labels so break & continue can reference them, just like vanilla JS, Java, C, etc.