Open masak opened 8 years ago
It'll need to be implemented using something like a "what are all the allowable infixes" hook, which adds all the assignment infixes in a dynamic-enough way that (say) defining a new ordinary infix
op
immediately makes the correspondingop=
form available.
The cool thing is that is parsed
gives us this for free. This is where "is parsed
regexes are scoped to the (current language's) parser" pays off — no hook needed; we just parse and the infix is there.
macro postfix:<op=>(term, infixop, expr) is parsed(/ <infix> "=" <EXPR> /) {
return quasi {
{{{term}}} = {{{term}}} {{{infixop @ Q::Infix}}} {{{expr}}}
}
}
Also, remember that the parsing of
op=
is such that the entire rest of the expression has invisible parentheses.
This also falls out of is parsed
; the <EXPR>
inside of it gobbles up everything, guaranteeing that there won't be an operator when it's done.
If we go ahead and make assignment a statement form, I guess this one becomes a statement form too.
Even here, lvalues get involved in a big way. Any side effect happening in the path of the lhs should happen once, which relates both to the Single Evaluation Rule and to the Assignment Protocol.
Also, remember that the parsing of
op=
is such that the entire rest of the expression has invisible parentheses.
I think this is wrong, but without observable consequences, so to speak.
infix:<=>
is of loosest precedence. op=
would be an infix op of the same precence. So, in effect, the rest of the expression, being tighter or of equal precedence, would parse as if it had invisible parentheses.
New attempt at implementation:
macro infix:<op=>(lhs, rhs, infixop) is equiv(infix:<=>) is parsed(/ <infix> "=" /) {
quasi {
my L = lvalue({{{lhs}}});
L.write(L.read() {{{Q.Infix @ infixop}}} {{{rhs}}});
};
}
Heh. The new implementation is left-recursive. Not a showstopper in itself, of course, but... interesting.
Easiest would be if the parser is able to take left-recursive things in stride. I gather a recursive-descent parser finds that bit challenging. Here I was gonna say "A Thompson engine would do fine with this, I think", but these aren't regular expressions we're talking about, since we have <infix>
calling <infix>
. That's some kind of pushdown thing.
There's also the question of how we allow +=
but disallow +==
. There's nothing wrong in principle with metaoperators acting twice; it's just in this case it doesn't make sense. This is where Perl 6 says that it can't make assignment out of +=
because +=
is too diffy.
- There's also the question of how we allow
+=
but disallow+==
.
It's possible is parsed(/ <infix> <!after "="> "=" /)
would be enough here. (Assuming the above-mentioned left recursion is handled.)
That brings up another point. The "chaining operators" in Perl 6 (corresponding to all the comparison ops in 007) are "too diffy" to be assignmentopified.
Perl 6 has a precedence level called "structural infix" which 007 doesn't really have. (But might when it gets for example ..
for ranges.) That one is diffy too.
Perhaps it would make sense to attach some metadata to precedence levels? That's a very unfinished thought, so I'll just leave that as it is.
The new implementation is left-recursive. Not a showstopper in itself, of course, but... interesting.
Just noting in passing that this could be handled by adopting the infix:postfix:<=>
idea from #430.
There's also the question of how we allow
+=
but disallow+==
. There's nothing wrong in principle with metaoperators acting twice; it's just in this case it doesn't make sense. This is where Perl 6 says that it can't make assignment out of+=
because+=
is too diffy.
I think we should adopt the diffy
categorization wholesale. It works. Precedence levels aren't first-class, but we can put a @Diffy
annotation on individual operators, as usual. I'm fine with only allowing @Diffy
annotations on the first defined operator of a precedence level.
The new implementation is left-recursive. Not a showstopper in itself, of course, but... interesting.
Just noting in passing that this could be handled by adopting the
infix:postfix:<=>
idea from #430.
I came here to make that same observation again, not knowing whether I'd made it before.
It is possible that infixes are "simple" enough (just an array of literal strings, right?) that a parsed infix:<op=>
rule might just power through them all an extend the declarative prefix all the way to the =
... but it feels like a dangerous game to play, and #430 feels like much better bet.
Another thing. This blog post at 2ality explains how the new JavaScript logical-assignment operators (&&=
, ||=
, and ??=
) do not follow the usual pattern of A op= B
meaning A = A op B
, but instead mean A op (A = B)
.
The reason for this (as explained in that post) is that the original logical operators are all short-circuiting, and the assignment versions should be too.
Interestingly, Raku does not go this route — a synopsis has a phrasing that amounts to "macroish operators lose their short-circuitingness as they are turned into assignment operators". That may be consistent, but it's not strangely consistent, if you see what I mean. I much prefer JavaScript's strategy here, even though it clearly means having two separate rules.
- There's also the question of how we allow
+=
but disallow+==
. There's nothing wrong in principle with metaoperators acting twice; it's just in this case it doesn't make sense. This is where Perl 6 says that it can't make assignment out of+=
because+=
is too diffy.
Actually, it's because it's too fiddly. (Duh!)
But this actually does solve the whole thing, like providing a base case against an infinite recursion; each thus created metaoperator only acts on operators which are not fiddly, but the created metaoperator itself is too fiddly and therefore not subject to further meta-ing.
I'm thinking we first do this one behind a feature flag, then (when we have modules) as a pragma that flips on the feature flag, and finally as a pure-007 parser-changing module.
It'll need to be implemented using something like a "what are all the allowable infixes" hook, which adds all the assignment infixes in a dynamic-enough way that (say) defining a new ordinary infix
op
immediately makes the correspondingop=
form available.Also, remember that the parsing of
op=
is such that the entire rest of the expression has invisible parentheses. So, for example