webgme-hfsm
Version:
WebGME Domain for creating Executable Heirarchical Finite State Machines (HFSMs). Contains metamodel, visualization, simulation, and code generation for Heirarchical Finite State Machines (HFSMs) following the UML State Machine specification.
321 lines (315 loc) • 12 kB
JavaScript
define(['./checkModel', 'underscore'], function(checkModel, _) {
'use strict';
return {
stripRegex: /^([^\n]+)/gm,
uniq: function(a) {
var seen = {};
return a.filter(function(item) {
return seen.hasOwnProperty(item) ? false : (seen[item] = true);
});
},
makeEventName: function(name) {
return name.trim(); // toUpperCase();
},
addEvent: function(model, obj, eventName) {
var self = this;
eventName = self.makeEventName(eventName);
obj.EventName = eventName;
if (eventName) {
// go to the State Machine object and add it there.
var p = model.objects[obj.parentPath];
while (p && p.type && p.type != 'State Machine') {
p = model.objects[p.parentPath];
}
if (p) {
if (!p.eventNames) {
p.eventNames = [];
}
p.eventNames.push( eventName );
}
}
},
sanitizeString: function(str) {
return str.replace(/[ \-]/gi,'_');
},
processTopLevel: function(obj) {
var self = this;
var sName = self.sanitizeString(obj.name);
obj.sanitizedName = sName;
if (obj.Declarations) {
obj.Declarations = obj.Declarations.replace(self.stripRegex, " $1");
}
if (obj.Definitions) {
obj.Definitions = obj.Definitions.replace(self.stripRegex, " $1");
}
if (!obj.eventNames) {
obj.eventNames = [];
}
},
addBasicParams: function(obj) {
obj.Substates = [];
obj.UnhandledEvents = [];
obj.isRoot = false;
obj.isExternalTransition = false;
obj.isLocalTransition = false;
obj.isState = false;
obj.isChoice = false;
obj.isDeepHistory = false;
obj.isShallowHistory = false;
obj.isEnd = false;
},
processModel: function(model) {
var self = this;
// REMOVE ALL EVENTS THAT ARE MARKED AS DISABLED
var transitionTypes = ['External Transition', 'Local Transition', 'Internal Transition'];
Object.keys(model.objects).map(function(objPath) {
var obj = model.objects[objPath];
if (transitionTypes.indexOf(obj.type) > -1 && !obj.Enabled) {
console.log('deleting disabled transition: '+objPath);
delete model.objects[objPath];
}
});
checkModel.checkModel(model);
// THIS FUNCTION HANDLES CREATION OF SOME CONVENIENCE MEMBERS
// FOR SELECT OBJECTS IN THE MODEL
var objPaths = Object.keys(model.objects);
objPaths.map(function(objPath) {
var obj = model.objects[objPath];
// init all basic params
self.addBasicParams( obj );
// Make sure top-level State Machine objects
// are good and code attributes are properly prefixed.
if (obj.type == 'State Machine' || obj.type == 'Library') {
self.processTopLevel( obj );
obj.isRoot = true;
}
// Process External Transition Data into convenience
// members of source State
else if (obj.type == 'External Transition') {
// need function to get final state that doesn't terminate on end states
var src = model.objects[obj.pointers['src']],
dst = model.objects[obj.pointers['dst']];
if ( src && dst ) {
// valid transition with source and destination pointers in the tree
// add new data to the object
obj.isExternalTransition = true;
obj.prevState = src;
obj.nextState = dst;
if (obj.Event) {
// add the event to a global list of events
self.addEvent( model, obj, obj.Event );
// add the external transition to the source
self.updateEventInfo( 'ExternalEvents',
src,
obj );
}
else if (src.type == 'Choice Pseudostate' ||
src.type == 'Initial') {
// add the external transition to the source
if (src.ExternalTransitions == undefined)
src.ExternalTransitions = [];
src.ExternalTransitions.push( obj );
src.ExternalTransitions.sort( self.transitionSort );
}
else {
// should be end event! need to build transition functions properly
}
}
}
// Process Local Transition Data into convenience
// members of source State
else if (obj.type == 'Local Transition') {
// need function to get final state that doesn't terminate on end states
var src = model.objects[obj.pointers['src']],
dst = model.objects[obj.pointers['dst']];
if ( src && dst ) {
// valid transition with source and destination pointers in the tree
// add new data to the object
obj.isLocalTransition = true;
obj.prevState = src;
obj.nextState = dst;
if (obj.Event) {
// add the event to a global list of events
self.addEvent( model, obj, obj.Event );
// add the external transition to the source
self.updateEventInfo( 'ExternalEvents',
src,
obj );
}
else {
// should never happen since this is local transition!
}
}
}
// Process Internal Transition Data into convenience
// members of parent State
else if (obj.type == 'Internal Transition') {
var parent = model.objects[ obj.parentPath ];
if (parent) {
// add the event to a global list of events
self.addEvent( model, obj, obj.Event );
// add the internal transition to the parent
self.updateEventInfo( 'InternalEvents',
parent,
obj );
}
}
// Process End State Data
else if (obj.type == 'End State') {
// for mustache template
obj.isEnd = true;
// add sanitized name
var sName = self.sanitizeString(obj.name);
obj.sanitizedName = sName;
// if root, make convenience to it
var parent = model.objects[ obj.parentPath ];
if (parent && parent.type != 'State') {
parent.END = obj;
}
else {
var endTransition = checkModel.getEndTransitions( parent, model.objects );
obj.endTransition = endTransition[0];
}
}
// Process Choice Pseudostate Data
else if (obj.type == 'Choice Pseudostate') {
// make a substate of its parent
self.makeSubstate( obj, model.objects );
// for mustache template
obj.isChoice = true;
// add sanitized name
var sName = self.sanitizeString(obj.name);
obj.sanitizedName = sName;
// make external transition convenience
var extTrans = checkModel.getTransitionsOutOf( obj, model.objects );
}
// Process Process Deep History Pseudostate Data
else if (obj.type == 'Deep History Pseudostate') {
// make a substate of its parent
self.makeSubstate( obj, model.objects );
// shouldn't need to do anything special here,
// just treat it like a normal state
// sanitize name for class name
var sName = self.sanitizeString(obj.name);
obj.sanitizedName = sName;
// for mustache template
obj.isDeepHistory = true;
}
// Process Process Shallow History Pseudostate Data
else if (obj.type == 'Shallow History Pseudostate') {
// make a substate of its parent
self.makeSubstate( obj, model.objects );
// shouldn't need to do anything special here,
// just treat it like a normal state
// sanitize name for class name
var sName = self.sanitizeString(obj.name);
obj.sanitizedName = sName;
// for mustache template
obj.isShallowHistory = true;
}
// make the state names for the variables and such
else if (obj.type == 'State') {
// make a substate of its parent
self.makeSubstate( obj, model.objects );
// for mustache template
obj.isState = true;
// sanitize name for class name
var sName = self.sanitizeString(obj.name);
obj.sanitizedName = sName;
// make sure the State_list is either a real list or null
if (!obj.State_list) {
obj.State_list = null;
}
// update the prefix for the state function
obj['Tick'] = obj['Tick'].replace(self.stripRegex, " $1");
obj['Exit'] = obj['Exit'].replace(self.stripRegex, " $1");
}
});
// make sure event names are unique and sort them
objPaths.map(function(objPath) {
var obj = model.objects[objPath];
if (obj.type == 'State Machine') {
obj.eventNames = self.uniq( obj.eventNames ).sort();
}
});
// make sure all objects have convenience members
self.makeConvenience( model );
},
// MAKE CONVENIENCE FOR WHAT EVENTS ARE HANDLED BY WHICH STATES
makeSubstate: function(obj, objDict) {
var parent = objDict[ obj.parentPath ];
if (parent) {
if (parent.Substates == undefined)
parent.Substates = [];
parent.Substates.push( obj );
}
},
findUnhandledEvents: function(obj, objDict) {
var self = this;
var parent = objDict[ obj.parentPath ];
if (parent) {
// figure out disjoint set of events
var handledEventNames = [];
if (obj.InternalEvents) {
handledEventNames = handledEventNames.concat(obj.InternalEvents.map((e) => {
return e.name;
}));
}
if (obj.ExternalEvents) {
handledEventNames = handledEventNames.concat(obj.ExternalEvents.map((e) => {
return e.name;
}));
}
obj.UnhandledEvents = _.difference( parent.UnhandledEvents, handledEventNames );
}
// recurse down from the top
obj.Substates.map((s) => {
self.findUnhandledEvents(s, objDict);
});
},
makeConvenience: function(model) {
var self = this;
Object.keys(model.objects).map((path) => {
var obj = model.objects[path];
if (obj.type == 'State Machine') {
obj.UnhandledEvents = obj.eventNames;
self.findUnhandledEvents(obj, model.objects);
}
});
},
transitionSort: function(transA, transB) {
var a = transA.Guard;
var b = transB.Guard;
if (!a && b) return 1;
if (a && !b) return -1;
return 0;
},
getEventInfo: function( key, obj, eventName ) {
var self = this;
var eventInfo = obj[ key ]; // should be list of objects { name: , Transitions: }
if (eventInfo == undefined) {
// have not had any events
obj[ key ] = [ { name: eventName, Transitions: [] } ];
eventInfo = obj[ key ];
}
eventInfo = eventInfo.filter(function(o) { return o.name == eventName; });
if (eventInfo.length == 0) {
// have had other events, but not this one
eventInfo = { name: eventName, Transitions: [] };
obj[ key ].push( eventInfo );
}
else {
// have had this event
eventInfo = eventInfo[0];
}
return eventInfo;
},
updateEventInfo: function( key, obj, transition ) {
var self = this;
var eventInfo = self.getEventInfo( key, obj, transition.EventName );
eventInfo.Transitions.push( transition );
eventInfo.Transitions.sort( self.transitionSort );
},
// END CONVENIENCE
}
});