In the current version of the algorithm, having an <initial> element with a transition to a <history> pseudo-state leads to unexpected results. The default history state is entered, even if the state has already been visited and there is a non-default history value.

In the case of transitions selected from parallel regions, the current algorithm completely specifies a) which transition is to be selected in case of conflicts (see isPreempted in B Algorithm for SCXML Interpretation ) and b) the order in which those transitions should be taken (see microstep in B Algorithm for SCXML Interpretation ). However, we could avoid completely specifying one or both of these choices. This would give implementations greater flexibility, but would impair the portability of applications, which would not necessarily behave the same way on different platforms. We solicit feedback on these alternatives.

Would such a change of naming conventions be an improvement? Could it serve to attract more new users to SCXML? Or would it break to much with "tradition" and thus repell some current users? We solicit feedback on this matter.

"go to s on e if x is bigger than one, assign two to /a/b and send e2 with a payload of five to t after 2 seconds" It seems there might be a way to name elements and attributes that 1) avoids abbreviations, 2) yet uses less space, 3) is easier to write, 4) is easier to read (since it is closer to English) and 5) suggests an natural order of attributes within elements, based on how the element reads when it is translated into English.

Note that the latter notation (but not so much the former) suggests a very natural translation into English, e.g. as follows:

We are considering redesigning this aspect of SCXML. For example, what is now written as follows:

It could be argued that the current choice of names of SCXML elements and attributes is not optimal. Often rather long words are used (e.g. "transition", "target" and "location"), making lines longer than necessary, and contributing to the size of SCXML documents. Also, abbreviations are used in some cases (e.g. "expr" and "cond") but not in others, which may give the impression of a language not well thought out. Moreover, conventions for how to order the attributes within elements are lacking, which may make SCXML documents harder than necessary to read and understand.

We are considering adding an iterative construct, such as 'foreach' or 'while', to the executable content in the Core module. Such a construct is not strictly necessary, since iterators can be modeled by conditionalized targetless transitions. For example, to model a 'while' loop with condition C and body B, create an eventless transition with condition C and executable content B. It will keep firing and executing B without leaving its containing state as long as C is true. However, an explicit iterator might permit more succinct state machines. We sollicit comments on the usefulness of such a construct.

B Algorithm for SCXML Interpretation

This section presents a normative algorithm for the interpretation of an SCXML document. Implementations are free to implement SCXML interpreters in any way they choose, but they must behave as if they were using the algorithm defined here.

The fact that SCXML implements a variant of the Statechart formalism does not as such determine a semantics for SCXML. Many different Statechart variants have been proposed, each with its own semantics. This section presents an informal semantics of SCXML documents, as well as a normative algorithm for the interpretation of SCXML documents.

Informal Semantics

The following definitions and highlevel principles and constraint are intended to provide a background to the normative algorithm, and to serve as a guide for the proper understanding of it.

Preliminary definitions

state An element of type <state>, <parallel>, <final> or <scxml>. pseudo state An element of type <initial> or <history>. transition target A state, or an element of type <history>. atomic state A state of type <state> with no child states, or a state of type <final>. compound state A state of type <state> with at least one child state, or a state of type <scxml>. start state A dummy state equipped with a transition which when triggered by the Run event leads to the initial state(s). Added by the interpreter with an id guaranteed to be unique within the statemachine. The only role of the start state is to simplify the algorithm. configuration The maximal consistent set of states (including parallel and final states) that the machine is currently in. We note that if a state s is in the configuration c, it is always the case that the parent of s (if any) is also in c. Note, however, that <scxml> is not a(n explicit) member of the configuration. source state The source state of a transition is the atomic state from which the transition is leaving. target state A target state of a transition is a state that the transition is entering. Note that a transition can have zero or more target states. targetless transition A transition having zero target states. eventless transition A transition lacking the 'event' attribute. external event An SCXML event appearing in the external event queue. See section ? for details. internal event An event appearing in the internal event queue. It's exact representation is implementation dependent, but a simple string would work. macrostep An external event causes an SCXML state machine to take exactly one macrostep. A macrostep consists of a sequence (a chain) of microsteps. However, if the external event does not enable any transitions, no microstep will be taken, and the corresponding macrostep will be empty. microstep If an external event enables one or (in the case of parallel states) more transitions, a microstep is taken, involving the processing of each of the enabled transitions. This microstep may change the the current configuration, update the datamodel and/or generate new (internal and/or external) events. This, by causality, may in turn enable additional transitions which will be handled in the next microstep in the sequence, and so on.

Principles and Constraints

We state here some principles and constraints, on the level of semantics, that SCXML adheres to:

Encapsulation An SCXML processor is a pure event processor. The only way to get data into an SCXML statemachine is to send external events to it. The only way to get data out is to receive events from it. Causality There shall be a causal justification of why events are (or are not) returned back to the environment, which can be traced back to the events provided by the system environment. Determinism An SCXML statemachine which does not invoke any external event processor must always react with the same behavior (i.e. the same sequence of output events) to a given sequence of input events (unless, of course, the statemachine is explicitly programmed to exhibit an non-deterministic behavior). In particular, the availability of the <parallel> element must not introduce any non-determinism of the kind often associated with concurrency. Note that observable determinism does not necessarily hold for state machines that invoke other event processors. Completeness An SCXML interpreter must always treat an SCXML document as completely specifying the behavior of a statemachine. In particular, SCXML is designed to use priorities (based on document order) to resolve situations which other statemachine frameworks would allow to remain under-specified (and thus non-deterministic, although in a different sense from the above). Run to completion SCXML adheres to a run to completion semantics in the sense that an external event can only be processed when the processing of the previous external event has completed, i.e. when all microsteps (involving all triggered transitions) have been completely taken. Termination A microstep always terminates. A macrostep may not. A macrostep that does not terminate may be said to consist of an infinitely long sequence of microsteps. This is currently allowed.

Algorithm

This section presents a normative algorithm for the interpretation of SCXML documents. Implementations are free to implement SCXML interpreters in any way they choose, but they must behave as if they were using the algorithm defined here. Note that the algorithm assumes a Lisp-like semantics in which the empty Set null is equivalent to boolean 'false' and all other entities are equivalent to 'true'.

Datatypes

These are the abstract datatypes that are used in the algorithm.

datatype List function head() // Returns the head of the list function tail() // Returns the tail of the list function append(l) // Returns the list appended with l function filter(f) // Returns the list of elements that satify the predicate f function some(f) // Returns true if some element in the list satifies the predicate f function every(f) // Returns true if every element in the list satifies the predicate f datatype Set procedure add(e) // Adds e to the set procedure delete(e) // Deletes e from the set function member(e) // Is e a member of set? function isEmpty() // Is the set empty? function toList() // Converts the set to a list function diff(set2) //Returns all members of Set that are not in set2 datatype Queue procedure enqueue(e) // Puts e last in the queue function dequeue() // Removes and returns first element in queue function isEmpty() // Is the queue empty? datatype BlockingQueue procedure enqueue(e) // Puts e last in the queue function dequeue() // Removes and returns first element in queue, blocks if queue is empty

Global variables

The following variables are global from the point of view of the algorithm. Their values will be set in the procedure interpret() .

global datamodel; global configuration; global internalQueue; global externalQueue; global historyValue; global continue

Procedures and Functions

This section defines the procedures and functions that make up the core of the SCXML interpreter.

procedure interpret(scxml,id)

The purpose of this procedure is to initialize the interpreter. It is called with a parsed representation of an SCXML document.

In order to interpret an SCXML document, first perform inplace expansions of states by including SCXML source referenced by urls (see 3.13 Referencing External Files) and change initial attributes to initial container children with empty transitions to the state from the attribute. Then (optionally) validate the resulting SCXML, and throw an exception if validation fails. Create an empty configuration complete with a new populated instance of the data model and a execute the global scripts. Create the two queues to handle events and set the global continue variable to true. Call executeTransitionContent on the initial transition that is a child of scxml. Then call enterState on the initial transition. Finally, start the interpreter's event loop.

procedure interpret(doc): expandScxmlSource(doc) if (!valid(doc)) {fail with error} configuration = new Set() previousConfiguration = new Set() datamodel = new Datamodel(doc) executeGlobalScriptElements(doc) internalQueue = new Queue() externalQueue = new BlockingQueue() continue = true executeTransitionContent([doc.initial.transition]) enterState([doc.initial.transition]) startEventLoop()

procedure startEventLoop()

Upon entering the state machine, we take all internally enabled transitions, namely those that don't require an event and those that are triggered by internal events. (Internal events can only be generated by the state machine itself.) When all such transitions have been taken, we move to the main event loop, which is driven by external events.

procedure procedure startEventLoop(): previousConfiguration = null; initialStepComplete = false; until(initialStepComplete): enabledTransitions = selectTransitions(null) if (enabledTransitions.isEmpty()): internalEvent = internalQueue.dequeue()// this call returns immediately if no event is available if (internalEvent): datamodel.assignValue("event", internalEvent) enabledTransitions = selectTransitions(internalEvent) if (enabledTransitions): microstep(enabledTransitions.toList() else: initialStepComplete = true mainEventLoop()

procedure mainEventLoop()

This loop runs until we enter a top-level final state or an external entity cancels processing. In either case 'continue' will be set to false (see EnterStates, below, for termination by entering a top-level final state.)

Each iteration through the loop consists of three main steps: 1) execute any <invoke> tags for atomic states that we entered on the last iteration through the loop 2) Wait for an external event and then execute any transitions that it triggers 3) Take any subsequent internally enabled transitions, namely those that don't require an event or that are triggered by an internal event.

This event loop thus enforces run-to-completion semantics, in which the system process an external event and then takes all the 'follow-up' transitions that the processing has enabled before looking for another external event. For example, suppose that the external event queue contains events e1 and e2 and the machine is in state s1. If processing e1 takes the machine to s2 and generates internal event e3, and s2 contains a transition t triggered by e3, the system is guaranteed to take t, no matter what transitions s2 or other states have that would be triggered by e2. Note that this is true even though e2 was already in the external event queue when e3 was generated. In effect, the algorithm treats the processing of e3 as finishing up the processing of e1.

procedure procedure mainEventLoop(): while(continue): for state in configuration.diff(previousConfiguration): if (isAtomic(state)): if state.invoke: state.invokeid = executeInvoke(state.invoke) datamodel.assignValue(state.invoke.attribute('idlocation'),state.invokeid) previousConfiguration = configuration externalEvent = externalQueue.dequeue() // this call blocks until an event is available datamodel.assignValue("event",externalEvent) enabledTransitions = selectTransitions(externalEvent) if (enabledTransitions): microstep(enabledTransitions.toList()) // now take any newly enabled null transitions and any transitions triggered by internal events macroStepComplete = false; until(macroStepComplete): enabledTransitions = selectEventlessTransitions() if (enabledTransitions.isEmpty()): internalEvent = internalQueue.dequeue()// this call returns immediately if no event is available if (internalEvent): datamodel.assignValue("event", internalEvent) enabledTransitions = selectTransitions(internalEvent) if (enabledTransitions): microstep(enabledTransitions.toList() else: macroStepComplete = true // if we get here, we have reached a top-level final state or some external entity has set continue to false exitInterpreter()

procedure exitInterpreter()

The purpose of this procedure is to exit the current SCXML process by exiting all active states. If the machine is in a top-level final state, a Done event is generated.

procedure exitInterpreter(): inFinalState = false statesToExit = new Set(configuration) for s in statesToExit.toList().sort(exitOrder) for content in s.onexit: executeContent(content) for inv in s.invoke: cancelInvoke(inv) if (isFinalState(s) && isScxmlState(s.parent())): inFinalState = true configuration.delete(s) if (inFinalState): sendDoneEvent(???)

function selectEventlessTransitions()

This function selects all transitions that are enabled in the current configuration that do not require an event trigger. First test if the state has been preempted by a transition that has already been selected and that will cause the state to be exited when the transition is taken. If the state has not been preempted, find a transition with no 'event' attribute whose condition evaluates to true . If multiple matching transitions are present, take the first in document order. If none are present, search in the state's ancestors in ancestory order until one is found. As soon as such a transition is found, add it to enabledTransitions , and proceed to the next atomic state in the configuration. If no such transition is found in the state or its ancestors, proceed to the next state in the configuration. When all atomic states have been visited and transitions selected, return the set of enabled transitions.

function selectEventlessTransitions(event): enabledTransitions = new Set() atomicStates = configuration.toList().filter(isAtomicState) for state in atomicStates: if !(isPreempted(s, enabledTransitions)): loop: for s in [state].append(getProperAncestors(state, null )): for t in s.transition: if ( t.attribute('event') == null && conditionMatch(t)) enabledTransitions.add(t) break loop return enabledTransitions

function selectTransitions(event)

The purpose of the selectTransitions() procedure is to collect the transitions that are enabled by this event in the current configuration.

Create an empty set of enabledTransitions . For each atomic state in the configuration, first check if the event is the result of an <invoke> in this state. If so, apply any <finalize> code in the state. Next test if the state has been preempted by a transition that has already been selected and that will cause the state to be exited when the transition is taken. If the state has not been preempted, find a transition whose 'event' attribute matches event and whose condition evaluates to true . If multiple matching transitions are present, take the first in document order. If none are present, search in the state's ancestors in ancestory order until one is found. As soon as such a transition is found, add it to enabledTransitions , and proceed to the next atomic state in the configuration. If no such transition is found in the state or its ancestors, proceed to the next state in the configuration. When all atomic states have been visited and transitions selected, return the set of enabled transitions.

function selectTransitions(event): enabledTransitions = new Set() atomicStates = configuration.toList().filter(isAtomicState) for state in atomicStates: if (event.attribute('invokeid') != null && state.invokeid = event.invokeid): //event is the result of an <invoke> in this state applyFinalize(state, event) if !(isPreempted(s, enabledTransitions)): loop: for s in [state].append(getProperAncestors(state, null )): for t in s.transition: if (t.attribute('event')!= null && isPrefix(t.attribute('event'), event.name) && conditionMatch(t)): enabledTransitions.add(t) break loop return enabledTransitions

function isPreempted(s transitionList)

Return true if a transition T in transitionList exits an ancestor of state s. In this case, taking T will pull the state machine out of s and we say that it preempts the selection of a transition from s. Such preemption will occur only if s is a descendant of a parallel region and T exits that region. If we did not do this preemption check, we could end up in an illegal configuration, namely one in which there were multiple active states that were not all descendants of a common parallel ancestor.

function isPreempted(s transitionList): preempted = false for t in transitionList: if (t.attribute('target') != null): LCA = findLCA([t.parent()].append(getTargetStates(t))) if (isDescendant(s,LCA)): preempted = true break return preempted

procedure microstep(enabledTransitions)

The purpose of the microstep procedure is to process the set of transitions enabled by an external event, an internal event, or by the presence or absence of certain values in the datamodel at the current point in time. The processing of the enabled transitions must be done in parallel ('lock step') in the sense that their source states must first be exited, then their actions must be executed, and finally their target states entered.

procedure microstep(enabledTransitions): exitStates(enabledTransitions) executeTransitionContent(enabledTransitions) enterStates(enabledTransitions)

procedure exitStates(enabledTransitions)

Create an empty statesToExit set. For each transition t in enabledTransitions , if t is targetless then do nothing, else let LCA be the least common ancestor state of the source state and target states of t . Add to the statesToExit set all states in the configuration that are descendants of LCA . Convert the statesToExit set to a list and sort it in exitOrder .

For each state s in the list, if s has a deep history state h , set the history value of h to be the list of all atomic descendants of s that are members in the current configuration, else set its value to be the list of all immediate children of s that are members of the current configuration. Again for each state s in the list, first execute any onexit handlers, then cancel any ongoing invocations, and finally remove s from the current configuration.

procedure exitStates(enabledTransitions): statesToExit = new Set() for t in enabledTransitions: if (t.attribute('target') != null ): LCA = findLCA([t.parent()].append(getTargetStates(t))) for s in configuration.toList(): if (isDescendant(s,LCA)): statesToExit.add(s) statesToExit = statesToExit.toList().sort(exitOrder) for s in statesToExit: for h in s.history: f = (h.attribute('type') == "deep") ? lambda(s0): isAtomicState(s0) && isDescendant(s0,s) : lambda(s0): s0.parent() == s historyValue[h.attribute('id')] = configuration.toList().filter(f) for s in statesToExit: for content in s.onexit: executeContent(content) for inv in s.invoke: cancelInvoke(inv) configuration.delete(s)

procedure executeTransitionContent(enabledTransitions)

For each transition in the list of enabledTransitions , execute its executable content.

procedure executeTransitions(enabledTransitions): for t in enabledTransitions: executeContent(t)

procedure enterStates(enabledTransitions)

Create an empty statesToEnter set, and an empty statesForDefaultEntry set. For each transition t in enabledTransitions , if t is targetless then do nothing, else let LCA be the least common ancestor state of the source state and target states of t . For each target state s , if s is a history state then add either the history values associated with s or s 's default target to statesToEnter . Else (if s is not a history state), add s to statesToEnter . Convert statesToEnter to a list and sort it in enterorder .

For each state s in the list, first add s to the current configuration, then invoke any external processes specified in <invoke> elements and assign their sessionid s to the datamodel, keyed on the <invoke> element's id, then execute any onentry handlers, and finally, if s is a final state, generate relevant Done events. If we have reached a top-level final state, exit the interpreter.

procedure enterStates(enabledTransitions): statesToEnter = new Set() statesForDefaultEntry = new Set() for t in enabledTransitions: if (t.attribute('target') != null ): LCA = findLCA([t.parent()].append(getTargetStates(t))) for s in getTargetStates(t): if (isHistoryState(s)): if (historyValue[s.attribute('id')] != null ): for s0 in historyValue[s.attribute('id')]: addStatesToEnter(s0,LCA,statesToEnter,statesForDefaultEntry) else : for s0 in getTargetStates(s.transition): addStatesToEnter(s0,LCA,statesToEnter,statesForDefaultEntry) else : addStatesToEnter(s,LCA,statesToEnter,statesForDefaultEntry) statesToEnter = statesToEnter.toList().sort(enterOrder) for s in statesToEnter: configuration.add(s) for content in s.onentry: executeContent(content) if (statesForDefaultEntry.member(s)): executeContent(s.initial.transition.children()) if (isFinalState(s)): parent = s.parent() grandparent = parent.parent() internalQueue.enqueue(parent.attribute('id') + ".Done") if (isParallelState(grandparent)): if (getChildStates(grandparent).every(isInFinalState)): internalQueue.enqueue(grandparent.attribute('id') + ".Done") for s in configuration.toList(): if (isFinalState(s) && isScxmlState(s.parent())): continue = false;

procedure addStatesToEnter(s,root,statesToEnter,statesForDefaultEntry)

The purpose of this procedure is to add to statesToEnter all states that must be entered as a result of entering state s . These include ancestors of s , parallel siblings of s or its ancestors, and s 's default children, if s is a parallel or compound state. Note that this procedure permanently modifies both statesToEnter and statesForDefaultEntry .

First, add s to statesToEnter . Then, if s is a parallel state, add each of s 's children to statesToEnter . Else, if s is a compound state, add s to statesForDefaultEntry and add its default initial state to statesToEnter . Finally, for each ancestor anc of s , add anc to statesToEnter and if anc is a parallel state, add the children of anc that does not have a descendant on statesToEnter to statesToEnter .

procedure addStatesToEnter(s,root,statesToEnter,statesForDefaultEntry): statesToEnter.add(s) if (isParallelState(s)): for child in getChildStates(s): addStatesToEnter(child,s,statesToEnter,statesForDefaultEntry) elif (isCompoundState(s)): statesForDefaultEntry.add(s) addStatesToEnter(getDefaultInitialState(s),s,statesToEnter,statesForDefaultEntry) for anc in getProperAncestors(s,root): statesToEnter.add(anc) if (isParallelState(anc)): for pChild in getChildStates(anc): if (!statesToEnter.toList().some(lambda(s): isDescendant(s,anc))): addStatesToEnter(pChild,anc,statesToEnter,statesForDefaultEntry)

procedure isInFinalState(s)

Return true if state s is a compound <state> and one of its children is a <final> state and is a member of the configuration, or if s is a <parallel> state and isInFinalState is true of all its children.

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

function findLCA(stateList)

Return the state s such that s is a proper ancestor of all states on stateList and no descendant of s has this property. Note that there is guaranteed to be such a state since the <scxml> element (which we treat as a state here) is a common ancestor of all states. Note also that since we are speaking of proper ancestor (parent or parent of a parent, etc.) the LCA is never a member of stateList .

function findLCA (stateList): for anc in getProperAncestors(stateList.head(),null): if (stateList.tail().every(lambda(s): isDescendant(s,anc))): return anc