@scion-scxml/scxml
Version:
An implementation of SCXML in JavaScript.
1,296 lines (1,099 loc) • 2.89 MB
JavaScript
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.scion = g.scion || {}; g.scion.scxml = f(); g.scion.core = g.scion.scxml.core;}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
(function (setImmediate){
'use strict';
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
var base = require('@scion-scxml/core-base'),
helpers = base.helpers,
query = base.query,
transitionComparator = base.helpers.transitionComparator;
/**
* @description Implements semantics described in Algorithm D of the SCXML specification.
* See {@link scion.BaseInterpreter} for information on the constructor arguments.
* @class SCInterpreter
* @extends BaseInterpreter
*/
var Statechart = function (_base$BaseInterpreter) {
_inherits(Statechart, _base$BaseInterpreter);
function Statechart(modelOrModelFactory, opts) {
_classCallCheck(this, Statechart);
opts = opts || {};
opts.legacySemantics = false;
return _possibleConstructorReturn(this, (Statechart.__proto__ || Object.getPrototypeOf(Statechart)).call(this, modelOrModelFactory, opts));
}
/** @private */
_createClass(Statechart, [{
key: '_selectTransitions',
value: function _selectTransitions(currentEvent, selectEventlessTransitions) {
var transitionSelector = this.opts.transitionSelector;
var enabledTransitions = new this.opts.Set();
var e = this._evaluateAction.bind(this, currentEvent);
var atomicStates = this._configuration.iter().sort(transitionComparator);
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;
try {
for (var _iterator = atomicStates[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
var state = _step.value;
var _iteratorNormalCompletion2 = true;
var _didIteratorError2 = false;
var _iteratorError2 = undefined;
try {
loop: for (var _iterator2 = [state].concat(query.getAncestors(state))[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
var s = _step2.value;
var _iteratorNormalCompletion3 = true;
var _didIteratorError3 = false;
var _iteratorError3 = undefined;
try {
for (var _iterator3 = s.transitions[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) {
var t = _step3.value;
if (transitionSelector(t, currentEvent, e, selectEventlessTransitions)) {
enabledTransitions.add(t);
break loop;
}
}
} catch (err) {
_didIteratorError3 = true;
_iteratorError3 = err;
} finally {
try {
if (!_iteratorNormalCompletion3 && _iterator3.return) {
_iterator3.return();
}
} finally {
if (_didIteratorError3) {
throw _iteratorError3;
}
}
}
}
} catch (err) {
_didIteratorError2 = true;
_iteratorError2 = err;
} finally {
try {
if (!_iteratorNormalCompletion2 && _iterator2.return) {
_iterator2.return();
}
} finally {
if (_didIteratorError2) {
throw _iteratorError2;
}
}
}
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return) {
_iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
var priorityEnabledTransitions = this._removeConflictingTransition(enabledTransitions);
this._log("priorityEnabledTransitions", priorityEnabledTransitions);
return priorityEnabledTransitions;
}
/** @private */
}, {
key: '_removeConflictingTransition',
value: function _removeConflictingTransition(enabledTransitions) {
var _this2 = this;
var filteredTransitions = new this.opts.Set();
//toList sorts the transitions in the order of the states that selected them
var _iteratorNormalCompletion4 = true;
var _didIteratorError4 = false;
var _iteratorError4 = undefined;
try {
for (var _iterator4 = enabledTransitions.iter()[Symbol.iterator](), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) {
var t1 = _step4.value;
var t1Preempted = false;
var transitionsToRemove = new Set();
var _iteratorNormalCompletion5 = true;
var _didIteratorError5 = false;
var _iteratorError5 = undefined;
try {
var _loop = function _loop() {
var t2 = _step5.value;
//TODO: can we compute this statically? for example, by checking if the transition scopes are arena orthogonal?
var t1ExitSet = _this2._computeExitSet([t1]);
var t2ExitSet = _this2._computeExitSet([t2]);
var hasIntersection = [].concat(_toConsumableArray(t1ExitSet)).some(function (s) {
return t2ExitSet.has(s);
}) || [].concat(_toConsumableArray(t2ExitSet)).some(function (s) {
return t1ExitSet.has(s);
});
_this2._log('t1ExitSet', t1.source.id, [].concat(_toConsumableArray(t1ExitSet)).map(function (s) {
return s.id;
}));
_this2._log('t2ExitSet', t2.source.id, [].concat(_toConsumableArray(t2ExitSet)).map(function (s) {
return s.id;
}));
_this2._log('hasIntersection', hasIntersection);
if (hasIntersection) {
if (t2.source.descendants.indexOf(t1.source) > -1) {
//is this the same as being ancestrally related?
transitionsToRemove.add(t2);
} else {
t1Preempted = true;
return 'break';
}
}
};
for (var _iterator5 = filteredTransitions.iter()[Symbol.iterator](), _step5; !(_iteratorNormalCompletion5 = (_step5 = _iterator5.next()).done); _iteratorNormalCompletion5 = true) {
var _ret = _loop();
if (_ret === 'break') break;
}
} catch (err) {
_didIteratorError5 = true;
_iteratorError5 = err;
} finally {
try {
if (!_iteratorNormalCompletion5 && _iterator5.return) {
_iterator5.return();
}
} finally {
if (_didIteratorError5) {
throw _iteratorError5;
}
}
}
if (!t1Preempted) {
var _iteratorNormalCompletion6 = true;
var _didIteratorError6 = false;
var _iteratorError6 = undefined;
try {
for (var _iterator6 = transitionsToRemove[Symbol.iterator](), _step6; !(_iteratorNormalCompletion6 = (_step6 = _iterator6.next()).done); _iteratorNormalCompletion6 = true) {
var t3 = _step6.value;
filteredTransitions.remove(t3);
}
} catch (err) {
_didIteratorError6 = true;
_iteratorError6 = err;
} finally {
try {
if (!_iteratorNormalCompletion6 && _iterator6.return) {
_iterator6.return();
}
} finally {
if (_didIteratorError6) {
throw _iteratorError6;
}
}
}
filteredTransitions.add(t1);
}
}
} catch (err) {
_didIteratorError4 = true;
_iteratorError4 = err;
} finally {
try {
if (!_iteratorNormalCompletion4 && _iterator4.return) {
_iterator4.return();
}
} finally {
if (_didIteratorError4) {
throw _iteratorError4;
}
}
}
return filteredTransitions;
}
}]);
return Statechart;
}(base.BaseInterpreter);
base.Statechart = Statechart;
//simple default invoker
base.InterpreterScriptingContext.invokers = {
"http://www.w3.org/TR/scxml/": function httpWwwW3OrgTRScxml(invokingSession, invokeObj, invokerExecutionContext, cb) {
//put invoke logic here:
var method = void 0,
arg = void 0;
if (invokeObj.constructorFunction) {
var fnModel = invokeObj.constructorFunction;
var options = {
invokeid: invokeObj.id,
params: invokeObj.params,
parentSession: invokingSession,
docUrl: invokeObj.docUrl
//sessionid : //TODO: construct or generate a sessionid for invoked session
};
var model = invokerExecutionContext;
var interpreter = void 0;
if (options.parentSession instanceof Statechart) {
interpreter = new Statechart(fnModel, options);
}
cb(null, interpreter, fnModel, model);
//we introduce a delay here before starting the interpreter to give clients that are subscribed to onInvokedSessionInitialized event a chance to subscribe to events on the newly instantiated interpreter
setImmediate(function () {
return interpreter.start();
});
} else {
throw new Error('Invoke object needs a constructorFunction property');
}
}
};
base.InterpreterScriptingContext.invokers[undefined] = base.InterpreterScriptingContext.invokers[null] = base.InterpreterScriptingContext.invokers['scxml'] = base.InterpreterScriptingContext.invokers["http://www.w3.org/TR/scxml/"];
module.exports = base;
}).call(this,require("timers").setImmediate)
},{"@scion-scxml/core-base":6,"timers":105}],2:[function(require,module,exports){
'use strict';
/* begin ArraySet */
/** @constructor */
function ArraySet(l) {
l = l || [];
this.o = new Set(l);
}
ArraySet.prototype = {
add: function add(x) {
this.o.add(x);
},
remove: function remove(x) {
return this.o.delete(x);
},
union: function union(l) {
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;
try {
for (var _iterator = l.o[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
var v = _step.value;
this.o.add(v);
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return) {
_iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
return this;
},
difference: function difference(l) {
var _iteratorNormalCompletion2 = true;
var _didIteratorError2 = false;
var _iteratorError2 = undefined;
try {
for (var _iterator2 = l.o[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
var v = _step2.value;
this.o.delete(v);
}
} catch (err) {
_didIteratorError2 = true;
_iteratorError2 = err;
} finally {
try {
if (!_iteratorNormalCompletion2 && _iterator2.return) {
_iterator2.return();
}
} finally {
if (_didIteratorError2) {
throw _iteratorError2;
}
}
}
return this;
},
contains: function contains(x) {
return this.o.has(x);
},
iter: function iter() {
return Array.from(this.o);
},
isEmpty: function isEmpty() {
return !this.o.size;
},
size: function size() {
return this.o.size;
},
equals: function equals(s2) {
if (this.o.size !== s2.size()) {
return false;
}
var _iteratorNormalCompletion3 = true;
var _didIteratorError3 = false;
var _iteratorError3 = undefined;
try {
for (var _iterator3 = this.o[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) {
var v = _step3.value;
if (!s2.contains(v)) {
return false;
}
}
} catch (err) {
_didIteratorError3 = true;
_iteratorError3 = err;
} finally {
try {
if (!_iteratorNormalCompletion3 && _iterator3.return) {
_iterator3.return();
}
} finally {
if (_didIteratorError3) {
throw _iteratorError3;
}
}
}
return true;
},
toString: function toString() {
return this.o.size === 0 ? '<empty>' : Array.from(this.o).join(',\n');
}
};
module.exports = ArraySet;
},{}],3:[function(require,module,exports){
'use strict';
var STATE_TYPES = {
BASIC: 0,
COMPOSITE: 1,
PARALLEL: 2,
HISTORY: 3,
INITIAL: 4,
FINAL: 5
};
var SCXML_IOPROCESSOR_TYPE = 'http://www.w3.org/TR/scxml/#SCXMLEventProcessor';
var HTTP_IOPROCESSOR_TYPE = 'http://www.w3.org/TR/scxml/#BasicHTTPEventProcessor';
var RX_TRAILING_WILDCARD = /\.\*$/;
module.exports = {
STATE_TYPES: STATE_TYPES,
SCXML_IOPROCESSOR_TYPE: SCXML_IOPROCESSOR_TYPE,
HTTP_IOPROCESSOR_TYPE: HTTP_IOPROCESSOR_TYPE,
RX_TRAILING_WILDCARD: RX_TRAILING_WILDCARD
};
},{}],4:[function(require,module,exports){
'use strict';
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
var constants = require('./constants'),
STATE_TYPES = constants.STATE_TYPES,
RX_TRAILING_WILDCARD = constants.RX_TRAILING_WILDCARD;
var printTrace = false;
module.exports = {
extend: extend,
transitionWithTargets: transitionWithTargets,
transitionComparator: transitionComparator,
initializeModel: initializeModel,
isEventPrefixMatch: isEventPrefixMatch,
isTransitionMatch: isTransitionMatch,
scxmlPrefixTransitionSelector: scxmlPrefixTransitionSelector,
eventlessTransitionSelector: eventlessTransitionSelector,
getTransitionWithHigherSourceChildPriority: getTransitionWithHigherSourceChildPriority,
sortInEntryOrder: sortInEntryOrder,
getStateWithHigherSourceChildPriority: getStateWithHigherSourceChildPriority,
initializeModelGeneratorFn: initializeModelGeneratorFn,
deserializeSerializedConfiguration: deserializeSerializedConfiguration,
deserializeHistory: deserializeHistory
};
function extend(to, from) {
Object.keys(from).forEach(function (k) {
to[k] = from[k];
});
return to;
};
function transitionWithTargets(t) {
return t.targets;
}
function transitionComparator(t1, t2) {
return t1.documentOrder - t2.documentOrder;
}
function initializeModel(rootState, opts) {
var transitions = [],
idToStateMap = new Map(),
documentOrder = 0;
//TODO: need to add fake ids to anyone that doesn't have them
//FIXME: make this safer - break into multiple passes
var idCount = {};
function generateId(type) {
if (idCount[type] === undefined) idCount[type] = 0;
do {
var count = idCount[type]++;
var id = '$generated-' + type + '-' + count;
} while (idToStateMap.has(id));
return id;
}
function wrapInFakeRootState(state) {
return {
$deserializeDatamodel: state.$deserializeDatamodel || function () {},
$serializeDatamodel: state.$serializeDatamodel || function () {
return null;
},
$idToStateMap: idToStateMap, //keep this for handy deserialization of serialized configuration
docUrl: state.docUrl,
name: state.name,
states: [{
$type: 'initial',
transitions: [{
target: state
}]
}, state]
};
}
var statesWithInitialAttributes = [];
/**
@this {SCTransition}
*/
function transitionToString(sourceState) {
return sourceState + ' -- ' + (this.events ? '(' + this.events.join(',') + ')' : null) + (this.cond ? '[' + this.cond.name + ']' : '') + ' --> ' + (this.targets ? this.targets.join(',') : null);
}
/**
@this {SCState}
*/
function stateToString() {
return this.id;
}
function populateStateIdMap(state) {
//populate state id map
if (state.id) {
idToStateMap.set(state.id, state);
}
if (state.states) {
for (var j = 0, len = state.states.length; j < len; j++) {
populateStateIdMap(state.states[j]);
}
}
}
function traverse(ancestors, state) {
if (printTrace) state.toString = stateToString;
//add to global transition and state id caches
if (state.transitions) transitions.push.apply(transitions, state.transitions);
//create a default type, just to normalize things
//this way we can check for unsupported types below
state.$type = state.$type || 'state';
//add ancestors and depth properties
state.ancestors = ancestors;
state.depth = ancestors.length;
state.parent = ancestors[0];
state.documentOrder = documentOrder++;
//add some information to transitions
state.transitions = state.transitions || [];
for (var j = 0, len = state.transitions.length; j < len; j++) {
var transition = state.transitions[j];
transition.documentOrder = documentOrder++;
transition.source = state;
if (printTrace) transition.toString = transitionToString.bind(transition, state);
};
//recursive step
if (state.states) {
var ancs = [state].concat(ancestors);
for (var j = 0, len = state.states.length; j < len; j++) {
traverse(ancs, state.states[j]);
}
}
//setup fast state type
switch (state.$type) {
case 'parallel':
state.typeEnum = STATE_TYPES.PARALLEL;
state.isAtomic = false;
break;
case 'initial':
state.typeEnum = STATE_TYPES.INITIAL;
state.isAtomic = true;
break;
case 'history':
state.typeEnum = STATE_TYPES.HISTORY;
state.isAtomic = true;
break;
case 'final':
state.typeEnum = STATE_TYPES.FINAL;
state.isAtomic = true;
break;
case 'state':
case 'scxml':
if (state.states && state.states.length) {
state.typeEnum = STATE_TYPES.COMPOSITE;
state.isAtomic = false;
} else {
state.typeEnum = STATE_TYPES.BASIC;
state.isAtomic = true;
}
break;
default:
throw new Error('Unknown state type: ' + state.$type);
}
//descendants property on states will now be populated. add descendants to this state
if (state.states) {
state.descendants = state.states.concat(state.states.map(function (s) {
return s.descendants;
}).reduce(function (a, b) {
return a.concat(b);
}, []));
} else {
state.descendants = [];
}
var initialChildren;
if (state.typeEnum === STATE_TYPES.COMPOSITE) {
//set up initial state
if (Array.isArray(state.initial) || typeof state.initial === 'string') {
statesWithInitialAttributes.push(state);
} else {
//take the first child that has initial type, or first child
initialChildren = state.states.filter(function (child) {
return child.$type === 'initial';
});
state.initialRef = [initialChildren.length ? initialChildren[0] : state.states[0]];
checkInitialRef(state);
}
}
//hook up history
if (state.typeEnum === STATE_TYPES.COMPOSITE || state.typeEnum === STATE_TYPES.PARALLEL) {
var historyChildren = state.states.filter(function (s) {
return s.$type === 'history';
});
state.historyRef = historyChildren;
}
//now it's safe to fill in fake state ids
if (!state.id) {
state.id = generateId(state.$type);
idToStateMap.set(state.id, state);
}
//normalize onEntry/onExit, which can be single fn or array, or array of arrays (blocks)
['onEntry', 'onExit'].forEach(function (prop) {
if (state[prop]) {
if (!Array.isArray(state[prop])) {
state[prop] = [state[prop]];
}
if (!state[prop].every(function (handler) {
return Array.isArray(handler);
})) {
state[prop] = [state[prop]];
}
}
});
if (state.invokes && !Array.isArray(state.invokes)) {
state.invokes = [state.invokes];
state.invokes.forEach(function (invoke) {
if (invoke.finalize && !Array.isArray(invoke.finalize)) {
invoke.finalize = [invoke.finalize];
}
});
}
}
//TODO: convert events to regular expressions in advance
function checkInitialRef(state) {
if (!state.initialRef) throw new Error('Unable to locate initial state for composite state: ' + state.id);
}
function connectIntialAttributes() {
for (var j = 0, len = statesWithInitialAttributes.length; j < len; j++) {
var s = statesWithInitialAttributes[j];
var initialStates = Array.isArray(s.initial) ? s.initial : [s.initial];
s.initialRef = initialStates.map(function (initialState) {
return idToStateMap.get(initialState);
});
checkInitialRef(s);
}
}
var RX_WHITESPACE = /\s+/;
function connectTransitionGraph() {
//normalize as with onEntry/onExit
for (var i = 0, len = transitions.length; i < len; i++) {
var t = transitions[i];
if (t.onTransition && !Array.isArray(t.onTransition)) {
t.onTransition = [t.onTransition];
}
//normalize "event" attribute into "events" attribute
if (typeof t.event === 'string') {
t.events = t.event.trim().split(RX_WHITESPACE);
}
delete t.event;
if (t.targets || typeof t.target === 'undefined') {
//targets have already been set up
continue;
}
if (typeof t.target === 'string') {
var target = idToStateMap.get(t.target);
if (!target) throw new Error('Unable to find target state with id ' + t.target);
t.target = target;
t.targets = [t.target];
} else if (Array.isArray(t.target)) {
t.targets = t.target.map(function (target) {
if (typeof target === 'string') {
target = idToStateMap.get(target);
if (!target) throw new Error('Unable to find target state with id ' + t.target);
return target;
} else {
return target;
}
});
} else if (_typeof(t.target) === 'object') {
t.targets = [t.target];
} else {
throw new Error('Transition target has unknown type: ' + t.target);
}
}
//hook up LCA - optimization
for (var i = 0, len = transitions.length; i < len; i++) {
var t = transitions[i];
if (t.targets) t.lcca = getLCCA(t.source, t.targets[0]); //FIXME: we technically do not need to hang onto the lcca. only the scope is used by the algorithm
t.scope = getScope(t);
}
}
function getScope(transition) {
//Transition scope is normally the least common compound ancestor (lcca).
//Internal transitions have a scope equal to the source state.
var transitionIsReallyInternal = transition.type === 'internal' && transition.source.typeEnum === STATE_TYPES.COMPOSITE && //is transition source a composite state
transition.source.parent && //root state won't have parent
transition.targets && //does it target its descendants
transition.targets.every(function (target) {
return transition.source.descendants.indexOf(target) > -1;
});
if (!transition.targets) {
return null;
} else if (transitionIsReallyInternal) {
return transition.source;
} else {
return transition.lcca;
}
}
function getLCCA(s1, s2) {
var commonAncestors = [];
for (var j = 0, len = s1.ancestors.length; j < len; j++) {
var anc = s1.ancestors[j];
if ((opts && opts.legacySemantics ? anc.typeEnum === STATE_TYPES.COMPOSITE : anc.typeEnum === STATE_TYPES.COMPOSITE || anc.typeEnum === STATE_TYPES.PARALLEL) && anc.descendants.indexOf(s2) > -1) {
commonAncestors.push(anc);
}
};
if (!commonAncestors.length) throw new Error("Could not find LCA for states.");
return commonAncestors[0];
}
//main execution starts here
//FIXME: only wrap in root state if it's not a compound state
populateStateIdMap(rootState);
var fakeRootState = wrapInFakeRootState(rootState); //I wish we had pointer semantics and could make this a C-style "out argument". Instead we return him
traverse([], fakeRootState);
connectTransitionGraph();
connectIntialAttributes();
return fakeRootState;
}
function isEventPrefixMatch(prefix, fullName) {
prefix = prefix.replace(RX_TRAILING_WILDCARD, '');
if (prefix === fullName) {
return true;
}
if (prefix.length > fullName.length) {
return false;
}
if (fullName.charAt(prefix.length) !== '.') {
return false;
}
return fullName.indexOf(prefix) === 0;
}
function isTransitionMatch(t, eventName) {
return t.events.some(function (tEvent) {
return tEvent === '*' || isEventPrefixMatch(tEvent, eventName);
});
}
function scxmlPrefixTransitionSelector(t, event, evaluator, selectEventlessTransitions) {
return (selectEventlessTransitions ? !t.events : t.events && event && event.name && isTransitionMatch(t, event.name)) && (!t.cond || evaluator(t.cond));
}
function eventlessTransitionSelector(state) {
return state.transitions.filter(function (transition) {
return !transition.events || transition.events && transition.events.length === 0;
});
}
//priority comparison functions
function getTransitionWithHigherSourceChildPriority(_args) {
var t1 = _args[0],
t2 = _args[1];
var r = getStateWithHigherSourceChildPriority(t1.source, t2.source);
//compare transitions based first on depth, then based on document order
if (t1.source.depth < t2.source.depth) {
return t2;
} else if (t2.source.depth < t1.source.depth) {
return t1;
} else {
if (t1.documentOrder < t2.documentOrder) {
return t1;
} else {
return t2;
}
}
}
function sortInEntryOrder(s1, s2) {
return getStateWithHigherSourceChildPriority(s1, s2) * -1;
}
function getStateWithHigherSourceChildPriority(s1, s2) {
//compare states based first on depth, then based on document order
if (s1.depth > s2.depth) {
return -1;
} else if (s1.depth < s2.depth) {
return 1;
} else {
//Equality
if (s1.documentOrder < s2.documentOrder) {
return 1;
} else if (s1.documentOrder > s2.documentOrder) {
return -1;
} else {
return 0;
}
}
}
function initializeModelGeneratorFn(modelFn, opts, interpreter) {
return modelFn.call(interpreter, opts._x, opts._x._sessionid, opts._x._ioprocessors, interpreter.isIn.bind(interpreter));
}
function deserializeSerializedConfiguration(serializedConfiguration, idToStateMap) {
return serializedConfiguration.map(function (id) {
var state = idToStateMap.get(id);
if (!state) throw new Error('Error loading serialized configuration. Unable to locate state with id ' + id);
return state;
});
}
function deserializeHistory(serializedHistory, idToStateMap) {
var o = {};
Object.keys(serializedHistory).forEach(function (sid) {
o[sid] = serializedHistory[sid].map(function (id) {
var state = idToStateMap.get(id);
if (!state) throw new Error('Error loading serialized history. Unable to locate state with id ' + id);
return state;
});
});
return o;
}
},{"./constants":3}],5:[function(require,module,exports){
'use strict';
var constants = require('./constants');
//model accessor functions
var query = {
isDescendant: function isDescendant(s1, s2) {
//Returns 'true' if state1 is a descendant of state2 (a child, or a child of a child, or a child of a child of a child, etc.) Otherwise returns 'false'.
return s2.descendants.indexOf(s1) > -1;
},
getAncestors: function getAncestors(s, root) {
var ancestors, index, state;
index = s.ancestors.indexOf(root);
if (index > -1) {
return s.ancestors.slice(0, index);
} else {
return s.ancestors;
}
},
isOrthogonalTo: function isOrthogonalTo(s1, s2) {
//Two control states are orthogonal if they are not ancestrally
//related, and their smallest, mutual parent is a Concurrent-state.
return !this.isAncestrallyRelatedTo(s1, s2) && this.getLCA(s1, s2).typeEnum === constants.STATE_TYPES.PARALLEL;
},
isAncestrallyRelatedTo: function isAncestrallyRelatedTo(s1, s2) {
//Two control states are ancestrally related if one is child/grandchild of another.
return this.getAncestorsOrSelf(s2).indexOf(s1) > -1 || this.getAncestorsOrSelf(s1).indexOf(s2) > -1;
},
getAncestorsOrSelf: function getAncestorsOrSelf(s, root) {
return [s].concat(query.getAncestors(s, root));
},
getDescendantsOrSelf: function getDescendantsOrSelf(s) {
return [s].concat(s.descendants);
},
getLCA: function getLCA(s1, s2) {
var commonAncestors = this.getAncestors(s1).filter(function (a) {
return a.descendants.indexOf(s2) > -1;
}, this);
return commonAncestors[0];
}
};
module.exports = query;
},{"./constants":3}],6:[function(require,module,exports){
(function (process,setImmediate){
// Copyright 2012-2012 Jacob Beard, INFICON, and other SCION contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* SCION-CORE global object
* @namespace scion
*/
/**
* An Array of strings representing the ids all of the basic states the
* interpreter is in after a big-step completes.
* @typedef {Array<string>} Configuration
*/
/**
* A set of basic and composite state ids.
* @typedef {Array<string>} FullConfiguration
*/
/**
* A set of basic and composite state ids.
* @typedef {Array<string>} FullConfiguration
*/
"use strict";
var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
var EventEmitter = require('tiny-events').EventEmitter,
util = require('util'),
ArraySet = require('./ArraySet'),
constants = require('./constants'),
helpers = require('./helpers'),
query = require('./query'),
extend = helpers.extend,
transitionWithTargets = helpers.transitionWithTargets,
transitionComparator = helpers.transitionComparator,
initializeModel = helpers.initializeModel,
isEventPrefixMatch = helpers.isEventPrefixMatch,
isTransitionMatch = helpers.isTransitionMatch,
scxmlPrefixTransitionSelector = helpers.scxmlPrefixTransitionSelector,
eventlessTransitionSelector = helpers.eventlessTransitionSelector,
getTransitionWithHigherSourceChildPriority = helpers.getTransitionWithHigherSourceChildPriority,
sortInEntryOrder = helpers.sortInEntryOrder,
getStateWithHigherSourceChildPriority = helpers.getStateWithHigherSourceChildPriority,
initializeModelGeneratorFn = helpers.initializeModelGeneratorFn,
deserializeSerializedConfiguration = helpers.deserializeSerializedConfiguration,
deserializeHistory = helpers.deserializeHistory,
BASIC = constants.STATE_TYPES.BASIC,
COMPOSITE = constants.STATE_TYPES.COMPOSITE,
PARALLEL = constants.STATE_TYPES.PARALLEL,
HISTORY = constants.STATE_TYPES.HISTORY,
INITIAL = constants.STATE_TYPES.INITIAL,
FINAL = constants.STATE_TYPES.FINAL,
SCXML_IOPROCESSOR_TYPE = constants.SCXML_IOPROCESSOR_TYPE;
var printTrace = typeof process !== 'undefined' && !!process.env.DEBUG;
/**
* @interface EventEmitter
*/
/**
* @event scion.BaseInterpreter#onError
* @property {string} tagname The name of the element that produced the error.
* @property {number} line The line in the source file in which the error occurred.
* @property {number} column The column in the source file in which the error occurred.
* @property {string} reason An informative error message. The text is platform-specific and subject to change.
*/
/**
* @function
* @name EventEmitter.prototype#on
* @param {string} type
* @param {callback} listener
*/
/**
* @function
* @name EventEmitter.prototype#once
* @param {string} type
* @param {callback} listener
*/
/**
* @function
* @name EventEmitter.prototype#off
* @param {string} type
* @param {callback} listener
*/
/**
* @function
* @name EventEmitter.prototype#emit
* @param {string} type
* @param {any} args
*/
/**
* @description The SCXML constructor creates an interpreter instance from a model object.
* @abstract
* @class BaseInterpreter
* @memberof scion
* @extends EventEmitter
* @param {SCJSON | scxml.ModelFactory} modelOrModelFactory Either an SCJSON root state; or an scxml.ModelFactory, which is a function which returns an SCJSON object.
* @param opts
* @param {string} [opts.sessionid] Used to populate SCXML _sessionid.
* @param {function} [opts.generateSessionid] Factory used to generate sessionid if sessionid keyword is not specified
* @param {Map<string, BaseInterpreter>} [opts.sessionRegistry] Map used to map sessionid strings to Statechart instances.
* @param [opts.Set] Class to use as an ArraySet. Defaults to ES6 Set.
* @param {object} [opts.params] Used to pass params from invoke. Sets the datamodel when interpreter is instantiated.
* @param {Snapshot} [opts.snapshot] State machine snapshot. Used to restore a serialized state machine.
* @param {Statechart} [opts.parentSession] Used to pass parent session during invoke.
* @param {string }[opts.invokeid] Support for id of invoke element at runtime.
* @param {boolean} [opts.legacySemantics]
* @param [opts.console]
* @param [opts.transitionSelector]
* @param [opts.customCancel]
* @param [opts.customSend]
* @param [opts.sendAsync]
* @param [opts.doSend]
* @param [opts.invokers]
* @param [opts.xmlParser]
* @param [opts.interpreterScriptingContext]
* @param [opts.invokerExecutionContext]
*/
var BaseInterpreter = function (_EventEmitter) {
_inherits(BaseInterpreter, _EventEmitter);
function BaseInterpreter(modelOrModelFactory, opts) {
_classCallCheck(this, BaseInterpreter);
var _this = _possibleConstructorReturn(this, (BaseInterpreter.__proto__ || Object.getPrototypeOf(BaseInterpreter)).call(this));
_this.opts = opts;
_this.opts.InterpreterScriptingContext = _this.opts.InterpreterScriptingContext || InterpreterScriptingContext;
_this._isStepping = false;
_this._scriptingContext = _this.opts.interpreterScriptingContext || (_this.opts.InterpreterScriptingContext ? new _this.opts.InterpreterScriptingContext(_this) : {});
_this.opts.generateSessionid = _this.opts.generateSessionid || BaseInterpreter.generateSessionid;
_this.opts.sessionid = _this.opts.sessionid || _this.opts.generateSessionid();
_this.opts.sessionRegistry = _this.opts.sessionRegistry || BaseInterpreter.sessionRegistry; //TODO: define a better interface. For now, assume a Map<sessionid, session>
_this.opts.invokerExecutionContext = opts.invokerExecutionContext || modelOrModelFactory._executionContext;
var _ioprocessors = {};
_ioprocessors[SCXML_IOPROCESSOR_TYPE] = {
location: '#_scxml_' + _this.opts.sessionid
};
_ioprocessors.scxml = _ioprocessors[SCXML_IOPROCESSOR_TYPE]; //alias
//SCXML system variables:
_this.opts._x = {
_sessionid: _this.opts.sessionid,
_ioprocessors: _ioprocessors
};
var model;
if (typeof modelOrModelFactory === 'function') {
model = initializeModelGeneratorFn(modelOrModelFactory, _this.opts, _this);
} else if ((typeof modelOrModelFactory === 'undefined' ? 'undefined' : _typeof(modelOrModelFactory)) === 'object') {
model = JSON.parse(JSON.stringify(modelOrModelFactory)); //assume object
} else {
throw new Error('Unexpected model type. Expected model factory function, or scjson object.');
}
_this._model = initializeModel(model, _this.opts);
_this.opts.console = _this.opts.console || (typeof console === 'undefined' ? { log: function log() {} } : console); //rely on global console if this console is undefined
_this.opts.Set = _this.opts.Set || ArraySet;
_this.opts.priorityComparisonFn = _this.opts.priorityComparisonFn || getTransitionWithHigherSourceChildPriority;
_this.opts.transitionSelector = _this.opts.transitionSelector || scxmlPrefixTransitionSelector;
_this.opts.sessionRegistry.set(String(_this.opts.sessionid), _this);
_this._scriptingContext.log = _this._scriptingContext.log || function log() {
if (this.opts.console.log.apply) {
this.opts.console.log.apply(this.opts.console, arguments);
} else {
//console.log on older IE does not support Function.apply, so just pass him the first argument. Best we can do for now.
this.opts.console.log(Array.prototype.slice.apply(arguments).join(','));
}
}.bind(_this); //set up default scripting context log function
_this._externalEventQueue = [];
_this._internalEventQueue = [];
if (_this.opts.params) {
_this._model.$deserializeDatamodel(_this.opts.params); //load up the datamodel
}
//check if we're loading from a previous snapshot
if (_this.opts.snapshot) {
_this._configuration = new _this.opts.Set(deserializeSerializedConfiguration(_this.opts.snapshot[0], _this._model.$idToStateMap));
_this._historyValue = deserializeHistory(_this.opts.snapshot[1], _this._model.$idToStateMap);
_this._isInFinalState = _this.opts.snapshot[2];
_this._model.$deserializeDatamodel(_this.opts.snapshot[3]); //load up the datamodel
_this._internalEventQueue = _this.opts.snapshot[4];
} else {
_this._configuration = new _this.opts.Set();
_this._historyValue = {};
_this._isInFinalState = false;
}
//add debug logging
BaseInterpreter.EVENTS.forEach(function (event) {
this.on(event, this._log.bind(this, event));
}, _this);
module.exports.emit('new', _this);
return _this;
}
/**
* Cancels the session. This clears all timers; puts the interpreter in a
* final state; and runs all exit actions on current states.
* @memberof BaseInterpreter.prototype
*/
_createClass(BaseInterpreter, [{
key: 'cancel',
value: function cancel() {
delete this.opts.parentSession;
if (this._isInFinalState) return;
this._isInFinalState = true;
this._log('session cancelled ' + this.opts.invokeid);
this._exitInterpreter(null);
}
}, {
key: '_exitInterpreter',
value: function _exitInterpreter(event) {
var _this2 = this;
//TODO: cancel invoked sessions
//cancel all delayed sends when we enter into a final state.
this._cancelAllDelayedSends();
var statesToExit = this._getFullConfiguration().sort(getStateWithHigherSourceChildPriority);
for (var j = 0, len = statesToExit.length; j < len; j++) {
var stateExited = statesToExit[j];
if (stateExited.onExit !== undefined) {
for (var exitIdx = 0, exitLen = stateExited.onExit.length; exitIdx < exitLen; exitIdx++) {
var block = stateExited.onExit[exitIdx];
for (var blockIdx = 0, blockLen = block.length; blockIdx < blockLen; blockIdx++) {
var actionRef = block[blockIdx];
try {
actionRef.call(this._scriptingContext, null);
} catch (e) {
this._handleError(e, actionRef);
break;
}
}
}
}
//cancel invoked session
if (stateExited.invokes) stateExited.invokes.forEach(function (invoke) {
_this2._scriptingContext.cancelInvoke(invoke.id);
});
//if he is a top-level <final> state, then return the done event
if (stateExited.$type === 'final' && stateExited.parent.$type === 'scxml') {
if (this.opts.parentSession) {
this._scriptingContext.send({
target: '#_parent',
name: 'done.invoke.' + this.opts.invokeid,
data: stateExited.donedata && stateExited.donedata.call(this._scriptingContext, event)
});
}
this.opts.sessionRegistry.delete(this.opts.sessionid);
this.emit('onExitInterpreter', event);
}
}
}
/**
* Starts the interpreter. Should only be called once, and should be called
* before BaseInterpreter.prototype#gen is called for the first time. Returns a
* Configuration.
* @return {Configuration}
* @memberof BaseInterpreter.prototype
* @emits scion.BaseInterpreter#onEntry
* @emits scion.BaseInterpreter#onExit
* @emits scion.BaseInterpreter#onTransition
* @emits scion.BaseInterpreter#onDefaultEntry
* @emits scion.BaseInterpreter#onError
* @emits scion.BaseInterpreter#onBigStepBegin
* @emits scion.BaseInterpreter#onBigStepEnd
* @emits scion.BaseInterpreter#onBigStepSuspend
* @emits scion.BaseInterpreter#onBigStepResume
* @emits scion.BaseInterpreter#onSmallStepBegin
* @emits scion.BaseInterpreter#onSmallStepEnd
* @emits scion.BaseInterpreter#onBigStepEnd
* @emits scion.BaseInterpreter#onExitInterpreter
*/
}, {
key: 'start',
value: function start() {
this._initStart();
this._performBigStep();
return this.getConfiguration();
}
/**
* This callback is displayed as a global member.
* @callback genCallback
* @param {Error} err
* @param {Configuration} configuration
*/
/**
* Starts the interpreter asynchronously
* @param {genCallback} cb Callback invoked with an error or th