Phrogz / LXSC

Lua XML StateChart interpreter - parses and executes SCXML state machines with a Lua data model.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Nested final states in parallel won't raise done event

sradomski opened this issue · comments

When every child of a parallel is in a final basic configuration, the interpreter ought to raise a done.state event for the parallel state. In LXSC, this is only the case if the final states are direct children of the parallel state.

test-not-ok.txt

Thanks for the test! I'll see what I call do.

If your test case had the <final> as a direct child of direct children of the <parallel>, I can justify it with the standard. As you say, the following testcase passes today:

<?xml version="1.0"?>
<scxml xmlns="http://www.w3.org/2005/07/scxml" seed="639855585" name="finalParallel" version="1.0">
  <parallel id="p0">
    <onentry><send event="timeout" delay="1s"/></onentry>
    <state id="p0_s0"><final id="p0_s0_f0"/></state>
    <state id="p0_s1"><final id="p0_s1_f0"/></state>
    <transition target="pass" event="done.state.p0"/>
    <transition target="fail" event="timeout"/>
  </parallel>
  <final id="pass" />
  <final id="fail" />
</scxml>

Section 3.7 of the spec says:

When the state machine enters the <final> child of a <state> element […] generate the event done.state.id [...] where id is the id of the parent state. Immediately thereafter, if the parent <state> is a child of a <parallel> element, and all […] other children are also in final states […] generate the event done.state.id where id is the id of the <parallel> element.

Note the highlighted words "parent"/"child" above (not "ancestor"/"descendant").

I'm looking for normative prose that says ~ "When done.state.id is fired on a <state>—as a result of the machine entering a <final> state—if the parent of the <state> is itself a <state>, that parent state shall itself have done.state.id fired upon it." (Except that specific prose I wrote is fraught with danger, as it could be interpreted to mean that entering a <final> state in one part of a state machine and manually an event named done.state.foo on an unrelated <state> should trigger the cascade of events.) Do you know where such a spec might exist? Is there a mistake in the spec that does not explicitly cover the 'done'-ness propogating upwards?

Currently on your test file done.state.id2 and done.state.id5 are fired by the LXSC interpreter. Do you think that done.state.id1 and done.state.id4 be fired, before done.state.p0 is fired?

Further discussion: in the (informative, not normative) Appendix D of the spec we have the following pseudo-code at the end of enterStates():

if isFinalState(s):
    if isSCXMLElement(s.parent):
        running = false
    else:
        parent = s.parent
        grandparent = parent.parent
        internalQueue.enqueue(new Event("done.state." + parent.id, s.donedata))
        if isParallelState(grandparent):
            if getChildStates(grandparent).every(isInFinalState):
                internalQueue.enqueue(new Event("done.state." + grandparent.id))

The above does not propagate the done.state. event up the ancestry tree below a <parallel>. Further, we have this pseudo-code for the isInFinalState() determination:

function isInFinalState(s):
    if isCompoundState(s):
        return getChildStates(s).some(lambda s: isFinalState(s) and configuration.isMember(s))
    elif isParallelState(s):
        return getChildStates(s).every(isInFinalState)
    else:
        return false

Again, the above only iterates the children of a compound state and looks to see if they are final states, without recursively calling isInFinalState() on each child.

I'm guessing that this all is an oversight, but without spec or errata to back up the change, I'm hesitant to change the interpreter to behave as I/we think it ought to behave, when that seems to conflict with the specification.

Yes, not having an interpretable Appendix D has been a major source of criticism. But as far as I can tell, "done.state." + grandparent.id is raised if isInFinalState holds for all of its children. In the case of compounds as in the test given, that is true if its active child state is a final state. That is strangely specific and I would challenge the implied semantic if it was not already a recommendation.

But granted, the spec does say something different. However, under a surprise me the least aspect, you'd expect done.state.[parallel@id] to be raised when all its active child states are final. At least that is what our users expected and what we implemented.

I'm going to close this for now due to the reasonable comments by Jim in this thread:
http://lists.w3.org/Archives/Public/www-voice/2017AprJun/thread.html

If you can show me that most other interpreters behave as yours does, I'll likely move that way for consistency. But for now, I'll stick to the de jure spec in lieu of a de facto one.