masak / alma

ALgoloid with MAcros -- a language with Algol-family syntax where macros take center stage
Artistic License 2.0
139 stars 15 forks source link

Make 'return' (and later 'next'/'last'/'redo') bind to the mainline, not to the quasi/macro #483

Open masak opened 5 years ago

masak commented 5 years ago

Suggestion via conversation with @vendethiel++, who finally convinced me this is a good idea.

Here's some code to discuss around:

macro r42() {
    quasi {
        return 42;
    };
}

func foo() {
    r42();
    return -1;
}

According to my previously held view, the return 42 would get injected by the mainline, but then fail at runtime because at that point the surrounding routine that bound that return (the macro r42) has already exited.

This issue suggests changing the semantics so that return 42 actually returns from foo. This is done in practice by return binding later, at macro expansion time. No prob, since a quasi does not contain 007 code.

By the way, once we have #325, this should apply to those control-flow keywords as well.

(@vendethiel describes the above behavior as return being dynamic, but I don't think that's quite it. It's still a lexical binding, it's just that it's done a bit later, at macro expansion time. This is in analogy with #216 and macro calls, by the way. The whole thing can be justified, or at least hand-waved, because return is a statement form. In other words, this is a counterargument against making return a listop, as #300 suggests. (Because then return would just be a normal identifier, and there'd be no good reason for it to bind so late.))

vendethiel commented 5 years ago

@vendethiel describes the above behavior as return being dynamic, but I don't think that's quite it. It's still a lexical binding, it's just that it's done a bit later, at macro expansion time.

I did mean "dynamic at compile-time" (please don't make this dynamic at runtime!), I think Let Over Lambda's term of "sublexical" is applicable here.

masak commented 5 years ago

Same as in https://github.com/masak/007/issues/159#issuecomment-233127155 ?

vendethiel commented 5 years ago

Yes -- you have better memory than I do.

masak commented 5 years ago

Probably not a big deal (since various dataflow steps will have to happen after macro expansion anyway), but implementing this issue means that you can't know things statically about unexpanded source with macros in it, since any given macro might contain control-flow primitives.

vendethiel commented 5 years ago

since any given macro might contain control-flow primitives.

It was already (meant to be) possible before, with exceptions.

masak commented 5 years ago

Fair point, although (correct me if I'm wrong), if you're not doing any CATCH on the mainline end, your code will either run to completion (because no exception) or die and be caught somewhere further up the stack. Whereas here, mysteryMacro() might mean that the code breaks out of your loop, or your case statement, or returns from your function.

Again, no big deal. It just means that until we've expanded fully, we don't necessarily have the full control-flow graph.