Handling `break` and `continue` in trailing macro body
Just wanted to bring this issue I've had up for discussion.
When using trailing macro bodies to implement iteration, break and continue don't apply to the macro body and can't be used the same as in a regular for-loop.
Example:
macro @foreach(int[] list; @body(int)) {
foreach (v : list) {
@body(v);
}
}
fn void test() {
@foreach(int[] {1,2,3}; int v) {
break; // Error: There is no valid target for 'break', did you make a mistake?
};
}
Is there some way we can "handle" break and continue in a better way to make iteration macros more useful?
I understand there's problems with merely including break in the @body. Sometimes you have nested loops in the macro and break should break the outer loop. Or, there may not even be a for loop in the macro.
The problem here is that whether or not a "break" (or "continue") exists is entirely opaque to the user of the function. At one point in time I was thinking of a way to kind of attach it somehow to the body as an attribute to allow break to work. However, that seemed too much work to make it work.
There are two workarounds:
- Create an iterator that encapsulates the macro behaviour. This is how the iterator on HashMap works.
- Use {| |} for breaks:
fn void test()
{
{|
@foreach(int[] {1,2,3}; int v)
{
return;
};
|}
}
This may be useful, but I don't know if it's widespread, because not all @body macros are loops.
I have this @body iterator in c3fmt, which iterates from arbitrary index to the end of array:
macro CodeFmt.@cache_statement_iter(&self; @body(idx, token)) @private
{
assert(self.state_stack.len() > 0);
usz start_len = self.state_stack[^1].token_cache_start;
for (usz i = start_len; i < self.__token_cache.len(); i++) {
@body(i, &self.__token_cache[i]);
}
}
And this is my wtf continue statement :)
self.@cache_statement_iter(; usz i, Token* t) {
if (self._print_lex) io::eprintf("%s ", t.type);
tmpstate.check(t); // this is used only for current scope_depth
// WTF: c3 doesn't support continue in macro @body!
if (i >= skip_i_until) {
// looooong block here
}
}
The typical way to do this in lambdas is to provide an in-parameter to set in order to break. I would like people instead write external iterators that handle this in a normal loop. Can we close this?
One idea could be this:
macro @foo(String[] x; bool @body(String s))
{
foreach (s : x)
{
if (!@body(s)) break;
}
}
// Used like:
...
@foo(some_arr; String s)
{
if (s == "abc") break;
};