diff --git a/.gitignore b/.gitignore index 3807c528..eb32f715 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,3 @@ -*.pyc -*.sw* -Session.vim -target node_modules -build -src/demo/node-repl/build/ -src/demo/nodejs/build/ -scion-browser.js -scion-browser-min.js -TODO -log +components +out* diff --git a/.travis.yml b/.travis.yml index c9d7916b..6c07ddca 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,8 @@ language: node_js node_js: - - 0.8 + - 0.12 + - 0.11 - 0.10 + - 0.8 before_install: - - git submodule update --init --recursive + - npm install -g npm diff --git a/Makefile b/Makefile index b6a6084b..1e0de049 100644 --- a/Makefile +++ b/Makefile @@ -1,14 +1,18 @@ js = $(shell find lib/ -name "*.js") -all : dist/scion.js dist/scion-min.js +all : dist/scxml.js dist/scxml.min.js -dist/scion.js : $(js) - mkdir -p dist && node lib/browser/build/stitch.js dist/scion.js +dist/scxml.js : $(js) + component build -o dist -n scxml -s scxml -dist/scion-min.js : dist/scion.js - uglifyjs dist/scion.js > dist/scion-min.js +dist/scxml.min.js : dist/scxml.js + uglifyjs dist/scxml.js > dist/scxml.min.js -clean : - rm -rf dist +get-deps : + npm install -g component uglifyjs + component install -.PHONY : clean all +clean : + rm dist/* + +.PHONY : get-deps all clean diff --git a/README.md b/README.md index 38b76740..a1e57100 100644 --- a/README.md +++ b/README.md @@ -1,206 +1,116 @@ +[![Build status](https://travis-ci.org/jbeard4/SCION.svg?branch=2.0.0-w3c-ecma)](https://travis-ci.org/jbeard4/SCION) + +SCION 2.0 is a lightweight SCXML-to-JavaScript compiler that targets the [SCION-CORE](http://github.com/jbeard4/SCION-CORE) Statecharts interpreter. It currently supports node.js and the browser, and will later support Rhino and other JavaScript environments. + # Overview -SCION provides an implementation of the [W3C SCXML draft specification](http://www.w3.org/TR/scxml/) in JavaScript. SCXML provides a declarative markup for Statecharts, a powerful modelling language for developing **complex, timed, event-driven, state-based systems**, and can offer elegant solutions to many problems faced in development of JavaScript-based applications across various domains. In the browser, SCION can be used to facilitate the development of **rich, web-based user interfaces** with complex behavioural requirements. On the server, SCION can be used to manage **asynchronous control flow**. +SCION 2.0 provides an implementation of the [W3C SCXML draft specification](http://www.w3.org/TR/scxml/) in JavaScript. SCXML provides a declarative markup for Statecharts, a powerful modelling language for developing **complex, timed, event-driven, state-based systems**, and can offer elegant solutions to many problems faced in development of JavaScript-based applications across various domains. In the browser, SCXML can be used to facilitate the development of **rich, web-based user interfaces** with complex behavioural requirements. On the server, SCXML can be used to manage **asynchronous control flow**. -Here are some reasons you might choose to use SCION: +# Installation -- Liberally licensed (Apache 2.0) -- Standards-based (SCXML) -- **Small**: Only 8kb minified and gzipped. -- **Robust**: automatically tested using a [custom testing framework for SCXML implementations](https://github.com/jbeard4/scxml-test-framework). ![Travis-CI build status](https://travis-ci.org/jbeard4/SCION.svg?branch=master) -- Maximally **portable** across JavaScript environments: works well in IE6+, modern browsers, node.js, rhino, and various JavaScript shells. -- **Easy** to learn and use: now with a simple, unified API for Rhino, Node.js and the browser. -- Aggressively **optimized** for performance, memory usage and payload size. More information on this will be forthcoming. +In node.js, install SCION 2.0 via npm: -# Quickstart and Simple Use Case + npm install scxml -Let's start with the simple example of drag-and-drop behaviour in the browser. You can run this demo live [here](http://jbeard4.github.com/SCION/demos/drag-and-drop/drag-and-drop.html), or on jsfiddle [here](http://jsfiddle.net/jbeard4/mjm72/). +In the browser, add the following script tags to your web page: -An entity that can be dragged has two states: idle and dragging. If the entity is in an idle state, and it receives a mousedown event, then it starts dragging. While dragging, if it receives a mousemove event, then it changes its position. Also while dragging, when it receives a mouseup event, it returns to the idle state. +```html + + + +``` -This natural-language description of behaviour can be described using the following simple state machine: +Note that SCION 2.0 assumes the presence of jQuery to handle cross-browser XMLHTTPRequest, however an alternative Ajax library could instead be used. This is set up in the following way: -![Drag and Drop](http://jbeard4.github.com/SCION/img/drag_and_drop.png) +```javascript + //perform this setup once, before SCION is used + scxml.ext.platformModule.platform.ajax = { + get : function(url,successCallback,dataType){ + //call your preferred Ajax library here to do HTTP GET + //if dataType is 'xml', the Ajax response must be parsed as DOM + }, + post : function(url, data, successCallback, dataType){ + //call your preferred Ajax library here to do HTTP POST + } + }; +``` -This state machine could be written in SCXML as follows: +Support is currently being added for Rhino. -```xml - +# API - - - +SCION 2.0 uses [SCION-CORE](http://github.com/jbeard4/SCION-CORE) as its Statecharts engine. SCION 2.0 first compiles the SCXML document to a JavaScript object model. The "model" is then used to instantiate a SCION Statecharts interpreter. - - - - +Here is an example of a typical usage: - -``` +```javascript + scxml.urlToModel(url,function(err, model){ -One can add action code in order to script an HTML DOM element, so as to change its position on mousemove events: + if(err) throw err; -```html - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -``` + //you can inspect the generated code if you like using JavaScript's Function.prototype.toString + console.log(model.toString()); -There are then **4 steps** that must be performed to go from an SCXML document to a working state machine instance that is consuming DOM events and scripting web content: + //instantiate the interpreter + var statechart1 = new scxml.scion.Statechart(model); -1. Get the SCXML document, and convert it to a SCXML "model" object for easier interpretation. -2. Use the SCXML model object to instantiate the SCXML interpreter. -3. Connect relevant event listeners to the SCXML interpreter. -4. Call the `start` method on the SCXML interpreter to start execution of the statechart. + //you can instantiate a second interpreter using the same model + var statechart2 = new scxml.scion.Statechart(model); + //start the interpreter + var initialConfiguration = statechart1.start(); -```html - - - - - - - - -
- - + //send events + statechart1.gen({name : 'foo', data : 'bar'}); + }); ``` -# API +In node, you can also use `require`. + +```javascript +var model = require('./path/to/foo.scxml'); +//etc... +``` + +Note that this will only work if the SCXML ` - - -``` - -SCION is also available as an AMD module, so if you are using RequireJS in your page, then you can do the following: - -```html - - - - -``` - -Note that SCION assumes the presence of jQuery to handle cross-browser XMLHTTPRequest, however an alternative Ajax library could instead be used. This is set up in the following way: - -```javascript - //perform this setup once, before SCION is used - scion.platformModule.platform.ajax = { - get : function(url,successCallback,dataType){ - //call your preferred Ajax library here to do HTTP GET - //if dataType is 'xml', the Ajax response must be parsed as DOM - }, - post : function(url, data, successCallback, dataType){ - //call your preferred Ajax library here to do HTTP POST - } - }; -``` - -SCION does not currently use `platform.ajax.post`, but there are plans to use this API in the future to implement SCXML ``. - -# Usage in Node.js - -Install SCION via npm: - - npm install scion - -Or, to get the latest and greatest from Github master: - - cd node_modules/ #cd into wherever your node_modules directory lives - git clone --recursive git://github.com/jbeard4/SCION.git scion #this fetches SCION and its git submodule dependencies into directory "scion" - cd scion && npm install #this fetches npm package dependencies - -For example usage see [SCION Demos](https://github.com/jbeard4/scion-demos). - -# Usage in Rhino - -Get it with git: - - git clone --recursive git://github.com/jbeard4/SCION.git - -Rhino 1.7R3 supports CommonJS modules, so SCION can be used as follows: - -```bash -#just put SCION/lib on your modules path -rhino -modules path/to/SCION/lib -main path/to/your/script.js -``` - - - -# Using SCION from Other Languages - -Because SCION is implemented in ECMAScript, it has been fairly easy to embed in other programming environments, including: - -* Java : [SCION-Java](https://github.com/jbeard4/SCION-Java) -* C# : [SCION.NET](https://github.com/jbeard4/SCION.NET) -* Python : [pySCION](https://github.com/jbeard4/pySCION) - -This guide describes how to embed SCION in other JavaScript environments: [Embedding SCION](https://github.com/jbeard4/SCION/wiki/Embedding-SCION) - -# SCION Semantics - -SCION takes many ideas from the SCXML standard. In particular, it reuses the syntax of SCXML, but changes some of the semantics. - -* If you're already familiar with SCXML, and want a high-level overview of similarities and differences between SCION and SCXML, start here: [SCION vs. SCXML Comparison](https://github.com/jbeard4/SCION/wiki/SCION-vs.-SCXML-Comparison). -* If you're a specification implementer or a semanticist, and would like the details of the SCION semantics, start here: [SCION Semantics](https://github.com/jbeard4/SCION/wiki/Scion-Semantics). - - - # Support [Mailing list](https://groups.google.com/group/scion-dev) - - - -# Other Resources - -* [Custom Actions](https://github.com/jbeard4/SCION/wiki/Custom-Action-Tags) -* [Action Tag Scripting APIs](https://github.com/jbeard4/SCION/wiki/Action-Tag-Scripting-API) -* [SCION Demos](https://github.com/jbeard4/scion-demos) -* [Table describing which SCXML tags are supported](https://github.com/jbeard4/SCION/wiki/SCION-Implementation-Status) -* [Project Background](https://github.com/jbeard4/SCION/wiki/Project-Background) -* [Jacob Beard's Master thesis](http://digitool.library.mcgill.ca/R/-?func=dbin-jump-full&object_id=116899&silo_library=GEN01) - - - -# Related Projects - -* [SCXML Test Framework](https://github.com/jbeard4/scxml-test-framework) -* [SCION-Java](https://github.com/jbeard4/SCION-Java) -* [SCXML Commons](http://commons.apache.org/scxml/) -* [PySCXML](http://code.google.com/p/pyscxml/) diff --git a/bin/repl.js b/bin/repl.js new file mode 100755 index 00000000..de98a979 --- /dev/null +++ b/bin/repl.js @@ -0,0 +1,40 @@ +#!/usr/bin/env node +//A simple scxml interactive simulation environment +var scxml = require('..'), + repl = require('repl'); + +var pathToScxml = process.argv[2]; + +if(!pathToScxml){ + console.log('Usage: scxml foo.scxml'); + process.exit(1); +} + +//1 - 2. get the xml file and convert it to jsonml +scxml.pathToModel(pathToScxml,function(err,model){ + + if(err){ + throw err; + } + + //Use the statechart object model to instantiate an instance of the statechart interpreter. Optionally, we can pass to the construct an object to be used as the context object (the 'this' object) in script evaluation. Lots of other parameters are available. + var interpreter = new scxml.scion.Statechart(model); + + interpreter.start(); + + console.log(interpreter.getConfiguration()); + + var parseRE = /\((.*)\n\)/; + + function processEvent(cmd,dontKnow,alsoDontKnow,callback){ + var e = cmd.match(parseRE)[1]; + interpreter.gen({name : e}); + var conf = interpreter.getConfiguration(); + callback(null,conf); + } + + //start + repl.start('#',process.stdin,processEvent); + +}); + diff --git a/component.json b/component.json new file mode 100644 index 00000000..539282ef --- /dev/null +++ b/component.json @@ -0,0 +1,33 @@ +{ + "name": "scxml", + "repo": "jbeard4/scxml.js", + "description": "An implementation of SCXML in portable ECMAScript.", + "version": "0.0.4", + "keywords": [ + "scxml", + "statecharts", + "w3c", + "javascript" + ], + "dependencies": { + "jbeard4/scion-ng": "*", + "isaacs/sax-js": "*" + }, + "development": {}, + "license": "Apache-2.0", + "main" : "lib/runtime/platform-bootstrap/browser/index.js", + "scripts": [ + "lib/compiler/scxml-to-scjson.js", + "lib/compiler/scjson-to-module.js", + "lib/runtime/transform/util.js", + "lib/runtime/transform/inline-srcs.js", + "lib/runtime/platform-bootstrap/browser/path.js", + "lib/runtime/platform-bootstrap/browser/url.js", + "lib/runtime/platform-bootstrap/browser/platform.js", + "lib/runtime/platform-bootstrap/browser/index.js", + "lib/runtime/platform-bootstrap/platform.js", + "lib/runtime/document-string-to-model.js", + "lib/runtime/facade.js" + ] +} + diff --git a/dist/scxml.js b/dist/scxml.js new file mode 100644 index 00000000..b77116e1 --- /dev/null +++ b/dist/scxml.js @@ -0,0 +1,3796 @@ +;(function(){ + +/** + * Require the given path. + * + * @param {String} path + * @return {Object} exports + * @api public + */ + +function require(path, parent, orig) { + var resolved = require.resolve(path); + + // lookup failed + if (null == resolved) { + orig = orig || path; + parent = parent || 'root'; + var err = new Error('Failed to require "' + orig + '" from "' + parent + '"'); + err.path = orig; + err.parent = parent; + err.require = true; + throw err; + } + + var module = require.modules[resolved]; + + // perform real require() + // by invoking the module's + // registered function + if (!module.exports) { + module.exports = {}; + module.client = module.component = true; + module.call(this, module.exports, require.relative(resolved), module); + } + + return module.exports; +} + +/** + * Registered modules. + */ + +require.modules = {}; + +/** + * Registered aliases. + */ + +require.aliases = {}; + +/** + * Resolve `path`. + * + * Lookup: + * + * - PATH/index.js + * - PATH.js + * - PATH + * + * @param {String} path + * @return {String} path or null + * @api private + */ + +require.resolve = function(path) { + if (path.charAt(0) === '/') path = path.slice(1); + var index = path + '/index.js'; + + var paths = [ + path, + path + '.js', + path + '.json', + path + '/index.js', + path + '/index.json' + ]; + + for (var i = 0; i < paths.length; i++) { + var path = paths[i]; + if (require.modules.hasOwnProperty(path)) return path; + } + + if (require.aliases.hasOwnProperty(index)) { + return require.aliases[index]; + } +}; + +/** + * Normalize `path` relative to the current path. + * + * @param {String} curr + * @param {String} path + * @return {String} + * @api private + */ + +require.normalize = function(curr, path) { + var segs = []; + + if ('.' != path.charAt(0)) return path; + + curr = curr.split('/'); + path = path.split('/'); + + for (var i = 0; i < path.length; ++i) { + if ('..' == path[i]) { + curr.pop(); + } else if ('.' != path[i] && '' != path[i]) { + segs.push(path[i]); + } + } + + return curr.concat(segs).join('/'); +}; + +/** + * Register module at `path` with callback `definition`. + * + * @param {String} path + * @param {Function} definition + * @api private + */ + +require.register = function(path, definition) { + require.modules[path] = definition; +}; + +/** + * Alias a module definition. + * + * @param {String} from + * @param {String} to + * @api private + */ + +require.alias = function(from, to) { + if (!require.modules.hasOwnProperty(from)) { + throw new Error('Failed to alias "' + from + '", it does not exist'); + } + require.aliases[to] = from; +}; + +/** + * Return a require function relative to the `parent` path. + * + * @param {String} parent + * @return {Function} + * @api private + */ + +require.relative = function(parent) { + var p = require.normalize(parent, '..'); + + /** + * lastIndexOf helper. + */ + + function lastIndexOf(arr, obj) { + var i = arr.length; + while (i--) { + if (arr[i] === obj) return i; + } + return -1; + } + + /** + * The relative require() itself. + */ + + function localRequire(path) { + var resolved = localRequire.resolve(path); + return require(resolved, parent, path); + } + + /** + * Resolve relative to the parent. + */ + + localRequire.resolve = function(path) { + var c = path.charAt(0); + if ('/' == c) return path.slice(1); + if ('.' == c) return require.normalize(p, path); + + // resolve deps by returning + // the dep in the nearest "deps" + // directory + var segs = parent.split('/'); + var i = lastIndexOf(segs, 'deps') + 1; + if (!i) i = 0; + path = segs.slice(0, i + 1).join('/') + '/deps/' + path; + return path; + }; + + /** + * Check if module is defined at `path`. + */ + + localRequire.exists = function(path) { + return require.modules.hasOwnProperty(localRequire.resolve(path)); + }; + + return localRequire; +}; +require.register("jbeard4-scion-ng/lib/scion.js", function(exports, require, module){ +// 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. + +//UMD boilerplate - https://github.com/umdjs/umd/blob/master/returnExports.js +(function (root, factory) { + if (typeof exports === 'object') { + // Node. Does not work with strict CommonJS, but + // only CommonJS-like enviroments that support module.exports, + // like Node. + module.exports = factory(); + } else if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define(factory); + } else { + // Browser globals (root is window) + root.SCION = factory(); + } +}(this, function () { + + "use strict"; + + var STATE_TYPES = { + BASIC: 0, + COMPOSITE: 1, + PARALLEL: 2, + HISTORY: 3, + INITIAL: 4, + FINAL: 5 + }; + + function initializeModel(rootState){ + var transitions = [], idToStateMap = {}, 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; + + var count = idCount[type]++; + return '$generated-' + type + '-' + count; + } + + function wrapInFakeRootState(state){ + return { + states : [ + { + type : 'initial', + transitions : [{ + target : state + }] + }, + state + ] + }; + } + + function traverse(ancestors,state){ + + //add to global transition and state id caches + if(state.transitions) transitions.push.apply(transitions,state.transitions); + + //populate state id map + if(state.id){ + if(idToStateMap[state.id]) throw new Error('Redefinition of state id ' + state.id); + + idToStateMap[state.id] = state; + } + + //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]; + + //add some information to transitions + state.transitions = state.transitions || []; + state.transitions.forEach(function(transition){ + transition.documentOrder = documentOrder++; + transition.source = state; + }); + + var t2 = traverse.bind(null,[state].concat(ancestors)); + + //recursive step + if(state.states) state.states.forEach(t2); + + //setup fast state type + switch(state.type){ + case 'parallel': + state.typeEnum = STATE_TYPES.PARALLEL; + break; + case 'initial' : + state.typeEnum = STATE_TYPES.INITIAL; + break; + case 'history' : + state.typeEnum = STATE_TYPES.HISTORY; + break; + case 'final' : + state.typeEnum = STATE_TYPES.FINAL; + break; + case 'state' : + case 'scxml' : + if(state.states && state.states.length){ + state.typeEnum = STATE_TYPES.COMPOSITE; + }else{ + state.typeEnum = STATE_TYPES.BASIC; + } + 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(typeof state.initial === 'string'){ + //dereference him from his + initialChildren = state.states.filter(function(child){ + return child.id === state.initial; + }); + if(initialChildren.length){ + state.initialRef = initialChildren[0]; + } + }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]; + } + + if(!state.initialRef) throw new Error('Unable to locate initial state for composite state: ' + state.id); + } + + //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[0]; + } + + //now it's safe to fill in fake state ids + if(!state.id){ + state.id = generateId(state.type); + idToStateMap[state.id] = state; + } + + //normalize onEntry/onExit, which can be single fn or array + ['onEntry','onExit'].forEach(function(prop){ + if(typeof state[prop] === 'function'){ + state[prop] = [state[prop]]; + } + }); + } + + //TODO: convert events to regular expressions in advance + + function connectTransitionGraph(){ + //normalize as with onEntry/onExit + transitions.forEach(function(t){ + if(typeof t.onTransition === 'function'){ + t.onTransition = [t.onTransition]; + } + }); + + transitions.forEach(function(t){ + //normalize "event" attribute into "events" attribute + if(t.event){ + t.events = t.event.trim().split(/ +/); + } + }); + + //hook up targets + transitions.forEach(function(t){ + if(t.targets || (typeof t.target === 'undefined')) return; //targets have already been set up + + if(typeof t.target === 'string'){ + //console.log('here1'); + var target = idToStateMap[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)){ + //console.log('here2'); + t.targets = t.target.map(function(target){ + if(typeof target === 'string'){ + target = idToStateMap[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 + transitions.forEach(function(t){ + 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); + //console.log('scope',t.source.id,t.scope.id,t.targets); + }); + } + + 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.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 transition.source; + }else if(transitionIsReallyInternal){ + return transition.source; + }else{ + return transition.lcca; + } + } + + function getLCCA(s1, s2) { + //console.log('getLCCA',s1, s2); + var commonAncestors = []; + s1.ancestors.forEach(function(anc){ + //console.log('s1.id',s1.id,'anc',anc.id,'anc.typeEnum',anc.typeEnum,'s2.id',s2.id); + if(anc.typeEnum === STATE_TYPES.COMPOSITE && + anc.descendants.indexOf(s2) > -1){ + commonAncestors.push(anc); + } + }); + //console.log('commonAncestors',s1.id,s2.id,commonAncestors.map(function(s){return s.id;})); + 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 + 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(); + + return fakeRootState; + } + + + /* begin ArraySet */ + + /** @constructor */ + function ArraySet(l) { + l = l || []; + this.o = []; + + l.forEach(function(x){ + this.add(x); + },this); + } + + ArraySet.prototype = { + + add : function(x) { + if (!this.contains(x)) return this.o.push(x); + }, + + remove : function(x) { + var i = this.o.indexOf(x); + if(i === -1){ + return false; + }else{ + this.o.splice(i, 1); + } + return true; + }, + + union : function(l) { + l = l.iter ? l.iter() : l; + l.forEach(function(x){ + this.add(x); + },this); + return this; + }, + + difference : function(l) { + l = l.iter ? l.iter() : l; + + l.forEach(function(x){ + this.remove(x); + },this); + return this; + }, + + contains : function(x) { + return this.o.indexOf(x) > -1; + }, + + iter : function() { + return this.o; + }, + + isEmpty : function() { + return !this.o.length; + }, + + equals : function(s2) { + var l2 = s2.iter(); + var l1 = this.o; + + return l1.every(function(x){ + return l2.indexOf(x) > -1; + }) && l2.every(function(x){ + return l1.indexOf(x) > -1; + }); + }, + + toString : function() { + return "Set(" + this.o.toString() + ")"; + } + }; + + var scxmlPrefixTransitionSelector = (function(){ + + 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, event) { + return event && event.name && + (t.events.indexOf("*") > -1 ? + true : + t.events.filter(function(tEvent){ + return retrieveEventRe(tEvent).test(event.name); + }).length); + + } + + return function(state, event, evaluator) { + return state.transitions.filter(function(t){ + return (!t.events || nameMatch(t,event)) && (!t.cond || evaluator(t.cond)); + }); + }; + })(); + + //model accessor functions + var query = { + getAncestors: function(s, root) { + var ancestors, index, state; + index = s.ancestors.indexOf(root); + if (index > -1) { + return s.ancestors.slice(0, index); + } else { + return s.ancestors; + } + }, + /** @this {model} */ + getAncestorsOrSelf: function(s, root) { + return [s].concat(this.getAncestors(s, root)); + }, + getDescendantsOrSelf: function(s) { + return [s].concat(s.descendants); + }, + /** @this {model} */ + 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).typeEnum === STATE_TYPES.PARALLEL; + }, + /** @this {model} */ + isAncestrallyRelatedTo: function(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; + }, + /** @this {model} */ + getLCA: function(s1, s2) { + var commonAncestors = this.getAncestors(s1).filter(function(a){ + return a.descendants.indexOf(s2) > -1; + },this); + return commonAncestors[0]; + } + }; + + //priority comparison functions + function getTransitionWithHigherSourceChildPriority(_arg) { + var t1 = _arg[0], t2 = _arg[1]; + //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 initializeModelGeneratorFn(modelFn, opts, interpreter){ + + opts.x = opts.x || {}; + + var args = ['x','sessionid','name','ioprocessors']. + map(function(name){ return opts.x['_' + name] = opts[name]; }). + concat(interpreter.isIn.bind(interpreter)); + + //the "model" might be a function, so we lazy-init him here to get the root state + return modelFn.apply(interpreter,args); + } + + /** @const */ + var printTrace = false; + + /** @constructor */ + function BaseInterpreter(modelOrFnGenerator, opts){ + + this._scriptingContext = opts.interpreterScriptingContext || (opts.InterpreterScriptingContext ? new opts.InterpreterScriptingContext(this) : {}); + + var model = typeof modelOrFnGenerator === 'function' ? + initializeModelGeneratorFn(modelOrFnGenerator, opts, this) : + modelOrFnGenerator; + + this._model = initializeModel(model); + + //console.log(require('util').inspect(this._model,false,4)); + + this.opts = opts || {}; + + this.opts.console = opts.console || (typeof console === 'undefined' ? {log : function(){}} : 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._scriptingContext.log = this._scriptingContext.log || this.opts.console.log; //set up default scripting context log function + + this._configuration = new this.opts.Set(); + this._historyValue = {}; + this._internalEventQueue = []; + this._isInFinalState = false; + + //SCXML system variables: + this._x = { + _sessionId : opts.sessionId || null, + _name : model.name || opts.name || null, + _ioprocessors : opts.ioprocessors || null + }; + + this._listeners = []; + } + + BaseInterpreter.prototype = { + + /** @expose */ + start : function() { + //perform big step without events to take all default transitions and reach stable initial state + if (printTrace) this.opts.console.log("performing initial big step"); + + //We effectively need to figure out states to enter here to populate initial config. assuming root is compound state makes this simple. + //but if we want it to be parallel, then this becomes more complex. so when initializing the model, we add a 'fake' root state, which + //makes the following operation safe. + this._configuration.add(this._model.initialRef); + + this._performBigStep(); + return this.getConfiguration(); + }, + + /** @expose */ + getConfiguration : function() { + return this._configuration.iter().map(function(s){return s.id;}); + }, + + /** @expose */ + getFullConfiguration : function() { + return this._configuration.iter(). + map(function(s){ return [s].concat(query.getAncestors(s));},this). + reduce(function(a,b){return a.concat(b);},[]). //flatten + map(function(s){return s.id;}). + reduce(function(a,b){return a.indexOf(b) > -1 ? a : a.concat(b);},[]); //uniq + }, + + + /** @expose */ + isIn : function(stateName) { + return this.getFullConfiguration().indexOf(stateName) > -1; + }, + + /** @expose */ + isFinal : function(stateName) { + return this._isInFinalState; + }, + + /** @private */ + _performBigStep : function(e) { + if (e) this._internalEventQueue.push(e); + var keepGoing = true; + while (keepGoing) { + var currentEvent = this._internalEventQueue.shift() || null; + + var selectedTransitions = this._performSmallStep(currentEvent); + keepGoing = !selectedTransitions.isEmpty(); + } + this._isInFinalState = this._configuration.iter().every(function(s){ return s.typeEnum === STATE_TYPES.FINAL; }); + }, + + /** @private */ + _performSmallStep : function(currentEvent) { + + if (printTrace) this.opts.console.log("selecting transitions with currentEvent: ", currentEvent); + + var selectedTransitions = this._selectTransitions(currentEvent); + + if (printTrace) this.opts.console.log("selected transitions: ", selectedTransitions); + + if (!selectedTransitions.isEmpty()) { + + if (printTrace) this.opts.console.log("sorted transitions: ", selectedTransitions); + + //we only want to enter and exit states from transitions with targets + //filter out targetless transitions here - we will only use these to execute transition actions + var selectedTransitionsWithTargets = new this.opts.Set(selectedTransitions.iter().filter(function(t){return t.targets;})); + + var exitedTuple = this._getStatesExited(selectedTransitionsWithTargets), + basicStatesExited = exitedTuple[0], + statesExited = exitedTuple[1]; + + var enteredTuple = this._getStatesEntered(selectedTransitionsWithTargets), + basicStatesEntered = enteredTuple[0], + statesEntered = enteredTuple[1]; + + if (printTrace) this.opts.console.log("basicStatesExited ", basicStatesExited); + if (printTrace) this.opts.console.log("basicStatesEntered ", basicStatesEntered); + if (printTrace) this.opts.console.log("statesExited ", statesExited); + if (printTrace) this.opts.console.log("statesEntered ", statesEntered); + + var eventsToAddToInnerQueue = new this.opts.Set(); + + //update history states + if (printTrace) this.opts.console.log("executing state exit actions"); + + var evaluateAction = this._evaluateAction.bind(this, currentEvent); //create helper fn that actions can call later on + + statesExited.forEach(function(state){ + + if (printTrace || this.opts.logStatesEnteredAndExited) this.opts.console.log("exiting ", state.id); + + //invoke listeners + this._listeners.forEach(function(l){ + if(l.onExit) l.onExit(state.id); + }); + + if(state.onExit !== undefined) state.onExit.forEach(evaluateAction); + + var f; + if (state.historyRef) { + if (state.historyRef.isDeep) { + f = function(s0) { + return s0.typeEnum === STATE_TYPES.BASIC && state.descendants.indexOf(s0) > -1; + }; + } else { + f = function(s0) { + return s0.parent === state; + }; + } + //update history + this._historyValue[state.historyRef.id] = statesExited.filter(f); + } + },this); + + + // -> Concurrency: Number of transitions: Multiple + // -> Concurrency: Order of transitions: Explicitly defined + var sortedTransitions = selectedTransitions.iter().sort(function(t1, t2) { + return t1.documentOrder - t2.documentOrder; + }); + + if (printTrace) this.opts.console.log("executing transitition actions"); + + + sortedTransitions.forEach(function(transition){ + + var targetIds = transition.targets && transition.targets.map(function(target){return target.id;}); + + this._listeners.forEach(function(l){ + if(l.onTransition) l.onTransition(transition.source.id,targetIds); + }); + + if(transition.onTransition !== undefined) transition.onTransition.forEach(evaluateAction); + },this); + + if (printTrace) this.opts.console.log("executing state enter actions"); + + statesEntered.forEach(function(state){ + + if (printTrace || this.opts.logStatesEnteredAndExited) this.opts.console.log("entering", state.id); + + this._listeners.forEach(function(l){ + if(l.onEntry) l.onEntry(state.id); + }); + + if(state.onEntry !== undefined) state.onEntry.forEach(evaluateAction); + },this); + + if (printTrace) this.opts.console.log("updating configuration "); + if (printTrace) this.opts.console.log("old configuration ", this._configuration); + + //update configuration by removing basic states exited, and adding basic states entered + this._configuration.difference(basicStatesExited); + this._configuration.union(basicStatesEntered); + + + if (printTrace) this.opts.console.log("new configuration ", this._configuration); + + //add set of generated events to the innerEventQueue -> Event Lifelines: Next small-step + if (!eventsToAddToInnerQueue.isEmpty()) { + if (printTrace) this.opts.console.log("adding triggered events to inner queue ", eventsToAddToInnerQueue); + this._internalEventQueue.push(eventsToAddToInnerQueue); + } + + } + + //if selectedTransitions is empty, we have reached a stable state, and the big-step will stop, otherwise will continue -> Maximality: Take-Many + return selectedTransitions; + }, + + /** @private */ + _evaluateAction : function(currentEvent, actionRef) { + return actionRef.call(this._scriptingContext, currentEvent); //SCXML system variables + }, + + /** @private */ + _getStatesExited : function(transitions) { + var statesExited = new this.opts.Set(); + var basicStatesExited = new this.opts.Set(); + + //States exited are defined to be active states that are + //descendants of the scope of each priority-enabled transition. + //Here, we iterate through the transitions, and collect states + //that match this condition. + transitions.iter().forEach(function(transition){ + var scope = transition.scope, + desc = scope.descendants; + + //For each state in the configuration + //is that state a descendant of the transition scope? + //Store ancestors of that state up to but not including the scope. + this._configuration.iter().forEach(function(state){ + if(desc.indexOf(state) > -1){ + basicStatesExited.add(state); + statesExited.add(state); + query.getAncestors(state,scope).forEach(function(anc){ + statesExited.add(anc); + }); + } + },this); + },this); + + var sortedStatesExited = statesExited.iter().sort(function(s1, s2) { + return s2.depth - s1.depth; + }); + return [basicStatesExited, sortedStatesExited]; + }, + + /** @private */ + _getStatesEntered : function(transitions) { + + var o = { + statesToEnter : new this.opts.Set(), + basicStatesToEnter : new this.opts.Set(), + statesProcessed : new this.opts.Set(), + statesToProcess : [] + }; + + //do the initial setup + transitions.iter().forEach(function(transition){ + transition.targets.forEach(function(target){ + this._addStateAndAncestors(target,transition.scope,o); + },this); + },this); + + //loop and add states until there are no more to add (we reach a stable state) + var s; + /*jsl:ignore*/ + while(s = o.statesToProcess.pop()){ + /*jsl:end*/ + this._addStateAndDescendants(s,o); + } + + //sort based on depth + var sortedStatesEntered = o.statesToEnter.iter().sort(function(s1, s2) { + return s1.depth - s2.depth; + }); + + return [o.basicStatesToEnter, sortedStatesEntered]; + }, + + /** @private */ + _addStateAndAncestors : function(target,scope,o){ + + //process each target + this._addStateAndDescendants(target,o); + + //and process ancestors of targets up to the scope, but according to special rules + query.getAncestors(target,scope).forEach(function(s){ + + if (s.typeEnum === STATE_TYPES.COMPOSITE) { + //just add him to statesToEnter, and declare him processed + //this is to prevent adding his initial state later on + o.statesToEnter.add(s); + + o.statesProcessed.add(s); + }else{ + //everything else can just be passed through as normal + this._addStateAndDescendants(s,o); + } + },this); + }, + + /** @private */ + _addStateAndDescendants : function(s,o){ + + if(o.statesProcessed.contains(s)) return; + + if (s.typeEnum === STATE_TYPES.HISTORY) { + if (s.id in this._historyValue) { + this._historyValue[s.id].forEach(function(stateFromHistory){ + this._addStateAndAncestors(stateFromHistory,s.parent,o); + },this); + } else { + o.statesToEnter.add(s); + o.basicStatesToEnter.add(s); + } + } else { + o.statesToEnter.add(s); + + if (s.typeEnum === STATE_TYPES.PARALLEL) { + o.statesToProcess.push.apply(o.statesToProcess, + s.states.filter(function(s){return s.typeEnum !== STATE_TYPES.HISTORY;})); + } else if (s.typeEnum === STATE_TYPES.COMPOSITE) { + o.statesToProcess.push(s.initialRef); + } else if (s.typeEnum === STATE_TYPES.INITIAL || s.typeEnum === STATE_TYPES.BASIC || s.typeEnum === STATE_TYPES.FINAL) { + o.basicStatesToEnter.add(s); + } + } + + o.statesProcessed.add(s); + }, + + /** @private */ + _selectTransitions : function(currentEvent) { + if (this.opts.onlySelectFromBasicStates) { + var states = this._configuration.iter(); + } else { + var statesAndParents = new this.opts.Set; + + //get full configuration, unordered + //this means we may select transitions from parents before states + + this._configuration.iter().forEach(function(basicState){ + statesAndParents.add(basicState); + query.getAncestors(basicState).forEach(function(ancestor){ + statesAndParents.add(ancestor); + }); + },this); + + states = statesAndParents.iter(); + } + + + + var usePrefixMatchingAlgorithm = currentEvent && currentEvent.name && currentEvent.name.search("."); + + var transitionSelector = usePrefixMatchingAlgorithm ? scxmlPrefixTransitionSelector : this.opts.transitionSelector; + var enabledTransitions = new this.opts.Set(); + + var e = this._evaluateAction.bind(this,currentEvent); + + states.forEach(function(state){ + transitionSelector(state,currentEvent,e).forEach(function(t){ + enabledTransitions.add(t); + }); + }); + + var priorityEnabledTransitions = this._selectPriorityEnabledTransitions(enabledTransitions); + + if (printTrace) this.opts.console.log("priorityEnabledTransitions", priorityEnabledTransitions); + + return priorityEnabledTransitions; + }, + + /** @private */ + _selectPriorityEnabledTransitions : function(enabledTransitions) { + var priorityEnabledTransitions = new this.opts.Set(); + + var tuple = this._getInconsistentTransitions(enabledTransitions), + consistentTransitions = tuple[0], + inconsistentTransitionsPairs = tuple[1]; + + priorityEnabledTransitions.union(consistentTransitions); + + if (printTrace) this.opts.console.log("enabledTransitions", enabledTransitions); + if (printTrace) this.opts.console.log("consistentTransitions", consistentTransitions); + if (printTrace) this.opts.console.log("inconsistentTransitionsPairs", inconsistentTransitionsPairs); + if (printTrace) this.opts.console.log("priorityEnabledTransitions", priorityEnabledTransitions); + + while (!inconsistentTransitionsPairs.isEmpty()) { + enabledTransitions = new this.opts.Set( + inconsistentTransitionsPairs.iter().map(function(t){return this.opts.priorityComparisonFn(t);},this)); + + tuple = this._getInconsistentTransitions(enabledTransitions); + consistentTransitions = tuple[0]; + inconsistentTransitionsPairs = tuple[1]; + + priorityEnabledTransitions.union(consistentTransitions); + + if (printTrace) this.opts.console.log("enabledTransitions", enabledTransitions); + if (printTrace) this.opts.console.log("consistentTransitions", consistentTransitions); + if (printTrace) this.opts.console.log("inconsistentTransitionsPairs", inconsistentTransitionsPairs); + if (printTrace) this.opts.console.log("priorityEnabledTransitions", priorityEnabledTransitions); + + } + return priorityEnabledTransitions; + }, + + /** @private */ + _getInconsistentTransitions : function(transitions) { + var allInconsistentTransitions = new this.opts.Set(); + var inconsistentTransitionsPairs = new this.opts.Set(); + var transitionList = transitions.iter(); + + if (printTrace) this.opts.console.log("transitions", transitionList); + + for(var i = 0; i < transitionList.length; i++){ + for(var j = i+1; j < transitionList.length; j++){ + var t1 = transitionList[i]; + var t2 = transitionList[j]; + if (this._conflicts(t1, t2)) { + allInconsistentTransitions.add(t1); + allInconsistentTransitions.add(t2); + inconsistentTransitionsPairs.add([t1, t2]); + } + } + } + + var consistentTransitions = transitions.difference(allInconsistentTransitions); + return [consistentTransitions, inconsistentTransitionsPairs]; + }, + + /** @private */ + _conflicts : function(t1, t2) { + return !this._isArenaOrthogonal(t1, t2); + }, + + /** @private */ + _isArenaOrthogonal : function(t1, t2) { + + if (printTrace) this.opts.console.log("transition scopes", t1.scope, t2.scope); + + var isOrthogonal = query.isOrthogonalTo(t1.scope, t2.scope); + + if (printTrace) this.opts.console.log("transition scopes are orthogonal?", isOrthogonal); + + return isOrthogonal; + }, + + + /* + registerListener provides a generic mechanism to subscribe to state change notifications. + Can be used for logging and debugging. For example, can attache a logger that simply logs the state changes. + Or can attach a network debugging client that sends state change notifications to a debugging server. + + listener is of the form: + { + onEntry : function(stateId){}, + onExit : function(stateId){}, + onTransition : function(sourceStateId,targetStatesIds[]){} + } + */ + //TODO: refactor this to be event emitter? + + /** @expose */ + registerListener : function(listener){ + return this._listeners.push(listener); + }, + + /** @expose */ + unregisterListener : function(listener){ + return this._listeners.splice(this._listeners.indexOf(listener),1); + }, + + /** @expose */ + getAllTransitionEvents : function(){ + var events = {}; + function getEvents(state){ + + if(state.transitions){ + state.transitions.forEach(function(transition){ + events[transition.event] = true; + }); + } + + if(state.states) state.states.forEach(getEvents); + } + getEvents(this._model); + + return Object.keys(events); + } + + }; + + /** + * @constructor + * @extends BaseInterpreter + */ + function Statechart(model, opts) { + opts = opts || {}; + + opts.InterpreterScriptingContext = opts.InterpreterScriptingContext || InterpreterScriptingContext; + + this._isStepping = false; + this._externalEventQueue = []; + + BaseInterpreter.call(this,model,opts); //call super constructor + } + + function beget(o){ + function F(){} + F.prototype = o; + return new F(); + } + + //Statechart.prototype = Object.create(BaseInterpreter.prototype); + //would like to use Object.create here, but not portable, but it's too complicated to use portably + Statechart.prototype = beget(BaseInterpreter.prototype); + + /** @expose */ + Statechart.prototype.gen = function(evtObjOrName,optionalData) { + + var e; + switch(typeof evtObjOrName){ + case 'string': + e = {name : evtObjOrName, data : optionalData}; + break; + case 'object': + if(typeof evtObjOrName.name === 'string'){ + e = evtObjOrName; + }else{ + throw new Error('Event object must have "name" property of type string.'); + } + break; + default: + throw new Error('First argument to gen must be a string or object.'); + } + + this._externalEventQueue.push(e); + + if(this._isStepping) return null; //we're already looping, we can exit and we'll process this event when the next big-step completes + + //otherwise, kick him off + this._isStepping = true; + + var currentEvent; + /*jsl:ignore*/ + while(currentEvent = this._externalEventQueue.shift()){ + /*jsl:end*/ + this._performBigStep(currentEvent); + } + + this._isStepping = false; + return this.getConfiguration(); + }; + + function InterpreterScriptingContext(interpreter){ + this._interpreter = interpreter; + this._timeoutMap = {}; + } + + //TODO: consider whether this is the API we would like to expose + InterpreterScriptingContext.prototype = { + raise : function(event){ + this._interpreter._internalEventQueue.push(event); + }, + send : function(event, options){ + if(options.delay === undefined){ + this.gen(event); + }else{ + if( typeof setTimeout === 'undefined' ) throw new Error('Default implementation of Statechart.prototype.send will not work unless setTimeout is defined globally.'); + + if (printTrace) this._interpreter.opts.log("sending event", event.name, "with content", event.data, "after delay", options.delay); + + var timeoutId = setTimeout(this._interpreter.gen.bind(this._interpreter,event), options.delay || 0); + + if (options.sendid) this._timeoutMap[options.sendid] = timeoutId; + } + }, + cancel : function(sendid){ + + if( typeof clearTimeout === 'undefined' ) throw new Error('Default implementation of Statechart.prototype.cancel will not work unless setTimeout is defined globally.'); + + if (sendid in this._timeoutMap) { + if (printTrace) this._interpreter.opts.log("cancelling ", sendid, " with timeout id ", this._timeoutMap[sendid]); + clearTimeout(this._timeoutMap[sendid]); + } + } + + }; + + return { + /** @expose */ + BaseInterpreter: BaseInterpreter, + /** @expose */ + Statechart: Statechart, + /** @expose */ + ArraySet : ArraySet, + /** @expose */ + STATE_TYPES : STATE_TYPES, + /** @expose */ + initializeModel : initializeModel, + /** @expose */ + InterpreterScriptingContext : InterpreterScriptingContext + }; +})); + +}); +require.register("isaacs-sax-js/lib/sax.js", function(exports, require, module){ +// wrapper for non-node envs +;(function (sax) { + +sax.parser = function (strict, opt) { return new SAXParser(strict, opt) } +sax.SAXParser = SAXParser +sax.SAXStream = SAXStream +sax.createStream = createStream + +// When we pass the MAX_BUFFER_LENGTH position, start checking for buffer overruns. +// When we check, schedule the next check for MAX_BUFFER_LENGTH - (max(buffer lengths)), +// since that's the earliest that a buffer overrun could occur. This way, checks are +// as rare as required, but as often as necessary to ensure never crossing this bound. +// Furthermore, buffers are only tested at most once per write(), so passing a very +// large string into write() might have undesirable effects, but this is manageable by +// the caller, so it is assumed to be safe. Thus, a call to write() may, in the extreme +// edge case, result in creating at most one complete copy of the string passed in. +// Set to Infinity to have unlimited buffers. +sax.MAX_BUFFER_LENGTH = 64 * 1024 + +var buffers = [ + "comment", "sgmlDecl", "textNode", "tagName", "doctype", + "procInstName", "procInstBody", "entity", "attribName", + "attribValue", "cdata", "script" +] + +sax.EVENTS = // for discoverability. + [ "text" + , "processinginstruction" + , "sgmldeclaration" + , "doctype" + , "comment" + , "attribute" + , "opentag" + , "closetag" + , "opencdata" + , "cdata" + , "closecdata" + , "error" + , "end" + , "ready" + , "script" + , "opennamespace" + , "closenamespace" + ] + +function SAXParser (strict, opt) { + if (!(this instanceof SAXParser)) return new SAXParser(strict, opt) + + var parser = this + clearBuffers(parser) + parser.q = parser.c = "" + parser.bufferCheckPosition = sax.MAX_BUFFER_LENGTH + parser.opt = opt || {} + parser.opt.lowercase = parser.opt.lowercase || parser.opt.lowercasetags + parser.looseCase = parser.opt.lowercase ? "toLowerCase" : "toUpperCase" + parser.tags = [] + parser.closed = parser.closedRoot = parser.sawRoot = false + parser.tag = parser.error = null + parser.strict = !!strict + parser.noscript = !!(strict || parser.opt.noscript) + parser.state = S.BEGIN + parser.ENTITIES = Object.create(sax.ENTITIES) + parser.attribList = [] + + // namespaces form a prototype chain. + // it always points at the current tag, + // which protos to its parent tag. + if (parser.opt.xmlns) parser.ns = Object.create(rootNS) + + // mostly just for error reporting + parser.trackPosition = parser.opt.position !== false + if (parser.trackPosition) { + parser.position = parser.line = parser.column = 0 + } + emit(parser, "onready") +} + +if (!Object.create) Object.create = function (o) { + function f () { this.__proto__ = o } + f.prototype = o + return new f +} + +if (!Object.getPrototypeOf) Object.getPrototypeOf = function (o) { + return o.__proto__ +} + +if (!Object.keys) Object.keys = function (o) { + var a = [] + for (var i in o) if (o.hasOwnProperty(i)) a.push(i) + return a +} + +function checkBufferLength (parser) { + var maxAllowed = Math.max(sax.MAX_BUFFER_LENGTH, 10) + , maxActual = 0 + for (var i = 0, l = buffers.length; i < l; i ++) { + var len = parser[buffers[i]].length + if (len > maxAllowed) { + // Text/cdata nodes can get big, and since they're buffered, + // we can get here under normal conditions. + // Avoid issues by emitting the text node now, + // so at least it won't get any bigger. + switch (buffers[i]) { + case "textNode": + closeText(parser) + break + + case "cdata": + emitNode(parser, "oncdata", parser.cdata) + parser.cdata = "" + break + + case "script": + emitNode(parser, "onscript", parser.script) + parser.script = "" + break + + default: + error(parser, "Max buffer length exceeded: "+buffers[i]) + } + } + maxActual = Math.max(maxActual, len) + } + // schedule the next check for the earliest possible buffer overrun. + parser.bufferCheckPosition = (sax.MAX_BUFFER_LENGTH - maxActual) + + parser.position +} + +function clearBuffers (parser) { + for (var i = 0, l = buffers.length; i < l; i ++) { + parser[buffers[i]] = "" + } +} + +SAXParser.prototype = + { end: function () { end(this) } + , write: write + , resume: function () { this.error = null; return this } + , close: function () { return this.write(null) } + } + +try { + var Stream = require("stream").Stream +} catch (ex) { + var Stream = function () {} +} + + +var streamWraps = sax.EVENTS.filter(function (ev) { + return ev !== "error" && ev !== "end" +}) + +function createStream (strict, opt) { + return new SAXStream(strict, opt) +} + +function SAXStream (strict, opt) { + if (!(this instanceof SAXStream)) return new SAXStream(strict, opt) + + Stream.apply(this) + + this._parser = new SAXParser(strict, opt) + this.writable = true + this.readable = true + + + var me = this + + this._parser.onend = function () { + me.emit("end") + } + + this._parser.onerror = function (er) { + me.emit("error", er) + + // if didn't throw, then means error was handled. + // go ahead and clear error, so we can write again. + me._parser.error = null + } + + streamWraps.forEach(function (ev) { + Object.defineProperty(me, "on" + ev, { + get: function () { return me._parser["on" + ev] }, + set: function (h) { + if (!h) { + me.removeAllListeners(ev) + return me._parser["on"+ev] = h + } + me.on(ev, h) + }, + enumerable: true, + configurable: false + }) + }) +} + +SAXStream.prototype = Object.create(Stream.prototype, + { constructor: { value: SAXStream } }) + +SAXStream.prototype.write = function (data) { + this._parser.write(data.toString()) + this.emit("data", data) + return true +} + +SAXStream.prototype.end = function (chunk) { + if (chunk && chunk.length) this._parser.write(chunk.toString()) + this._parser.end() + return true +} + +SAXStream.prototype.on = function (ev, handler) { + var me = this + if (!me._parser["on"+ev] && streamWraps.indexOf(ev) !== -1) { + me._parser["on"+ev] = function () { + var args = arguments.length === 1 ? [arguments[0]] + : Array.apply(null, arguments) + args.splice(0, 0, ev) + me.emit.apply(me, args) + } + } + + return Stream.prototype.on.call(me, ev, handler) +} + + + +// character classes and tokens +var whitespace = "\r\n\t " + // this really needs to be replaced with character classes. + // XML allows all manner of ridiculous numbers and digits. + , number = "0124356789" + , letter = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + // (Letter | "_" | ":") + , quote = "'\"" + , entity = number+letter+"#" + , attribEnd = whitespace + ">" + , CDATA = "[CDATA[" + , DOCTYPE = "DOCTYPE" + , XML_NAMESPACE = "http://www.w3.org/XML/1998/namespace" + , XMLNS_NAMESPACE = "http://www.w3.org/2000/xmlns/" + , rootNS = { xml: XML_NAMESPACE, xmlns: XMLNS_NAMESPACE } + +// turn all the string character sets into character class objects. +whitespace = charClass(whitespace) +number = charClass(number) +letter = charClass(letter) + +// http://www.w3.org/TR/REC-xml/#NT-NameStartChar +// This implementation works on strings, a single character at a time +// as such, it cannot ever support astral-plane characters (10000-EFFFF) +// without a significant breaking change to either this parser, or the +// JavaScript language. Implementation of an emoji-capable xml parser +// is left as an exercise for the reader. +var nameStart = /[:_A-Za-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD]/ + +var nameBody = /[:_A-Za-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\u00B7\u0300-\u036F\u203F-\u2040\.\d-]/ + +quote = charClass(quote) +entity = charClass(entity) +attribEnd = charClass(attribEnd) + +function charClass (str) { + return str.split("").reduce(function (s, c) { + s[c] = true + return s + }, {}) +} + +function isRegExp (c) { + return Object.prototype.toString.call(c) === '[object RegExp]' +} + +function is (charclass, c) { + return isRegExp(charclass) ? !!c.match(charclass) : charclass[c] +} + +function not (charclass, c) { + return !is(charclass, c) +} + +var S = 0 +sax.STATE = +{ BEGIN : S++ +, TEXT : S++ // general stuff +, TEXT_ENTITY : S++ // & and such. +, OPEN_WAKA : S++ // < +, SGML_DECL : S++ // +, SCRIPT : S++ // + + + + + + + + +
+
+ + + diff --git a/test/platform-tests/browser/runner.js b/test/platform-tests/browser/runner.js new file mode 100644 index 00000000..e69de29b diff --git a/test/platform-tests/browser/scxml-test-framework b/test/platform-tests/browser/scxml-test-framework new file mode 120000 index 00000000..f56aa76a --- /dev/null +++ b/test/platform-tests/browser/scxml-test-framework @@ -0,0 +1 @@ +../../scxml-test-framework \ No newline at end of file diff --git a/test/platform-tests/browser/scxml.conf.js b/test/platform-tests/browser/scxml.conf.js new file mode 100644 index 00000000..c6188288 --- /dev/null +++ b/test/platform-tests/browser/scxml.conf.js @@ -0,0 +1,76 @@ +// Karma configuration +// Generated on Fri May 03 2013 22:39:17 GMT-0600 (MDT) + + +// base path, that will be used to resolve files and exclude +basePath = ''; + + +// list of files / patterns to load in the browser +files = [ + QUNIT, + QUNIT_ADAPTER, + //JASMINE, + //JASMINE_ADAPTER, + //MOCHA, + //MOCHA_ADAPTER, + + './lib/async.js', + './lib/jquery.js', + './lib/scxml.js', + //'./spec.js' + {pattern: 'scxml-test-framework/test/*/*', watched: true, included: false, served: true}, + {pattern: 'spec.js', watched: true, included: true, served: true} +]; + + +// list of files to exclude +exclude = [ + +]; + + +// test results reporter to use +// possible values: 'dots', 'progress', 'junit' +reporters = ['progress']; + + +// web server port +port = 9876; + + +// cli runner port +runnerPort = 9100; + + +// enable / disable colors in the output (reporters and logs) +colors = true; + + +// level of logging +// possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG +logLevel = LOG_DEBUG; + + +// enable / disable watching file and executing tests whenever any file changes +autoWatch = false; + + +// Start these browsers, currently available: +// - Chrome +// - ChromeCanary +// - Firefox +// - Opera +// - Safari (only Mac) +// - PhantomJS +// - IE (only Windows) +browsers = ['Firefox']; + + +// If browser does not capture in given timeout [ms], kill it +captureTimeout = 60000; + + +// Continuous Integration mode +// if true, it capture browsers, run tests and exit +singleRun = false; diff --git a/test/platform-tests/browser/spec.js b/test/platform-tests/browser/spec.js new file mode 100644 index 00000000..177d3e84 --- /dev/null +++ b/test/platform-tests/browser/spec.js @@ -0,0 +1,155 @@ +[ + { name : '/base/scxml-test-framework/test/more-parallel/test0', test : '/base/scxml-test-framework/test/more-parallel/test0.json', scxml : '/base/scxml-test-framework/test/more-parallel/test0.scxml' }, + { name : '/base/scxml-test-framework/test/more-parallel/test7', test : '/base/scxml-test-framework/test/more-parallel/test7.json', scxml : '/base/scxml-test-framework/test/more-parallel/test7.scxml' }, + { name : '/base/scxml-test-framework/test/more-parallel/test5', test : '/base/scxml-test-framework/test/more-parallel/test5.json', scxml : '/base/scxml-test-framework/test/more-parallel/test5.scxml' }, + { name : '/base/scxml-test-framework/test/more-parallel/test2', test : '/base/scxml-test-framework/test/more-parallel/test2.json', scxml : '/base/scxml-test-framework/test/more-parallel/test2.scxml' }, + { name : '/base/scxml-test-framework/test/more-parallel/test3', test : '/base/scxml-test-framework/test/more-parallel/test3.json', scxml : '/base/scxml-test-framework/test/more-parallel/test3.scxml' }, + { name : '/base/scxml-test-framework/test/more-parallel/test10', test : '/base/scxml-test-framework/test/more-parallel/test10.json', scxml : '/base/scxml-test-framework/test/more-parallel/test10.scxml' }, + { name : '/base/scxml-test-framework/test/more-parallel/test1', test : '/base/scxml-test-framework/test/more-parallel/test1.json', scxml : '/base/scxml-test-framework/test/more-parallel/test1.scxml' }, + { name : '/base/scxml-test-framework/test/more-parallel/test4', test : '/base/scxml-test-framework/test/more-parallel/test4.json', scxml : '/base/scxml-test-framework/test/more-parallel/test4.scxml' }, + { name : '/base/scxml-test-framework/test/more-parallel/test8', test : '/base/scxml-test-framework/test/more-parallel/test8.json', scxml : '/base/scxml-test-framework/test/more-parallel/test8.scxml' }, + { name : '/base/scxml-test-framework/test/more-parallel/test6', test : '/base/scxml-test-framework/test/more-parallel/test6.json', scxml : '/base/scxml-test-framework/test/more-parallel/test6.scxml' }, + { name : '/base/scxml-test-framework/test/more-parallel/test9', test : '/base/scxml-test-framework/test/more-parallel/test9.json', scxml : '/base/scxml-test-framework/test/more-parallel/test9.scxml' }, + { name : '/base/scxml-test-framework/test/default-initial-state/initial1', test : '/base/scxml-test-framework/test/default-initial-state/initial1.json', scxml : '/base/scxml-test-framework/test/default-initial-state/initial1.scxml' }, + { name : '/base/scxml-test-framework/test/default-initial-state/initial2', test : '/base/scxml-test-framework/test/default-initial-state/initial2.json', scxml : '/base/scxml-test-framework/test/default-initial-state/initial2.scxml' }, + { name : '/base/scxml-test-framework/test/parallel+interrupt/test18', test : '/base/scxml-test-framework/test/parallel+interrupt/test18.json', scxml : '/base/scxml-test-framework/test/parallel+interrupt/test18.scxml' }, + { name : '/base/scxml-test-framework/test/parallel+interrupt/test0', test : '/base/scxml-test-framework/test/parallel+interrupt/test0.json', scxml : '/base/scxml-test-framework/test/parallel+interrupt/test0.scxml' }, + { name : '/base/scxml-test-framework/test/parallel+interrupt/test23', test : '/base/scxml-test-framework/test/parallel+interrupt/test23.json', scxml : '/base/scxml-test-framework/test/parallel+interrupt/test23.scxml' }, + { name : '/base/scxml-test-framework/test/parallel+interrupt/test7', test : '/base/scxml-test-framework/test/parallel+interrupt/test7.json', scxml : '/base/scxml-test-framework/test/parallel+interrupt/test7.scxml' }, + { name : '/base/scxml-test-framework/test/parallel+interrupt/test29', test : '/base/scxml-test-framework/test/parallel+interrupt/test29.json', scxml : '/base/scxml-test-framework/test/parallel+interrupt/test29.scxml' }, + { name : '/base/scxml-test-framework/test/parallel+interrupt/test27', test : '/base/scxml-test-framework/test/parallel+interrupt/test27.json', scxml : '/base/scxml-test-framework/test/parallel+interrupt/test27.scxml' }, + { name : '/base/scxml-test-framework/test/parallel+interrupt/test28', test : '/base/scxml-test-framework/test/parallel+interrupt/test28.json', scxml : '/base/scxml-test-framework/test/parallel+interrupt/test28.scxml' }, + { name : '/base/scxml-test-framework/test/parallel+interrupt/test25', test : '/base/scxml-test-framework/test/parallel+interrupt/test25.json', scxml : '/base/scxml-test-framework/test/parallel+interrupt/test25.scxml' }, + { name : '/base/scxml-test-framework/test/parallel+interrupt/test17', test : '/base/scxml-test-framework/test/parallel+interrupt/test17.json', scxml : '/base/scxml-test-framework/test/parallel+interrupt/test17.scxml' }, + { name : '/base/scxml-test-framework/test/parallel+interrupt/test5', test : '/base/scxml-test-framework/test/parallel+interrupt/test5.json', scxml : '/base/scxml-test-framework/test/parallel+interrupt/test5.scxml' }, + { name : '/base/scxml-test-framework/test/parallel+interrupt/test12', test : '/base/scxml-test-framework/test/parallel+interrupt/test12.json', scxml : '/base/scxml-test-framework/test/parallel+interrupt/test12.scxml' }, + { name : '/base/scxml-test-framework/test/parallel+interrupt/test2', test : '/base/scxml-test-framework/test/parallel+interrupt/test2.json', scxml : '/base/scxml-test-framework/test/parallel+interrupt/test2.scxml' }, + { name : '/base/scxml-test-framework/test/parallel+interrupt/test3', test : '/base/scxml-test-framework/test/parallel+interrupt/test3.json', scxml : '/base/scxml-test-framework/test/parallel+interrupt/test3.scxml' }, + { name : '/base/scxml-test-framework/test/parallel+interrupt/test30', test : '/base/scxml-test-framework/test/parallel+interrupt/test30.json', scxml : '/base/scxml-test-framework/test/parallel+interrupt/test30.scxml' }, + { name : '/base/scxml-test-framework/test/parallel+interrupt/test22', test : '/base/scxml-test-framework/test/parallel+interrupt/test22.json', scxml : '/base/scxml-test-framework/test/parallel+interrupt/test22.scxml' }, + { name : '/base/scxml-test-framework/test/parallel+interrupt/test10', test : '/base/scxml-test-framework/test/parallel+interrupt/test10.json', scxml : '/base/scxml-test-framework/test/parallel+interrupt/test10.scxml' }, + { name : '/base/scxml-test-framework/test/parallel+interrupt/test1', test : '/base/scxml-test-framework/test/parallel+interrupt/test1.json', scxml : '/base/scxml-test-framework/test/parallel+interrupt/test1.scxml' }, + { name : '/base/scxml-test-framework/test/parallel+interrupt/test14', test : '/base/scxml-test-framework/test/parallel+interrupt/test14.json', scxml : '/base/scxml-test-framework/test/parallel+interrupt/test14.scxml' }, + { name : '/base/scxml-test-framework/test/parallel+interrupt/test16', test : '/base/scxml-test-framework/test/parallel+interrupt/test16.json', scxml : '/base/scxml-test-framework/test/parallel+interrupt/test16.scxml' }, + { name : '/base/scxml-test-framework/test/parallel+interrupt/test20', test : '/base/scxml-test-framework/test/parallel+interrupt/test20.json', scxml : '/base/scxml-test-framework/test/parallel+interrupt/test20.scxml' }, + { name : '/base/scxml-test-framework/test/parallel+interrupt/test11', test : '/base/scxml-test-framework/test/parallel+interrupt/test11.json', scxml : '/base/scxml-test-framework/test/parallel+interrupt/test11.scxml' }, + { name : '/base/scxml-test-framework/test/parallel+interrupt/test15', test : '/base/scxml-test-framework/test/parallel+interrupt/test15.json', scxml : '/base/scxml-test-framework/test/parallel+interrupt/test15.scxml' }, + { name : '/base/scxml-test-framework/test/parallel+interrupt/test19', test : '/base/scxml-test-framework/test/parallel+interrupt/test19.json', scxml : '/base/scxml-test-framework/test/parallel+interrupt/test19.scxml' }, + { name : '/base/scxml-test-framework/test/parallel+interrupt/test31', test : '/base/scxml-test-framework/test/parallel+interrupt/test31.json', scxml : '/base/scxml-test-framework/test/parallel+interrupt/test31.scxml' }, + { name : '/base/scxml-test-framework/test/parallel+interrupt/test4', test : '/base/scxml-test-framework/test/parallel+interrupt/test4.json', scxml : '/base/scxml-test-framework/test/parallel+interrupt/test4.scxml' }, + { name : '/base/scxml-test-framework/test/parallel+interrupt/test8', test : '/base/scxml-test-framework/test/parallel+interrupt/test8.json', scxml : '/base/scxml-test-framework/test/parallel+interrupt/test8.scxml' }, + { name : '/base/scxml-test-framework/test/parallel+interrupt/test21', test : '/base/scxml-test-framework/test/parallel+interrupt/test21.json', scxml : '/base/scxml-test-framework/test/parallel+interrupt/test21.scxml' }, + { name : '/base/scxml-test-framework/test/parallel+interrupt/test24', test : '/base/scxml-test-framework/test/parallel+interrupt/test24.json', scxml : '/base/scxml-test-framework/test/parallel+interrupt/test24.scxml' }, + { name : '/base/scxml-test-framework/test/parallel+interrupt/test13', test : '/base/scxml-test-framework/test/parallel+interrupt/test13.json', scxml : '/base/scxml-test-framework/test/parallel+interrupt/test13.scxml' }, + { name : '/base/scxml-test-framework/test/parallel+interrupt/test26', test : '/base/scxml-test-framework/test/parallel+interrupt/test26.json', scxml : '/base/scxml-test-framework/test/parallel+interrupt/test26.scxml' }, + { name : '/base/scxml-test-framework/test/parallel+interrupt/test6', test : '/base/scxml-test-framework/test/parallel+interrupt/test6.json', scxml : '/base/scxml-test-framework/test/parallel+interrupt/test6.scxml' }, + { name : '/base/scxml-test-framework/test/parallel+interrupt/test9', test : '/base/scxml-test-framework/test/parallel+interrupt/test9.json', scxml : '/base/scxml-test-framework/test/parallel+interrupt/test9.scxml' }, + { name : '/base/scxml-test-framework/test/in/TestInPredicate', test : '/base/scxml-test-framework/test/in/TestInPredicate.json', scxml : '/base/scxml-test-framework/test/in/TestInPredicate.scxml' }, + { name : '/base/scxml-test-framework/test/assign-current-small-step/test0', test : '/base/scxml-test-framework/test/assign-current-small-step/test0.json', scxml : '/base/scxml-test-framework/test/assign-current-small-step/test0.scxml' }, + { name : '/base/scxml-test-framework/test/assign-current-small-step/test2', test : '/base/scxml-test-framework/test/assign-current-small-step/test2.json', scxml : '/base/scxml-test-framework/test/assign-current-small-step/test2.scxml' }, + { name : '/base/scxml-test-framework/test/assign-current-small-step/test3', test : '/base/scxml-test-framework/test/assign-current-small-step/test3.json', scxml : '/base/scxml-test-framework/test/assign-current-small-step/test3.scxml' }, + { name : '/base/scxml-test-framework/test/assign-current-small-step/test1', test : '/base/scxml-test-framework/test/assign-current-small-step/test1.json', scxml : '/base/scxml-test-framework/test/assign-current-small-step/test1.scxml' }, + { name : '/base/scxml-test-framework/test/assign-current-small-step/test4', test : '/base/scxml-test-framework/test/assign-current-small-step/test4.json', scxml : '/base/scxml-test-framework/test/assign-current-small-step/test4.scxml' }, + { name : '/base/scxml-test-framework/test/basic/basic2', test : '/base/scxml-test-framework/test/basic/basic2.json', scxml : '/base/scxml-test-framework/test/basic/basic2.scxml' }, + { name : '/base/scxml-test-framework/test/basic/basic1', test : '/base/scxml-test-framework/test/basic/basic1.json', scxml : '/base/scxml-test-framework/test/basic/basic1.scxml' }, + { name : '/base/scxml-test-framework/test/basic/basic0', test : '/base/scxml-test-framework/test/basic/basic0.json', scxml : '/base/scxml-test-framework/test/basic/basic0.scxml' }, + { name : '/base/scxml-test-framework/test/hierarchy+documentOrder/test0', test : '/base/scxml-test-framework/test/hierarchy+documentOrder/test0.json', scxml : '/base/scxml-test-framework/test/hierarchy+documentOrder/test0.scxml' }, + { name : '/base/scxml-test-framework/test/hierarchy+documentOrder/test1', test : '/base/scxml-test-framework/test/hierarchy+documentOrder/test1.json', scxml : '/base/scxml-test-framework/test/hierarchy+documentOrder/test1.scxml' }, + { name : '/base/scxml-test-framework/test/script-src/test0', test : '/base/scxml-test-framework/test/script-src/test0.json', scxml : '/base/scxml-test-framework/test/script-src/test0.scxml' }, + { name : '/base/scxml-test-framework/test/script-src/test2', test : '/base/scxml-test-framework/test/script-src/test2.json', scxml : '/base/scxml-test-framework/test/script-src/test2.scxml' }, + { name : '/base/scxml-test-framework/test/script-src/test3', test : '/base/scxml-test-framework/test/script-src/test3.json', scxml : '/base/scxml-test-framework/test/script-src/test3.scxml' }, + { name : '/base/scxml-test-framework/test/script-src/test1', test : '/base/scxml-test-framework/test/script-src/test1.json', scxml : '/base/scxml-test-framework/test/script-src/test1.scxml' }, + { name : '/base/scxml-test-framework/test/multiple-events-per-transition/test1', test : '/base/scxml-test-framework/test/multiple-events-per-transition/test1.json', scxml : '/base/scxml-test-framework/test/multiple-events-per-transition/test1.scxml' }, + { name : '/base/scxml-test-framework/test/parallel/test0', test : '/base/scxml-test-framework/test/parallel/test0.json', scxml : '/base/scxml-test-framework/test/parallel/test0.scxml' }, + { name : '/base/scxml-test-framework/test/parallel/test2', test : '/base/scxml-test-framework/test/parallel/test2.json', scxml : '/base/scxml-test-framework/test/parallel/test2.scxml' }, + { name : '/base/scxml-test-framework/test/parallel/test3', test : '/base/scxml-test-framework/test/parallel/test3.json', scxml : '/base/scxml-test-framework/test/parallel/test3.scxml' }, + { name : '/base/scxml-test-framework/test/parallel/test1', test : '/base/scxml-test-framework/test/parallel/test1.json', scxml : '/base/scxml-test-framework/test/parallel/test1.scxml' }, + { name : '/base/scxml-test-framework/test/foreach/test1', test : '/base/scxml-test-framework/test/foreach/test1.json', scxml : '/base/scxml-test-framework/test/foreach/test1.scxml' }, + { name : '/base/scxml-test-framework/test/send-data/send1', test : '/base/scxml-test-framework/test/send-data/send1.json', scxml : '/base/scxml-test-framework/test/send-data/send1.scxml' }, + { name : '/base/scxml-test-framework/test/atom3-basic-tests/m3', test : '/base/scxml-test-framework/test/atom3-basic-tests/m3.json', scxml : '/base/scxml-test-framework/test/atom3-basic-tests/m3.scxml' }, + { name : '/base/scxml-test-framework/test/atom3-basic-tests/m0', test : '/base/scxml-test-framework/test/atom3-basic-tests/m0.json', scxml : '/base/scxml-test-framework/test/atom3-basic-tests/m0.scxml' }, + { name : '/base/scxml-test-framework/test/atom3-basic-tests/m1', test : '/base/scxml-test-framework/test/atom3-basic-tests/m1.json', scxml : '/base/scxml-test-framework/test/atom3-basic-tests/m1.scxml' }, + { name : '/base/scxml-test-framework/test/atom3-basic-tests/m2', test : '/base/scxml-test-framework/test/atom3-basic-tests/m2.json', scxml : '/base/scxml-test-framework/test/atom3-basic-tests/m2.scxml' }, + { name : '/base/scxml-test-framework/test/internal-transitions/test0', test : '/base/scxml-test-framework/test/internal-transitions/test0.json', scxml : '/base/scxml-test-framework/test/internal-transitions/test0.scxml' }, + { name : '/base/scxml-test-framework/test/internal-transitions/test1', test : '/base/scxml-test-framework/test/internal-transitions/test1.json', scxml : '/base/scxml-test-framework/test/internal-transitions/test1.scxml' }, + { name : '/base/scxml-test-framework/test/send-internal/test0', test : '/base/scxml-test-framework/test/send-internal/test0.json', scxml : '/base/scxml-test-framework/test/send-internal/test0.scxml' }, + { name : '/base/scxml-test-framework/test/cond-js/test0', test : '/base/scxml-test-framework/test/cond-js/test0.json', scxml : '/base/scxml-test-framework/test/cond-js/test0.scxml' }, + { name : '/base/scxml-test-framework/test/cond-js/test2', test : '/base/scxml-test-framework/test/cond-js/test2.json', scxml : '/base/scxml-test-framework/test/cond-js/test2.scxml' }, + { name : '/base/scxml-test-framework/test/cond-js/test1', test : '/base/scxml-test-framework/test/cond-js/test1.json', scxml : '/base/scxml-test-framework/test/cond-js/test1.scxml' }, + { name : '/base/scxml-test-framework/test/cond-js/TestConditionalTransition', test : '/base/scxml-test-framework/test/cond-js/TestConditionalTransition.json', scxml : '/base/scxml-test-framework/test/cond-js/TestConditionalTransition.scxml' }, + { name : '/base/scxml-test-framework/test/documentOrder/documentOrder0', test : '/base/scxml-test-framework/test/documentOrder/documentOrder0.json', scxml : '/base/scxml-test-framework/test/documentOrder/documentOrder0.scxml' }, + { name : '/base/scxml-test-framework/test/delayedSend/send3', test : '/base/scxml-test-framework/test/delayedSend/send3.json', scxml : '/base/scxml-test-framework/test/delayedSend/send3.scxml' }, + { name : '/base/scxml-test-framework/test/delayedSend/send1', test : '/base/scxml-test-framework/test/delayedSend/send1.json', scxml : '/base/scxml-test-framework/test/delayedSend/send1.scxml' }, + { name : '/base/scxml-test-framework/test/delayedSend/send2', test : '/base/scxml-test-framework/test/delayedSend/send2.json', scxml : '/base/scxml-test-framework/test/delayedSend/send2.scxml' }, + { name : '/base/scxml-test-framework/test/hierarchy/hier1', test : '/base/scxml-test-framework/test/hierarchy/hier1.json', scxml : '/base/scxml-test-framework/test/hierarchy/hier1.scxml' }, + { name : '/base/scxml-test-framework/test/hierarchy/hier2', test : '/base/scxml-test-framework/test/hierarchy/hier2.json', scxml : '/base/scxml-test-framework/test/hierarchy/hier2.scxml' }, + { name : '/base/scxml-test-framework/test/hierarchy/hier0', test : '/base/scxml-test-framework/test/hierarchy/hier0.json', scxml : '/base/scxml-test-framework/test/hierarchy/hier0.scxml' }, + { name : '/base/scxml-test-framework/test/if-else/test0', test : '/base/scxml-test-framework/test/if-else/test0.json', scxml : '/base/scxml-test-framework/test/if-else/test0.scxml' }, + { name : '/base/scxml-test-framework/test/targetless-transition/test0', test : '/base/scxml-test-framework/test/targetless-transition/test0.json', scxml : '/base/scxml-test-framework/test/targetless-transition/test0.scxml' }, + { name : '/base/scxml-test-framework/test/targetless-transition/test2', test : '/base/scxml-test-framework/test/targetless-transition/test2.json', scxml : '/base/scxml-test-framework/test/targetless-transition/test2.scxml' }, + { name : '/base/scxml-test-framework/test/targetless-transition/test3', test : '/base/scxml-test-framework/test/targetless-transition/test3.json', scxml : '/base/scxml-test-framework/test/targetless-transition/test3.scxml' }, + { name : '/base/scxml-test-framework/test/targetless-transition/test1', test : '/base/scxml-test-framework/test/targetless-transition/test1.json', scxml : '/base/scxml-test-framework/test/targetless-transition/test1.scxml' }, + { name : '/base/scxml-test-framework/test/script/test0', test : '/base/scxml-test-framework/test/script/test0.json', scxml : '/base/scxml-test-framework/test/script/test0.scxml' }, + { name : '/base/scxml-test-framework/test/script/test2', test : '/base/scxml-test-framework/test/script/test2.json', scxml : '/base/scxml-test-framework/test/script/test2.scxml' }, + { name : '/base/scxml-test-framework/test/script/test1', test : '/base/scxml-test-framework/test/script/test1.json', scxml : '/base/scxml-test-framework/test/script/test1.scxml' }, + { name : '/base/scxml-test-framework/test/raise/send5', test : '/base/scxml-test-framework/test/raise/send5.json', scxml : '/base/scxml-test-framework/test/raise/send5.scxml' }, + { name : '/base/scxml-test-framework/test/raise/send7', test : '/base/scxml-test-framework/test/raise/send7.json', scxml : '/base/scxml-test-framework/test/raise/send7.scxml' }, + { name : '/base/scxml-test-framework/test/raise/send3', test : '/base/scxml-test-framework/test/raise/send3.json', scxml : '/base/scxml-test-framework/test/raise/send3.scxml' }, + { name : '/base/scxml-test-framework/test/raise/send1', test : '/base/scxml-test-framework/test/raise/send1.json', scxml : '/base/scxml-test-framework/test/raise/send1.scxml' }, + { name : '/base/scxml-test-framework/test/raise/send2', test : '/base/scxml-test-framework/test/raise/send2.json', scxml : '/base/scxml-test-framework/test/raise/send2.scxml' }, + { name : '/base/scxml-test-framework/test/raise/send6', test : '/base/scxml-test-framework/test/raise/send6.json', scxml : '/base/scxml-test-framework/test/raise/send6.scxml' }, + { name : '/base/scxml-test-framework/test/raise/send4', test : '/base/scxml-test-framework/test/raise/send4.json', scxml : '/base/scxml-test-framework/test/raise/send4.scxml' }, + { name : '/base/scxml-test-framework/test/raise/send8', test : '/base/scxml-test-framework/test/raise/send8.json', scxml : '/base/scxml-test-framework/test/raise/send8.scxml' }, + { name : '/base/scxml-test-framework/test/history/history6', test : '/base/scxml-test-framework/test/history/history6.json', scxml : '/base/scxml-test-framework/test/history/history6.scxml' }, + { name : '/base/scxml-test-framework/test/history/history2', test : '/base/scxml-test-framework/test/history/history2.json', scxml : '/base/scxml-test-framework/test/history/history2.scxml' }, + { name : '/base/scxml-test-framework/test/history/history5', test : '/base/scxml-test-framework/test/history/history5.json', scxml : '/base/scxml-test-framework/test/history/history5.scxml' }, + { name : '/base/scxml-test-framework/test/history/history3', test : '/base/scxml-test-framework/test/history/history3.json', scxml : '/base/scxml-test-framework/test/history/history3.scxml' }, + { name : '/base/scxml-test-framework/test/history/history1', test : '/base/scxml-test-framework/test/history/history1.json', scxml : '/base/scxml-test-framework/test/history/history1.scxml' }, + { name : '/base/scxml-test-framework/test/history/history4', test : '/base/scxml-test-framework/test/history/history4.json', scxml : '/base/scxml-test-framework/test/history/history4.scxml' }, + { name : '/base/scxml-test-framework/test/history/history0', test : '/base/scxml-test-framework/test/history/history0.json', scxml : '/base/scxml-test-framework/test/history/history0.scxml' }, + { name : '/base/scxml-test-framework/test/scxml-prefix-event-name-matching/test0', test : '/base/scxml-test-framework/test/scxml-prefix-event-name-matching/test0.json', scxml : '/base/scxml-test-framework/test/scxml-prefix-event-name-matching/test0.scxml' }, + { name : '/base/scxml-test-framework/test/scxml-prefix-event-name-matching/test1', test : '/base/scxml-test-framework/test/scxml-prefix-event-name-matching/test1.json', scxml : '/base/scxml-test-framework/test/scxml-prefix-event-name-matching/test1.scxml' }, + { name : '/base/scxml-test-framework/test/scxml-prefix-event-name-matching/star0', test : '/base/scxml-test-framework/test/scxml-prefix-event-name-matching/star0.json', scxml : '/base/scxml-test-framework/test/scxml-prefix-event-name-matching/star0.scxml' } +].forEach(function(test){ + var tmp = test.name.split('/'), testGroup = tmp[0], testName = tmp[1]; + + module(testGroup); + asyncTest(testName,function(){ + $.getJSON(test.test,function(testScript){ + scxml.urlToModel(test.scxml,function(err,model){ + if(err) throw err; + + console.log('model',model.toString()); + var sc = new scxml.scion.Statechart(model); + var actualInitialConf = sc.start(); + + console.log('initial configuration',actualInitialConf); + + deepEqual(actualInitialConf.sort(),testScript.initialConfiguration.sort(),'initial configuration'); + + async.forEachSeries(testScript.events,function(nextEvent,cb){ + + function ns(){ + console.log('sending event',nextEvent.event); + + var actualNextConf = sc.gen(nextEvent.event); + + console.log('next configuration',actualNextConf); + + deepEqual(actualNextConf.sort(),nextEvent.nextConfiguration.sort(),'next configuration after sending event ' + JSON.stringify(nextEvent)); + + cb(); + } + + if(nextEvent.after){ + console.log('Test harness waiting',nextEvent.after,'ms before sending next event'); + setTimeout(ns,nextEvent.after); + }else{ + ns(); + } + },start); + + }); + }); + }); +}); diff --git a/test/platform-tests/node/require/require.json b/test/platform-tests/node/require/require.json new file mode 100644 index 00000000..1d2faec4 --- /dev/null +++ b/test/platform-tests/node/require/require.json @@ -0,0 +1,12 @@ +{ + "initialConfiguration" : ["a"], + "events" : [ + { + "event" : { "name" : "t" }, + "nextConfiguration" : ["b"] + } + ] +} + + + diff --git a/test/platform-tests/node/require/script2.js b/test/platform-tests/node/require/script2.js new file mode 100644 index 00000000..25259e75 --- /dev/null +++ b/test/platform-tests/node/require/script2.js @@ -0,0 +1 @@ +submodule2 = require('./submodule2'); diff --git a/test/platform-tests/node/require/script3.js b/test/platform-tests/node/require/script3.js new file mode 100644 index 00000000..76a37e15 --- /dev/null +++ b/test/platform-tests/node/require/script3.js @@ -0,0 +1 @@ +submodule3 = require('./submodule3'); diff --git a/test/platform-tests/node/require/script4.js b/test/platform-tests/node/require/script4.js new file mode 100644 index 00000000..348b8c90 --- /dev/null +++ b/test/platform-tests/node/require/script4.js @@ -0,0 +1 @@ +submodule4 = require('./submodule4'); diff --git a/test/platform-tests/node/require/submodule1.js b/test/platform-tests/node/require/submodule1.js new file mode 100644 index 00000000..bd816eab --- /dev/null +++ b/test/platform-tests/node/require/submodule1.js @@ -0,0 +1 @@ +module.exports = 1; diff --git a/test/platform-tests/node/require/submodule2.js b/test/platform-tests/node/require/submodule2.js new file mode 100644 index 00000000..4bbffde1 --- /dev/null +++ b/test/platform-tests/node/require/submodule2.js @@ -0,0 +1 @@ +module.exports = 2; diff --git a/test/platform-tests/node/require/submodule3.js b/test/platform-tests/node/require/submodule3.js new file mode 100644 index 00000000..690aad34 --- /dev/null +++ b/test/platform-tests/node/require/submodule3.js @@ -0,0 +1 @@ +module.exports = 3; diff --git a/test/platform-tests/node/require/submodule4.js b/test/platform-tests/node/require/submodule4.js new file mode 100644 index 00000000..a9bbdd80 --- /dev/null +++ b/test/platform-tests/node/require/submodule4.js @@ -0,0 +1 @@ +module.exports = 4; diff --git a/test/platform-tests/node/require/test.scxml b/test/platform-tests/node/require/test.scxml new file mode 100644 index 00000000..221ad818 --- /dev/null +++ b/test/platform-tests/node/require/test.scxml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + +