Permalink
Browse files

Rewrote core SCXML interpreter in JavaScript.

  • Loading branch information...
jbeard4 committed Mar 29, 2012
1 parent 726e243 commit 26aba9727a22c7d1c39e264b75c78f53ac624dd1
View

Large diffs are not rendered by default.

Oops, something went wrong.
@@ -0,0 +1,21 @@
+// Copyright 2011-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.
+
+var _ = require('underscore');
+
+module.exports = function(state,eventNames,evaluator){
+ return _.filter(state.transitions,function(t){
+ return !t.event || ( _.contains(eventNames,t.event) && (!t.cond || evaluator(t)) );
+ });
+};
View
@@ -0,0 +1,133 @@
+// Copyright 2011-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.
+
+var _ = require('underscore');
+
+function getDelayInMs(delayString){
+ if (!delayString) {
+ return 0;
+ } else {
+ if (delayString.slice(-2) === "ms") {
+ return parseFloat(delayString.slice(0, -2));
+ } else if (delayString.slice(-1) === "s") {
+ return parseFloat(delayString.slice(0, -1)) * 1000;
+ } else {
+ return parseFloat(delayString);
+ }
+ }
+}
+
+function makeEvaluationFn(s, isExpression) {
+ return new Function(
+ "getData",
+ "setData",
+ "In",
+ "_events",
+ "datamodel",
+ "var _event = _events[0]; with(datamodel){" + (isExpression ? "return" : "") + " " + s + "}");
+}
+
+module.exports = function(json) {
+
+ function stateIdToReference(stateId){
+ return idToStateMap[stateId];
+ }
+
+ var idToStateMap = {};
+ _.forEach(json.states,function(state){
+ idToStateMap[state.id] = state;
+ });
+
+ _.forEach(json.transitions,function(transition){
+ transition.evaluateCondition = makeEvaluationFn(transition.cond,true);
+ });
+
+ _.forEach(json.states,function(state){
+ state.transitions = _.map(state.transitions,function(transitionNum){ return json.transitions[transitionNum];});
+
+ var actions = state.onentry.concat(state.onexit);
+
+ _.forEach(state.transitions,function(transition){
+ _.forEach(transition.actions,function(action){
+ actions.push(action);
+ });
+
+ if(transition.lca){
+ transition.lca = idToStateMap[transition.lca];
+ }
+ });
+
+ _.forEach(actions,function(action){
+ switch (action.type) {
+ case "script":
+ action.evaluate = makeEvaluationFn(action.script);
+ break;
+ case "assign":
+ action.evaluate = makeEvaluationFn(action.expr, true);
+ break;
+ case "send":
+ _.forEach(['contentexpr', 'eventexpr', 'targetexpr', 'typeexpr', 'delayexpr'],function(attributeName){
+ if (action[attributeName]) {
+ action[attributeName] = {
+ evaluate: makeEvaluationFn(action[attributeName], true)
+ };
+ }
+ });
+
+ _.forEach(action.params,function(param){
+ if (param.expr) {
+ param.expr = {
+ evaluate: makeEvaluationFn(param.expr, true)
+ };
+ }
+ });
+ break;
+ case "log":
+ action.evaluate = makeEvaluationFn(action.expr, true);
+ break;
+ default : break;
+ }
+
+ if (action.type === "send" && action.delay) {
+ action.delay = getDelayInMs(action.delay);
+ }
+
+ });
+
+ state.initial = idToStateMap[state.initial];
+ state.history = idToStateMap[state.history];
+
+ state.children = _.map(state.children,stateIdToReference);
+
+ state.parent = idToStateMap[state.parent];
+
+ if (state.ancestors) {
+ state.ancestors = _.map(state.ancestors,stateIdToReference);
+ }
+
+ if (state.descendants) {
+ state.descendants = _.map(state.descendants,stateIdToReference);
+ }
+
+ _.forEach(state.transitions,function(t){
+ t.source = idToStateMap[t.source];
+ t.targets = t.targets && _.map(t.targets,stateIdToReference);
+ });
+ });
+
+ json.root = idToStateMap[json.root];
+
+ return json;
+};
+
View
@@ -0,0 +1,100 @@
+// Copyright 2011-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.
+
+var _ = require('underscore');
+
+var stateKinds = require('./state-kinds-enum');
+
+module.exports = {
+ getDepth: function(s) {
+ var count, state;
+ if (s.depth !== undefined) {
+ return s.depth;
+ } else {
+ count = 0;
+ state = s.parent;
+ while (state) {
+ count = count + 1;
+ state = state.parent;
+ }
+ return count;
+ }
+ },
+ getAncestors: function(s, root) {
+ var ancestors, index, state;
+ if (s.ancestors) {
+ if (_.contains(s.ancestors,root)) {
+ return s.ancestors.slice(0, index);
+ } else {
+ return s.ancestors;
+ }
+ } else {
+ ancestors = [];
+ state = s.parent;
+ while (state && !(state === root)) {
+ ancestors.push(state);
+ state = state.parent;
+ }
+ return ancestors;
+ }
+ },
+ getAncestorsOrSelf: function(s, root) {
+ return [s].concat(this.getAncestors(s, root));
+ },
+ getDescendants: function(s) {
+ var child, descendants, queue, state, _i, _len, _ref;
+ if (s.descendants) {
+ return s.descendants;
+ } else {
+ descendants = [];
+ queue = s.children.slice();
+ while (queue.length) {
+ state = queue.shift();
+ descendants.push(state);
+ queue.push.apply(queue,state.children);
+ }
+ return descendants;
+ }
+ },
+ getDescendantsOrSelf: function(s) {
+ return [s].concat(this.getDescendants(s));
+ },
+ isOrthogonalTo: function(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).kind === stateKinds.PARALLEL;
+ },
+ isAncestrallyRelatedTo: function(s1, s2) {
+ //Two control states are ancestrally related if one is child/grandchild of another.
+ return _.contains(this.getAncestorsOrSelf(s2), s1) || _.contains(this.getAncestorsOrSelf(s1), s2);
+ },
+ getLCA: function(tOrS1, s2) {
+ if (tOrS1.lca) {
+ return tOrS1.lca;
+ } else {
+ //can take one or two arguments: either 1 transition, or two states
+ if (arguments.length === 1) {
+ var transition = tOrS1;
+ return this.getLCA(transition.source, transition.targets[0]);
+ } else {
+ var s1 = tOrS1;
+ var commonAncestors = _.filter(this.getAncestors(s1),_.bind(function(a){
+ return _.contains(this.getDescendants(a),s2);
+ },this));
+ return commonAncestors[0];
+ }
+ }
+ }
+};
+
@@ -0,0 +1,44 @@
+// Copyright 2011-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.
+
+var _ = require('underscore');
+
+var eventNameReCache = {};
+
+function eventNameToRe(name) {
+ return new RegExp("^" + (name.replace(/\./g, "\\.")) + "(\\.[0-9a-zA-Z]+)*$");
+}
+
+function retrieveEventRe(name) {
+ return eventNameReCache[name] ? eventNameReCache[name] : eventNameReCache[name] = eventNameToRe(name);
+}
+
+function nameMatch(t, eventNames) {
+ var tEvents = t.events;
+ var f =
+ _.contains(tEvents, "*") ?
+ function() { return true; } :
+ function(name) {
+ return _(tEvents).filter(function(tEvent){
+ return retrieveEventRe(tEvent).test(name);
+ }).length;
+ };
+ return _(eventNames).filter(f).length;
+}
+
+module.exports = function(state, eventNames, evaluator) {
+ return _(state.transitions).filter(function(t){
+ return (!t.events || nameMatch(t,eventNames)) && (!t.cond || evaluator(t));
+ });
+};
View
@@ -0,0 +1,81 @@
+// Copyright 2011-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.
+
+var _ = require('underscore');
+
+module.exports = function(l) {
+ l = l || [];
+ this.o = [];
+
+ _.forEach(l,_.bind(function(x){
+ this.add(x);
+ },this));
+};
+
+//TODO: delegate to underscore's union and difference
+module.exports.prototype = {
+
+ add : function(x) {
+ if (!this.contains(x)) return this.o.push(x);
+ },
+
+ remove : function(x) {
+ var i = _.indexOf(this.o,x);
+ if(i === -1){
+ return false;
+ }else{
+ this.o.splice(i, 1);
+ }
+ return true;
+ },
+
+ union : function(l) {
+ l = l.iter ? l.iter() : l;
+ _.forEach(l,_.bind(function(x){
+ this.add(x);
+ },this));
+ return this;
+ },
+
+ difference : function(l) {
+ l = l.iter ? l.iter() : l;
+
+ _.forEach(l,_.bind(function(x){
+ this.remove(x);
+ },this));
+ return this;
+ },
+
+ contains : function(x) {
+ return _.indexOf(this.o, x) >= 0;
+ },
+
+ iter : function() {
+ return this.o;
+ },
+
+ isEmpty : function() {
+ return !this.o.length;
+ },
+
+ equals : function(s2) {
+ var l2 = s2.iter();
+
+ return _.difference(this.o,l2).length === 0;
+ },
+
+ toString : function() {
+ return "Set(" + this.o.toString() + ")";
+ }
+};
Oops, something went wrong.

0 comments on commit 26aba97

Please sign in to comment.