Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

break used as value for detecting broken loops, simpler global ref resolution #359

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

edemaine
Copy link
Collaborator

break expression

Semantics:

  • break used as a value (not a statement) turns into a magic variable which is set to true/false according to previous (or not yet assigned/undefined if used before any loops occured).
  • The magic variable has var semantics (hoisted to function block), which is important because you need to use it outside the loop. We could imagine let semantics 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
jashkenas/coffeescript#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).

Necessary for expressionization that creates functions
Fixes `var broke` colliding with otherwise declared `broke` variable
@edemaine edemaine temporarily deployed to build February 11, 2023 23:01 — with GitHub Actions Inactive
@STRd6
Copy link
Contributor

STRd6 commented Feb 11, 2023

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

@edemaine
Copy link
Collaborator Author

edemaine commented Feb 11, 2023

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?

@STRd6
Copy link
Contributor

STRd6 commented Feb 11, 2023

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.

@edemaine
Copy link
Collaborator Author

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?

@STRd6
Copy link
Contributor

STRd6 commented Feb 11, 2023

Maybe break? for the value

@edemaine
Copy link
Collaborator Author

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.

@STRd6
Copy link
Contributor

STRd6 commented Feb 12, 2023

break.value could be used for the value passed from break with.

@GoToLoop
Copy link

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.

@edemaine edemaine marked this pull request as draft February 12, 2023 16:09
@edemaine edemaine mentioned this pull request May 15, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants