UNPKG

@scion-scxml/scxml

Version:

An implementation of SCXML in JavaScript.

1,296 lines (1,099 loc) 2.89 MB
(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