Open walkie opened 3 years ago
Agreed the parser doesn't trace through the execution graph to determine if all paths are terminated and cheats by insisting on a ^ or :> as an event handler terminator. However, to be clear, one can add the terminators for each branch, but the grammar does insist on a redundant event handler terminator, which isn't elegant:
#TerminatorOutOfBranch
-interface-
Foo
-machine-
$S1
|Foo|
true ?
-> $S2 ^
:
-> $S3 ^
::
^
$S2
$S3
-actions-
-domain-
##
I am very open to improving this as I know a lot of (or all?) compilers will complain. Any pointers to how this kind of tracking is done would be welcome as well as an open invitation to implement. I'll create an RFC doc and we can add technical proposals, background there and get an agreed upon solution defined.
Ah, good to know it's supported and I was just doing it wrong!
I think this should be a relatively easy constraint to remove. You don't need a sophisticated control-flow analysis, just a terminated
property attached to each node n
in the AST (you can also define this externally as a function, either approach is equivalent). The value of n.terminated
is defined recursively as follows:
true
if n
is a terminatortrue
if n
is a statement block whose final statement is terminatedtrue
if n
is a conditional/match statement and every branch is terminatedfalse
otherwiseThen the rule changes to: the statement block that is the body of a handler must be terminated.
Additionally, you can perform dead code analysis with this property by checking the following rule: if n
is a statement block, it is an error if any statement other than the final statement is terminated.
I think this covers all of the cases, but it's possible I'm missing something!
I included a test for terminators inside of branches in my latest PR, and it seems to work!
Cool - for awareness I'm actively working on the parser to make this work correctly, by which I mean not requiring a terminating return if the last branching statement has all paths terminated.
Sounds good! I assume the continue terminator will also be allowed at the end of each branch?
Relatedly, it would be good to come up with a way to test in the test suite that a given Frame file doesn't compile. I'll look into a way to do that.
Commit 9ebe8f4..3ca27d0 supports:
No event handler terminator if all branches for a boolean conditional terminate:
$S1
|Foo|
t ?
-> $S2 ^
:
y() ^
::
|x| ^
Termination of a branch ending in a transition/change_state statement:
$S1
|Foo|
t ?
-> $S2
:
y() ^
:: ^
|x| ^
Looks good! In the second case, do you need the final terminator at the end of Foo
, or does the transition imply a return in the then-branch?
as long as all branches are terminated by the next statement then the parser considers it valid. however you raise a really interesting question as to whether a transition/statechange should be self terminating.
For logical consistency I presume that self terminating transitions would also support this syntax:
$S1
|Foo| -> $S2
|x| ^
$S2
@walkie I think this is easy enough to do, however I do think these options need to be supported in the code generators:
$S1
|A| -> $S2
|B| -> $S2 ^
|C| -> $S2 ^(ret)
$S2
Thoughts? I'm assuming that the code generators need to detect a self termination with no explicit return and generate a return but not generate if an explicit return exists.
So I'm thinking how to implement things with self terminating statements (transitions the only example so far).
Here is a few cases I've been mulling over:
#Terminators2
-machine-
$TransitionTermination
|A| -> $S2 :> --- this should be invalid
|B| -> $S3 ^
|C| -> $S4 ^(0)
|D|
t ? -> $S2 :>
u ? -> $S3 :>
v ? -> $S4 ^(1) :>
w ? a() :>
x ? b() ^ :>
: f()
:: ^(42)
|E|
t ? -> $S2
: ^ ::
|F|
t ? -> $S2 ^
: -> $S3 ::
##
It seems to me the cases to deal with are:
There is more nuance but those are key I think.
Your list of cases looks reasonable to me.
I would vote for still allowing a return at the end of an if-then-else, even if it's not needed. This will keep all the existing code compiling and is consistent with your second example, where the return is not required, but may be optionally provided.
It sounds like you plan to still include a return node in the AST after a transition (i.e. generating the return node even if a return statement is not included). In that case, I don't think this should require any additional work on the backends. However, I think the Rust backend should already work whether or not the return node is included.
still allowing a return at the end of an if-then-else, even if it's not needed. Agreed.
It sounds like you plan to still include a return node in the AST after a transition I hadn't considered that but that is probably the simplest way to do it. Good idea.
So I've been playing with the "implicit return" aspect of transitions and am thinking now that it is a complication that doesn't really give a lot of value other than being slightly more elegant. It is at the cost of significant complexity in the parser as well as being complex to the programmer to understand what is going on in deeply nested conditionals.
Any objections, strong or otherwise, to simply requiring the very next token be a return?
So
t ? -> $S2 ^ ::
would be good while
t ? -> $S2 :: ^
would be an error.
That would be fine, although I actually think I would prefer just completely rolling back the changes made because of this ticket and marking it as "won't fix".
Requiring a single terminator at the end of every handler (as Frame currently does) is less syntactic overhead than requiring a terminator after every transition. And the semantics of returning after a transition is relatively easy to implement in the backend, as I've done for the Rust backend already.
In my initial issue report, I thought that return was not allowed inside of branches at all, which would have been a major limitation. Once you corrected me that they are supported, but you still need the final terminator, I was able to fix the semantics and write lots of tests checking various interactions of transition/change-state/continue/return. Things seems to work well, and I think this is a pretty good solution.
What do you think about just rolling the changes back, going back to always requiring a final terminator, and delegating the fact that transitions/change-states imply a return to the backends?
Also, if you're on-board with this idea, I can go ahead and roll back the changes on the v0.6.0
branch and merge in my other fixes. Just let me know!
Ok sounds good. Roller back!
The commit history ended up being a little too messy to automatically revert the changes, but I was successfully able to manually revert them (i.e. diffing with an old version and deleting/restoring code). This ended up being slightly better anyway since I was able to retain some of your other code/comment improvements you made along the way.
I'm gonna let you merge the PR since it's obviously well outside of the scope of the Rust backend!
I'll rebase and merge my other changes onto v0.6.0
once you do.
Tis in!
On Fri, Sep 10, 2021 at 10:38 PM Eric Walkingshaw @.***> wrote:
The commit history ended up being a little too messy to automatically revert the changes, but I was successfully able to manually revert them (e.g. diffing with an old version and deleting/restoring code). This ended up being slightly better anyway since I was able to retain some of your other code/comment improvements you made along the way.
I'm gonna let you merge the PR since it's obviously well outside of the scope of the Rust backend!
I'll rebase and merge my other changes onto v0.6.0 once you do.
— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/frame-lang/frame_transpiler/issues/17#issuecomment-917345644, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABOQ2RUWTHTT2EM37MOVNE3UBLTMNANCNFSM5C32O2BA . Triage notifications on the go with GitHub Mobile for iOS https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675 or Android https://play.google.com/store/apps/details?id=com.github.android&referrer=utm_campaign%3Dnotification-email%26utm_medium%3Demail%26utm_source%3Dgithub.
--
Mark Truluck - Inventor
e | @.*** w | frame-lang.org
https://www.patreon.com/framelang [image: Frame Language] https://www.patreon.com/framelang
Currently, terminators are not allowed inside of branch statements. Since branches are statement-level, it seems like this should be permitted.
For example, this Frame spec currently returns a (confusing) Frame parse error:
Instead one must move the terminators out of the branch to write this:
Enabling within-branch terminators would give handlers finer grained control over whether to propagate a message on to a parent handler in a hierarchical state machine. For example, one could write the following handler, which is currently a parse error: