From 66efd68bfcca7acbc39a0e4263254c1773044bbd Mon Sep 17 00:00:00 2001 From: Nick Fargo Date: Sat, 21 Dec 2013 18:59:12 -0800 Subject: [PATCH] re-npmignore src; un-gitignore lib --- .gitignore | 2 - .npmignore | 2 + lib/export-static.js | 146 ++ lib/guard-map.js | 177 ++ lib/index.js | 18 + lib/region.js | 655 +++++++ lib/root-state.js | 277 +++ lib/state-content.js | 28 + lib/state-event-emitter.js | 167 ++ lib/state-expression.js | 212 ++ lib/state-function.js | 57 + lib/state-metaobject.js | 30 + lib/state.js | 1721 +++++++++++++++++ lib/transition-expression.js | 63 + lib/transition.js | 83 + test/lib/api/guard-map.js | 177 ++ .../root-state/get-transition-expression.js | 5 + test/lib/api/state/data.test.js | 200 ++ test/lib/api/state/mutate.js | 137 ++ test/lib/api/state/query.js | 180 ++ test/lib/api/state/substates.js | 141 ++ test/lib/attributes.test.js | 537 +++++ test/lib/creation.test.js | 114 ++ test/lib/events.test.js | 454 +++++ test/lib/guards.test.js | 130 ++ test/lib/linearization.test.js | 132 ++ test/lib/methods.test.js | 240 +++ test/lib/perf.test.js | 179 ++ test/lib/querying.test.js | 122 ++ 29 files changed, 6384 insertions(+), 2 deletions(-) create mode 100644 lib/export-static.js create mode 100644 lib/guard-map.js create mode 100644 lib/index.js create mode 100644 lib/region.js create mode 100644 lib/root-state.js create mode 100644 lib/state-content.js create mode 100644 lib/state-event-emitter.js create mode 100644 lib/state-expression.js create mode 100644 lib/state-function.js create mode 100644 lib/state-metaobject.js create mode 100644 lib/state.js create mode 100644 lib/transition-expression.js create mode 100644 lib/transition.js create mode 100644 test/lib/api/guard-map.js create mode 100644 test/lib/api/root-state/get-transition-expression.js create mode 100644 test/lib/api/state/data.test.js create mode 100644 test/lib/api/state/mutate.js create mode 100644 test/lib/api/state/query.js create mode 100644 test/lib/api/state/substates.js create mode 100644 test/lib/attributes.test.js create mode 100644 test/lib/creation.test.js create mode 100644 test/lib/events.test.js create mode 100644 test/lib/guards.test.js create mode 100644 test/lib/linearization.test.js create mode 100644 test/lib/methods.test.js create mode 100644 test/lib/perf.test.js create mode 100644 test/lib/querying.test.js diff --git a/.gitignore b/.gitignore index 0d2483c..07e6e47 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1 @@ /node_modules -/lib -/test/lib diff --git a/.npmignore b/.npmignore index 07bcfba..f0a3161 100644 --- a/.npmignore +++ b/.npmignore @@ -1,2 +1,4 @@ build/ +src/ test/ +*.coffee.md diff --git a/lib/export-static.js b/lib/export-static.js new file mode 100644 index 0000000..64bf649 --- /dev/null +++ b/lib/export-static.js @@ -0,0 +1,146 @@ +// Generated by CoffeeScript 1.6.3 +(function() { + var global; + + global = this; + + module.exports = function(state) { + var define, _ref, + _this = this; + if (state == null) { + state = this; + } + define = this.define; + this.VERSION = '0.2.0'; + this.options = { + memoizeProtostates: true, + useDispatchTables: false + }; + this.O = (_ref = require('omicron'), this.env = _ref.env, this.NIL = _ref.NIL, _ref); + this.rxAccessor = /([$_A-Za-z][$\w]*)::(.*)/; + this.rxDelegatedEvent = /^(.*)\:([\w$]+)\s*$/; + this.rxTransitionArrow = /^\s*([\-=]>)\s*(.*)/; + this.transitionArrowMethods = { + '->': 'change', + '=>': 'changeTo' + }; + this.noConflict = (function() { + var original; + original = global.state; + return function() { + global.state = original; + return this; + }; + })(); + this.bitfield = function(object, names, offset) { + var index, key, _i, _len; + if (object == null) { + object = {}; + } + if (offset == null) { + offset = 0; + } + if (typeof names === 'string') { + names = names.split(/\s+/); + } + for (index = _i = 0, _len = names.length; _i < _len; index = ++_i) { + key = names[index]; + object[key] = 1 << index + offset; + } + return object; + }; + this.debug = function() { + if (_this.env.debug) { + return console.log.apply(console, arguments); + } + }; + this.bind = (function() { + var StateBoundFunction, bind; + bind = function(fn) { + return new StateBoundFunction(fn); + }; + bind["class"] = StateBoundFunction = (function() { + StateBoundFunction.prototype.type = 'state-bound-function'; + + function StateBoundFunction(fn) { + this.fn = fn; + } + + return StateBoundFunction; + + })(); + return bind; + })(); + this.fix = (function() { + var StateFixedFunction, fix; + fix = function(fn) { + return new StateFixedFunction(fn); + }; + fix["class"] = StateFixedFunction = (function() { + StateFixedFunction.prototype.type = 'state-fixed-function'; + + function StateFixedFunction(fn) { + this.fn = fn; + } + + return StateFixedFunction; + + })(); + return fix; + })(); + this.own = (function() { + var rxAccessor; + rxAccessor = _this.rxAccessor; + return function(owner, selector, expr) { + var accessorName, instated, match, _ref1; + if (rxAccessor.test(selector)) { + _ref1 = rxAccessor.exec(selector), match = _ref1[0], accessorName = _ref1[1], selector = _ref1[2]; + } else { + accessorName = 'state'; + } + if (!(instated = owner[accessorName](selector))) { + return null; + } + if (instated.owner === owner) { + return instated[instated.isVirtual() && 'realize' || 'mutate'](expr); + } else { + return instated.virtualize(owner[accessorName]('')).realize(expr); + } + }; + })(); + this.extend = function(parastates, attributes, expression) { + if (typeof attributes !== 'string') { + expression = attributes; + attributes = ''; + } + if (expression == null) { + expression = {}; + } + expression.parastates = parastates; + return define(attributes, expression); + }; + this.STATE_ATTRIBUTE_MODIFIERS = "mutable finite static immutable\ninitial conclusive final\nabstract concrete default\nreflective\nhistory retained shallow versioned\nconcurrent"; + this.STATE_EXPRESSION_CATEGORIES = 'parastates data methods events guards substates transitions'; + this.STATE_EXPRESSION_CATEGORY_SYNONYMS = { + extend: 'parastates', + "extends": 'parastates', + states: 'substates' + }; + this.STATE_EVENT_TYPES = 'construct depart exit enter arrive destroy mutate noSuchMethod'; + this.GUARD_ACTIONS = 'admit release'; + this.TRANSITION_PROPERTIES = 'origin source target action conjugate'; + this.TRANSITION_EXPRESSION_CATEGORIES = 'methods events guards'; + this.TRANSITION_EVENT_TYPES = 'construct destroy enter exit start end abort'; + this.STATE_ATTRIBUTES = this.bitfield({ + NORMAL: 0 + }, "INCIPIENT\nATOMIC\nDESTROYED\nVIRTUAL\nPARASTATIC" + ' ' + this.STATE_ATTRIBUTE_MODIFIERS.toUpperCase()); + this.TRAVERSAL_FLAGS = this.bitfield({ + VIA_NONE: 0, + VIA_ALL: ~0 + }, "VIA_SUB\nVIA_PARA\nVIA_SUPER\nVIA_PROTO\nVIA_VIRTUAL"); + (function() { + return this.VIA_HYPER = this.VIA_PARA | this.VIA_SUPER; + }).apply(this.TRAVERSAL_FLAGS); + }; + +}).call(this); diff --git a/lib/guard-map.js b/lib/guard-map.js new file mode 100644 index 0000000..0df4d7d --- /dev/null +++ b/lib/guard-map.js @@ -0,0 +1,177 @@ +// Generated by CoffeeScript 1.6.3 +(function() { + var GuardMap, O, + __hasProp = {}.hasOwnProperty; + + O = require('omicron'); + + module.exports = GuardMap = (function() { + var coerceToPredicates, falsePredicate, interpret, isArray, isEmpty, predicateOf, trim, truePredicate; + + trim = O.trim, isArray = O.isArray, isEmpty = O.isEmpty; + + function GuardMap(guardType, expression) { + this.guardType = guardType; + this.map = interpret(expression); + } + + truePredicate = function() { + return true; + }; + + falsePredicate = function() { + return false; + }; + + predicateOf = function(value) { + if (value) { + return truePredicate; + } else { + return falsePredicate; + } + }; + + coerceToPredicates = function(list) { + var element, index, _i, _len; + for (index = _i = 0, _len = list.length; _i < _len; index = ++_i) { + element = list[index]; + if (typeof element !== 'function') { + list[index] = predicateOf(element); + } + } + return list; + }; + + interpret = function(expression) { + var list, map, selector; + if (expression == null) { + return null; + } + if (typeof expression === 'string') { + map = {}; + map[expression] = [truePredicate]; + return map; + } else if (typeof expression === 'function') { + return { + '***': [expression] + }; + } else if (isArray(expression)) { + return { + '***': coerceToPredicates(expression.slice(0)) + }; + } else if (typeof expression === 'object') { + map = {}; + for (selector in expression) { + if (!__hasProp.call(expression, selector)) continue; + list = expression[selector]; + list = isArray(list) ? list.slice(0) : [list]; + map[selector] = coerceToPredicates(list); + } + return map; + } else { + return { + '***': [predicateOf(expression)] + }; + } + }; + + GuardMap.prototype.get = function(selector) { + var _ref; + return (_ref = this.map[selector]) != null ? _ref.slice(0) : void 0; + }; + + GuardMap.prototype.add = function(selector, predicates) { + var list, map, predicate, _i, _len; + map = this.map != null ? this.map : this.map = {}; + list = map[selector] != null ? map[selector] : map[selector] = []; + if (typeof predicates === 'function') { + list.push(predicates); + } else if (isArray(predicates)) { + for (_i = 0, _len = predicates.length; _i < _len; _i++) { + predicate = predicates[_i]; + if (typeof predicate !== 'function') { + predicate = predicateOf(predicate); + } + list.push(predicate); + } + } else { + list.push(predicateOf(predicates)); + } + }; + + GuardMap.prototype.remove = function(selector) { + var list; + if (!(list = this.map[selector])) { + return null; + } + delete this.map[selector]; + return list; + }; + + GuardMap.prototype.evaluate = function(againstState, asState) { + var context, guardType, matched, owner, predicate, predicates, queryAgainst, selector, selectors, succeededAtLeastOnce, _i, _j, _len, _len1, _ref, _ref1; + guardType = this.guardType; + owner = asState.owner; + if (isEmpty(this.map)) { + return true; + } + queryAgainst = againstState; + while (!(queryAgainst.root === asState.root || queryAgainst.root.isProtostateOf(asState.root))) { + if (!(queryAgainst = queryAgainst.protostate)) { + throw new Error("Unrelated: '" + againstState + "', '" + asState + "'"); + } + } + _ref = this.map; + for (selectors in _ref) { + if (!__hasProp.call(_ref, selectors)) continue; + predicates = _ref[selectors]; + if (!((predicates != null ? predicates.length : void 0) > 0)) { + continue; + } + if (!succeededAtLeastOnce) { + succeededAtLeastOnce = false; + } + matched = false; + _ref1 = trim(selectors).split(/\s*,+\s*/); + for (_i = 0, _len = _ref1.length; _i < _len; _i++) { + selector = _ref1[_i]; + if (matched = asState.query(selector, queryAgainst)) { + break; + } + } + if (!matched) { + continue; + } + for (_j = 0, _len1 = predicates.length; _j < _len1; _j++) { + predicate = predicates[_j]; + if (!(predicate != null)) { + continue; + } + if (predicate === false) { + return false; + } else if (typeof predicate === 'function') { + context = owner; + } else if (predicate.type === 'state-bound-function') { + context = asState; + predicate = predicate.fn; + } else if (predicate) { + continue; + } + if (!predicate.call(context, againstState, asState, guardType)) { + return false; + } + } + succeededAtLeastOnce = true; + } + return succeededAtLeastOnce || false; + }; + + GuardMap.prototype.clone = function() { + return new this.constructor(this.guardType, this.map); + }; + + return GuardMap; + + })(); + +}).call(this); diff --git a/lib/index.js b/lib/index.js new file mode 100644 index 0000000..807e469 --- /dev/null +++ b/lib/index.js @@ -0,0 +1,18 @@ +// Generated by CoffeeScript 1.6.3 +(function() { + var exports, state; + + module.exports = state = require('./state-function'); + + exports = function() { + this.State = require('./state'); + this.StateExpression = require('./state-expression'); + this.GuardMap = require('./guard-map'); + this.RootState = require('./root-state'); + this.Transition = require('./transition'); + return this.TransitionExpression = require('./transition-expression'); + }; + + exports.apply(state); + +}).call(this); diff --git a/lib/region.js b/lib/region.js new file mode 100644 index 0000000..7538322 --- /dev/null +++ b/lib/region.js @@ -0,0 +1,655 @@ +// Generated by CoffeeScript 1.6.3 +(function() { + var CURRENCY_STATES, O, Region, STATE_ATTRIBUTES, State, StateExpression, TRAVERSAL_FLAGS, Transition, TransitionExpression, defaultTransitionExpression, state, + __hasProp = {}.hasOwnProperty, + __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; + + O = require('omicron'); + + state = require('./state-function'); + + State = require('./state'); + + StateExpression = null; + + Transition = null; + + TransitionExpression = null; + + STATE_ATTRIBUTES = state.STATE_ATTRIBUTES, TRAVERSAL_FLAGS = state.TRAVERSAL_FLAGS, CURRENCY_STATES = state.CURRENCY_STATES; + + module.exports = Region = (function(_super) { + var ABSTRACT, ACTIVE, AUTONOMOUS, BACKGROUNDED, BLOCKED, CONCLUSIVE, CONCURRENT, FINAL, FINALIZED, IMMEDIATE, JOINED, ORTHOGONAL, RETAINED, SINGULAR, SUSPENDED, TERMINATED, TRANSITIONING, VIA_NONE, VIA_PROTO, VIA_SUB, VIA_SUPER, VIRTUAL, VOID, VOLATILE, evaluateGuard, fail, invert, isArray, isEmpty, isPrototypeOf, regionStates, slice, trim, type, _ref, _ref1; + + __extends(Region, _super); + + isPrototypeOf = Object.prototype.isPrototypeOf; + + slice = Array.prototype.slice; + + trim = O.trim, type = O.type, isEmpty = O.isEmpty, isArray = O.isArray, invert = O.invert; + + fail = state.fail; + + _ref = (CONCURRENT = STATE_ATTRIBUTES.CONCURRENT, ORTHOGONAL = STATE_ATTRIBUTES.ORTHOGONAL, SINGULAR = STATE_ATTRIBUTES.SINGULAR, AUTONOMOUS = STATE_ATTRIBUTES.AUTONOMOUS, VOLATILE = STATE_ATTRIBUTES.VOLATILE, STATE_ATTRIBUTES), VIRTUAL = _ref.VIRTUAL, ABSTRACT = _ref.ABSTRACT, CONCLUSIVE = _ref.CONCLUSIVE, FINAL = _ref.FINAL, RETAINED = _ref.RETAINED, IMMEDIATE = _ref.IMMEDIATE; + + VIA_NONE = TRAVERSAL_FLAGS.VIA_NONE, VIA_SUB = TRAVERSAL_FLAGS.VIA_SUB, VIA_SUPER = TRAVERSAL_FLAGS.VIA_SUPER, VIA_PROTO = TRAVERSAL_FLAGS.VIA_PROTO; + + _ref1 = (JOINED = CURRENCY_STATES.JOINED, FINALIZED = CURRENCY_STATES.FINALIZED, TERMINATED = CURRENCY_STATES.TERMINATED, CURRENCY_STATES), VOID = _ref1.VOID, ACTIVE = _ref1.ACTIVE, BACKGROUNDED = _ref1.BACKGROUNDED, BLOCKED = _ref1.BLOCKED, TRANSITIONING = _ref1.TRANSITIONING, SUSPENDED = _ref1.SUSPENDED; + + regionStates = (function() { + var key, object, value, _ref2; + _ref2 = object = invert(CURRENCY_STATES); + for (key in _ref2) { + value = _ref2[key]; + object[key] = value.toLowerCase(); + } + return object; + })(); + + function Region(base, name, expression) { + Region.__super__.constructor.apply(this, arguments); + this._state = VOID; + this._current = null; + this._transition = null; + this._queue = this.attributes & IMMEDIATE ? null : []; + } + + Region.prototype.decodeState = function() { + var key, value, _state; + _state = this._state; + return ((function() { + var _results; + _results = []; + for (key in regionStates) { + value = regionStates[key]; + if (_state & key) { + _results.push(value); + } + } + return _results; + })()).join(' '); + }; + + Region.prototype.initialize = function() { + Region.__super__.initialize.apply(this, arguments); + this.activate(); + return this; + }; + + Region.prototype.activate = function(initialState) { + var attributes, current, _ref2, _ref3, _state; + _state = this._state, attributes = this.attributes; + if (_state & TERMINATED) { + throw new Error("'" + (this.path()) + "': incoming currency already TERMINATED"); + } + current = (_ref2 = (initialState != null ? this.query(initialState) : this.initialSubstate())) != null ? _ref2 : this; + if (current.attributes & ABSTRACT) { + current = (_ref3 = current.defaultSubstate()) != null ? _ref3 : current; + } + if (current.owner !== this.owner) { + current = current.virtualize(this); + } + this._current = current; + this._state = ACTIVE; + if (current.attributes & CONCURRENT) { + this.fork(current); + } + if (_state === SUSPENDED) { + this.emit('resume', current, VIA_PROTO); + } else if (_state === VOID) { + this.emit('initialize', current, VIA_PROTO); + } + return current; + }; + + Region.prototype.concurrencyDeactivated = function() { + var attributes, _current, _ref2, _state; + attributes = this.attributes, _current = this._current, _state = this._state; + if (_state & TERMINATED) { + return; + } + if (attributes & AUTONOMOUS) { + this._state = ACTIVE | BACKGROUNDED; + } else if (attributes & SINGULAR) { + this.finalize(); + } else if (attributes & VOLATILE) { + if ((_ref2 = this._transition) != null) { + _ref2.abort(); + } + if (_current.attributes & VIRTUAL) { + _current.destroy(); + } + this._current = null; + this._state = VOID; + } else { + this.suspend(); + } + }; + + Region.prototype.suspend = function() { + if (!(this._state & ACTIVE)) { + throw new Error("'" + (this.path()) + "': incoming currency must be ACTIVE"); + } + this._state = SUSPENDED; + this.emit('suspend', this._current, VIA_PROTO); + }; + + Region.prototype.resume = function() { + if (!(this._state & SUSPENDED)) { + throw new Error("'" + (this.path()) + "': incoming currency must be SUSPENDED"); + } + this._state = ACTIVE; + this.emit('resume', this._current, VIA_PROTO); + }; + + Region.prototype.finalize = function() { + var attributes, _current, _state; + _state = this._state, attributes = this.attributes, _current = this._current; + if (_state & TERMINATED) { + throw new Error("'" + (this.path()) + "': incoming currency already TERMINATED"); + } + _state = FINALIZED; + if (attributes & SINGULAR) { + _state |= TERMINATED; + } + this._state = _state; + this.emit('finalize', _current, VIA_PROTO); + if (_state & TERMINATED) { + this.emit('terminate', _current, VIA_PROTO); + } + }; + + Region.prototype.fork = function(target, transition) { + var name, owner, subregion, subregionState, subtrex, targetAttributes, _ref2, _ref3; + owner = this.owner; + targetAttributes = target.attributes; + if (!(targetAttributes & CONCURRENT)) { + throw new TypeError; + } + if (targetAttributes & VIRTUAL) { + target.realize(); + } + _ref2 = target.substates(VIA_PROTO); + for (name in _ref2) { + subregion = _ref2[name]; + if (subregion.owner !== owner) { + subregion = state.own(owner, subregion.path()); + } + subregionState = subregion._state; + if (subregionState & TERMINATED) { + continue; + } + if (subtrex = transition != null ? (_ref3 = transition.fork) != null ? _ref3[name] : void 0 : void 0) { + if (subregionState & VOID) { + subregion.activate(subtrex.target); + } + subregion["do"](subtrex); + } else { + if (subregionState & SUSPENDED) { + subregion.resume(); + } else { + subregion.activate(); + } + } + } + }; + + Region.prototype.join = function() { + var transition; + if (this === this.root) { + throw new TypeError; + } + if (this._state & TRANSITIONING) { + transition = this._current; + } + if (transition) { + + } + this.finalize(); + this._state |= JOINED; + }; + + evaluateGuard = function(context, guard, against) { + var args, key, result, selector, selectors, value, valueIsFn, _i, _len; + if (typeof guard === 'string') { + guard = context.guard(guard); + } + if (!guard) { + return true; + } + args = slice.call(arguments, 1); + for (key in guard) { + if (!__hasProp.call(guard, key)) continue; + value = guard[key]; + valueIsFn = typeof value === 'function'; + selectors = trim(key).split(/\s*,+\s*/); + for (_i = 0, _len = selectors.length; _i < _len; _i++) { + selector = selectors[_i]; + if (!(context.query(selector, against))) { + continue; + } + result = valueIsFn ? value.apply(context, args) : value; + break; + } + if (!result) { + break; + } + } + return !!result; + }; + + Region.prototype.isVoid = function() { + return !!(this._state & VOID); + }; + + Region.prototype.isActive = function() { + return !!(this._state & ACTIVE); + }; + + Region.prototype.isBackgrounded = function() { + return !!(this._state & BACKGROUNDED); + }; + + Region.prototype.isBlocked = function() { + return !!(this._state & BLOCKED); + }; + + Region.prototype.isTransitioning = function() { + return !!(this._state & TRANSITIONING); + }; + + Region.prototype.isSuspended = function() { + return !!(this._state & SUSPENDED); + }; + + Region.prototype.isJoined = function() { + return !!(this._state & JOINED); + }; + + Region.prototype.isFinalized = function() { + return !!(this._state & FINALIZED); + }; + + Region.prototype.isTerminated = function() { + return !!(this._state & TERMINATED); + }; + + Region.prototype.getTransitionExpression = (function() { + var search; + search = function(target, origin, subject, ceiling) { + var admit, expr, guards, key, release, _ref2, _ref3; + while (subject && subject !== ceiling) { + _ref3 = (_ref2 = subject._) != null ? _ref2.transitions : void 0; + for (key in _ref3) { + if (!__hasProp.call(_ref3, key)) continue; + expr = _ref3[key]; + if ((!(guards = expr.guards) || (!(admit = guards.admit) || isEmpty(admit) || evaluateGuard.call(origin, admit, target, origin)) && (!(release = guards.release) || isEmpty(release) || evaluateGuard.call(target, release, origin, target))) && (expr.target ? subject.query(expr.target, target) : subject === target) && (!expr.origin || subject.query(expr.origin, origin))) { + return expr; + } + } + if (ceiling == null) { + break; + } + subject = subject.superstate; + } + }; + return function(target, origin) { + return (search(target, origin, target)) || (origin !== target ? search(target, origin, origin) : void 0) || (search(target, origin, target.superstate, this.root)) || (search(target, origin, this.root)) || (!target.isIn(origin) ? search(target, origin, origin.superstate, origin.common(target)) : void 0) || null; + }; + })(); + + Region.prototype.determineTransitionTarget = (function() { + return function(trex, origin) { + var _ref2; + if (origin == null) { + origin = ((_ref2 = this._transition) != null ? _ref2.origin : void 0) || this._current; + } + if (typeof trex === 'string') { + return trex = origin.transition(trex, VIA_SUPER | VIA_PROTO); + } + }; + })(); + + Region.prototype.go = function(target, options, callback) { + var origin, transitionExpression, _ref2; + origin = ((_ref2 = this._transition) != null ? _ref2.origin : void 0) || this._current; + transitionExpression = this.getTransitionExpression(target, origin); + if (this.attributes & STRICT) { + if (transitionExpression == null) { + throw new Error("Undefined transition '" + origin + "' -> '" + target + "'"); + } + } else { + transitionExpression = defaultTransitionExpression; + } + return this.schedule(transitionExpression, target, options, callback); + }; + + Region.prototype["do"] = function(transitionExpression, options, callback) { + var origin, target, _ref2; + origin = ((_ref2 = this._transition) != null ? _ref2.origin : void 0) || this._current; + target = this.determineTransitionTarget(transitionExpression, origin); + if (target == null) { + throw new Error("Ambiguous target '" + origin + "' ':" + transitionExpression.name + "'"); + } + return this.schedule(transitionExpression, target, options, callback); + }; + + Region.prototype.schedule = function(transitionExpression, target, options, callback) { + if (~this.attributes & IMMEDIATE && this._state & TRANSITIONING) { + this._queue.push(arguments); + return; + } + return this.execute.apply(this, arguments); + }; + + Region.prototype.execute = function(transitionExpression, target, options, callback) { + var admitted, args, ascend, ascendant, current, descend, domain, name, origin, owner, previous, released, retainee, root, selector, source, subregion, symbol, targetOwner, transition, _ref2; + if (typeof options === 'function' && (callback == null)) { + callback = options; + options = void 0; + } + if (!(this._state & ACTIVE)) { + return fail({ + message: "Concurrency: inactive region '" + this + "'" + }, callback); + } + root = this.root, owner = this.owner; + current = this._current; + previous = this._transition; + origin = (previous != null ? previous.origin : void 0) || current; + source = previous || origin; + if (origin.attributes & FINAL) { + return fail({ + message: "Origin: state '" + origin + "' is final" + }, callback); + } + selector = target instanceof State ? target.path() : target; + target = origin.query(selector); + targetOwner = target != null ? target.owner : void 0; + if (!(owner === targetOwner || isPrototypeOf.call(targetOwner, owner))) { + return fail({ + message: "Target: invalid state '" + selector + "'" + }, callback); + } + if (options != null) { + args = (isArray(options)) || (type(options)) === 'arguments' ? options : options.args; + if (args != null) { + args = slice.call(args); + } + } + if (target.attributes & RETAINED) { + if (retainee = target.query(target._.retaineePath)) { + target = retainee; + } + } + while (target.attributes & ABSTRACT) { + if (!(target = target.defaultSubstate())) { + return fail({ + message: "Abstraction: no concrete target" + }, callback); + } + } + if (!(options != null ? options.forced : void 0)) { + released = evaluateGuard(origin, 'release', target); + admitted = evaluateGuard(target, 'admit', origin); + if (!(released && admitted)) { + symbol = !released && !admitted ? "|->|" : !released ? "| ->" : !admitted ? "-> |" : void 0; + return fail({ + message: "Guarded: '" + origin + "' " + symbol + " '" + target + "'" + }, callback); + } + } + if (origin.attributes & CONCURRENT) { + _ref2 = origin._.substates; + for (name in _ref2) { + subregion = _ref2[name]; + subregion.concurrencyDeactivated(); + } + } + if ((target != null ? target.root : void 0) !== root) { + target = target.virtualize(this); + if (target.attributes & CONCURRENT) { + target.realize(); + } + } + domain = source.common(target); + ascendant = source; + while (ascendant !== domain) { + if (ascendant.attributes & CONCLUSIVE) { + return fail({ + message: "Conclusive: cannot exit state '" + ascendant + "'" + }, callback); + } + ascendant = ascendant.superstate; + } + transition = new Transition(target, source, transitionExpression); + if (previous != null) { + previous.divertTo(transition); + } + ascend = function() { + var eventArgs, _ref3, _results; + _ref3 = transition = this._transition, origin = _ref3.origin, source = _ref3.source, domain = _ref3.domain; + eventArgs = [transition, args]; + source.emit('depart', eventArgs, VIA_PROTO); + this._current = this._transition = transition; + transition.emit('enter', VIA_NONE); + ascendant = source; + _results = []; + while (ascendant !== domain) { + if (ascendant.attributes & RETAINED) { + ascendant._.retaineePath = origin.path(); + } + ascendant.emit('exit', eventArgs, VIA_PROTO); + _results.push(transition.superstate = ascendant = ascendant.superstate); + } + return _results; + }; + return descend = function() { + var descendant, pathToState, _ref3; + _ref3 = transition = this._transition, origin = _ref3.origin, domain = _ref3.domain, target = _ref3.target; + if ((transition == null) || transition.aborted) { + return fail({ + message: "" + }, callback); + } + descendant = target; + pathToState = []; + while (descendant !== domain) { + pathToState.push(descendant); + descendant = descendant.superstate; + } + while (descendant = pathToState.pop()) { + transition.superstate = descendant; + descendant.emit('enter', eventArgs, VIA_PROTO); + if (descendant.attributes & CONCLUSIVE) { + this.emit('conclude', descendant, VIA_PROTO); + } + if (transition.aborted) { + return fail({ + message: "" + }, callback); + } + } + transition.emit('exit', VIA_NONE); + if (transition.aborted) { + return fail({ + message: "" + }, callback); + } + return this._current = target; + }; + }; + + Region.prototype.change = function(target, options) { + var admitted, args, current, domain, eventArgs, name, origin, owner, released, retainee, root, s, source, subregion, targetOwner, transition, _ref2, _ref3; + if (!(this._state & ACTIVE)) { + return null; + } + root = this.root, owner = this.owner; + current = this._current; + transition = this._transition; + origin = (transition != null ? transition.origin : void 0) || current; + if (origin.attributes & FINAL) { + return null; + } + if (!(target instanceof State)) { + target = target === '' ? root : origin.query(target); + } + if (target == null) { + return null; + } + targetOwner = target.owner; + if (owner !== targetOwner && !targetOwner.isPrototypeOf(owner)) { + return null; + } + if (options != null) { + args = (isArray(options)) || (type(options)) === 'arguments' ? options : options.args; + if (args != null) { + args = slice.call(args); + } + } + if (target.attributes & RETAINED) { + if (retainee = target.query(target._.retaineePath)) { + target = retainee; + } + } + while (target.attributes & ABSTRACT) { + if (!(target = target.defaultSubstate())) { + return null; + } + } + if (!(options != null ? options.forced : void 0)) { + released = evaluateGuard(origin, 'release', target); + admitted = evaluateGuard(target, 'admit', origin); + if (!(released && admitted)) { + if (options != null) { + if ((_ref2 = options.failure) != null) { + if (typeof _ref2.call === "function") { + _ref2.call(this); + } + } + } + return null; + } + } + if (origin.attributes & CONCURRENT) { + _ref3 = origin._.substates; + for (name in _ref3) { + subregion = _ref3[name]; + subregion.concurrencyDeactivated(); + } + } + if ((target != null ? target.root : void 0) !== root) { + target = target.virtualize(this); + if (target.attributes & CONCURRENT) { + target.realize(); + } + } + source = current; + domain = source.common(target); + s = source; + while (s !== domain) { + if (s.attributes & CONCLUSIVE) { + return null; + } + s = s.superstate; + } + if (transition != null) { + transition.abort(); + } + this._transition = transition = new Transition(target, source, (this.getTransitionExpression(target, origin)) || defaultTransitionExpression); + eventArgs = [transition, args]; + source.emit('depart', eventArgs, VIA_PROTO); + if (transition.aborted) { + this._transition = transition = null; + } + if (transition) { + this._current = transition; + transition.emit('enter', VIA_NONE); + if (transition.aborted) { + this._transition = transition = null; + } + } + s = source; + while (transition && s !== domain) { + if (s.attributes & RETAINED) { + s._.retaineePath = origin.path(); + } + s.emit('exit', eventArgs, VIA_PROTO); + transition.superstate = s = s.superstate; + if (transition.aborted) { + this._transition = transition = null; + } + } + if (transition != null) { + transition.callback = function() { + var pathToState, ss, substate, _ref4; + while (true) { + if (!((transition != null) && !transition.aborted)) { + break; + } + s = target; + pathToState = []; + while (s !== domain) { + pathToState.push(s); + s = s.superstate; + } + while (substate = pathToState.pop()) { + transition.superstate = substate; + substate.emit('enter', eventArgs, VIA_PROTO); + if (substate.attributes & CONCLUSIVE) { + this.emit('conclude', substate, VIA_PROTO); + } + if (transition.aborted) { + break; + } + } + transition.emit('exit', VIA_NONE); + if (transition.aborted) { + break; + } + this._current = target; + if (target.attributes & FINAL) { + this._state = this.attributes & SINGULAR ? FINALIZED | TERMINATED : FINALIZED; + } + target.emit('arrive', eventArgs, VIA_PROTO); + s = origin; + while (s.attributes & VIRTUAL) { + ss = s.superstate; + s.destroy(); + s = ss; + } + if (target.attributes & CONCURRENT) { + this.fork(target, transition); + } + transition.destroy(); + this._transition = null; + if (options != null) { + if ((_ref4 = options.success) != null) { + if (typeof _ref4.call === "function") { + _ref4.call(this); + } + } + } + return target; + } + return this._transition = null; + }; + } + return (transition != null ? transition.start.apply(transition, args) : void 0) || this._current; + }; + + return Region; + + })(State); + + StateExpression = require('./state-expression'); + + Transition = require('./transition'); + + TransitionExpression = require('./transition-expression'); + + defaultTransitionExpression = new TransitionExpression; + +}).call(this); diff --git a/lib/root-state.js b/lib/root-state.js new file mode 100644 index 0000000..27ddcca --- /dev/null +++ b/lib/root-state.js @@ -0,0 +1,277 @@ +// Generated by CoffeeScript 1.6.3 +(function() { + var O, RootState, State, StateExpression, Transition, TransitionExpression, state, + __hasProp = {}.hasOwnProperty, + __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + __slice = [].slice; + + O = require('omicron'); + + state = require('./state-function'); + + State = require('./state'); + + StateExpression = null; + + Transition = null; + + TransitionExpression = null; + + module.exports = RootState = (function(_super) { + var ABSTRACT, CONCLUSIVE, FINAL, RETAINED, VIA_NONE, VIA_PROTO, VIRTUAL, createAccessor, env, hasOwn, isArray, isEmpty, rxTransitionArrow, slice, transitionArrowMethods, trim, type; + + __extends(RootState, _super); + + rxTransitionArrow = state.rxTransitionArrow, transitionArrowMethods = state.transitionArrowMethods; + + env = O.env, hasOwn = O.hasOwn, trim = O.trim, type = O.type, isEmpty = O.isEmpty, isArray = O.isArray; + + slice = Array.prototype.slice; + + VIRTUAL = RootState.VIRTUAL, ABSTRACT = RootState.ABSTRACT, CONCLUSIVE = RootState.CONCLUSIVE, FINAL = RootState.FINAL, RETAINED = RootState.RETAINED; + + VIA_NONE = RootState.VIA_NONE, VIA_PROTO = RootState.VIA_PROTO; + + function RootState(owner, expression, accessorName, initialState) { + var current, _ref, _ref1; + if (owner == null) { + owner = {}; + } + this.accessorName = accessorName != null ? accessorName : accessorName = 'state'; + owner[accessorName] = createAccessor(owner, accessorName, this); + RootState.__super__.constructor.call(this, owner, '', expression); + current = (_ref = (initialState != null ? this.query(initialState) : this.initialSubstate())) != null ? _ref : this; + if (current.attributes & ABSTRACT) { + current = (_ref1 = current.defaultSubstate()) != null ? _ref1 : current; + } + if (current.root !== this) { + current = current.virtualize(this); + } + this._current = current; + this._transition = null; + } + + createAccessor = function(owner, name, root) { + var accessor; + accessor = function() { + var args, current, input, match, method; + input = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : []; + current = root._current || root; + if (this === owner) { + if (input == null) { + return current; + } + if (typeof input === 'function') { + return current.change(input.call(this)); + } + if (typeof input === 'string' && (match = input.match(rxTransitionArrow)) && (method = transitionArrowMethods[match[1]])) { + if (args.length) { + return current[method].apply(current, [match[2]].concat(args)); + } else { + return current[method](match[2]); + } + } + return current.query.apply(current, arguments); + } else if ((owner.isPrototypeOf(this)) && ((!hasOwn.call(this, name)) || this[name] === owner[name])) { + new RootState(this, null, name, current.path()); + return this[name].apply(this, arguments); + } + }; + accessor.isAccessor = true; + if (env.debug) { + accessor.toString = function() { + return "[accessor] -> " + (root._current.path()); + }; + } + return accessor; + }; + + RootState.prototype.getTransitionExpression = (function() { + var search; + search = function(target, origin, subject, ceiling) { + var admit, disallowedByGuard, expr, guards, name, originMatches, release, targetMatches, _ref, _ref1; + while ((subject != null) && subject !== ceiling) { + _ref1 = (_ref = subject._) != null ? _ref.transitions : void 0; + for (name in _ref1) { + if (!__hasProp.call(_ref1, name)) continue; + expr = _ref1[name]; + if (!(expr != null)) { + continue; + } + guards = expr.guards; + if (guards != null) { + admit = guards.admit, release = guards.release; + } + disallowedByGuard = (!(admit != null ? admit.evaluate(target, origin) : void 0)) || (!(release != null ? release.evaluate(origin, target) : void 0)); + targetMatches = expr.target ? subject.query(expr.target, target) : subject === target; + originMatches = !expr.origin || subject.query(expr.origin, origin); + if (targetMatches && originMatches && !disallowedByGuard) { + return expr; + } + } + if (ceiling == null) { + break; + } + subject = subject.superstate; + } + }; + return function(target, origin) { + if (origin == null) { + origin = this._current; + } + return (search(target, origin, target)) || (origin !== target ? search(target, origin, origin) : void 0) || (search(target, origin, target.superstate, this.root)) || (search(target, origin, this.root)) || (!target.isIn(origin) ? search(target, origin, origin.superstate, origin.common(target)) : void 0) || new TransitionExpression; + }; + })(); + + RootState.prototype.change = function(target, options) { + var admitted, args, current, domain, eventArgs, origin, owner, released, retainee, root, s, source, targetOwner, transition, _ref; + root = this.root, owner = this.owner; + current = this._current; + transition = this._transition; + origin = (transition != null ? transition.origin : void 0) || current; + if (origin.attributes & FINAL) { + return null; + } + if (!(target instanceof State)) { + target = target ? origin.query(target) : root; + } + if (!target) { + return null; + } + targetOwner = target.owner; + if (owner !== targetOwner && !targetOwner.isPrototypeOf(owner)) { + return null; + } + if (options != null) { + args = (isArray(options)) || (type(options)) === 'arguments' ? options : options.args; + if (args != null) { + args = slice.call(args); + } + } + if (target.attributes & RETAINED) { + if (retainee = target.query(target._.retaineePath)) { + target = retainee; + } + } + while (target.attributes & ABSTRACT) { + if (!(target = target.defaultSubstate())) { + return null; + } + } + if (!(options != null ? options.forced : void 0)) { + released = origin.evaluateGuards('release', target); + admitted = target.evaluateGuards('admit', origin); + if (!(released && admitted)) { + if (options != null) { + if ((_ref = options.failure) != null) { + if (typeof _ref.call === "function") { + _ref.call(this); + } + } + } + return null; + } + } + if ((target != null ? target.root : void 0) !== root) { + target = target.virtualize(this); + } + source = current; + domain = source.common(target); + s = source; + while (s !== domain) { + if (s.attributes & CONCLUSIVE) { + return null; + } + s = s.superstate; + } + if (transition != null) { + transition.abort(); + } + this._transition = transition = new Transition(target, source, this.getTransitionExpression(target, origin)); + eventArgs = [transition, args]; + source.emit('depart', eventArgs); + if (transition.aborted) { + this._transition = transition = null; + } + if (transition) { + this._current = transition; + transition.emit('enter', VIA_NONE); + if (transition.aborted) { + this._transition = transition = null; + } + } + s = source; + while (transition && s !== domain) { + if (s.attributes & RETAINED) { + s._.retaineePath = transition.origin.path(); + } + s.emit('exit', eventArgs); + transition.superstate = s = s.superstate; + if (transition.aborted) { + this._transition = transition = null; + } + } + if (transition != null) { + transition.callback = function() { + var pathToState, ss, substate, _ref1; + if (transition.aborted) { + transition = null; + } + if (transition) { + s = target; + pathToState = []; + while (s !== domain) { + pathToState.push(s); + s = s.superstate; + } + } + while (transition && (substate = pathToState.pop())) { + transition.superstate = substate; + substate.emit('enter', eventArgs); + if (transition.aborted) { + transition = null; + } + } + if (transition) { + transition.emit('exit', VIA_NONE); + if (transition.aborted) { + transition = null; + } + } + if (transition) { + this._current = target; + target.emit('arrive', eventArgs); + s = origin; + while (s.attributes & VIRTUAL) { + ss = s.superstate; + s.destroy(); + s = ss; + } + transition.destroy(); + this._transition = transition = null; + if (options != null) { + if ((_ref1 = options.success) != null) { + if (typeof _ref1.call === "function") { + _ref1.call(this); + } + } + } + return target; + } + return null; + }; + } + return (transition != null ? transition.start.apply(transition, args) : void 0) || this._current; + }; + + return RootState; + + })(State); + + StateExpression = require('./state-expression'); + + Transition = require('./transition'); + + TransitionExpression = require('./transition-expression'); + +}).call(this); diff --git a/lib/state-content.js b/lib/state-content.js new file mode 100644 index 0000000..ff0bc45 --- /dev/null +++ b/lib/state-content.js @@ -0,0 +1,28 @@ +// Generated by CoffeeScript 1.6.3 +(function() { + var StateContent, state; + + state = require('./state-function'); + + module.exports = StateContent = (function() { + var useDispatchTables; + + useDispatchTables = state.options.useDispatchTables; + + function StateContent() { + this.data = null; + this.methods = null; + this.events = null; + this.guards = null; + this.substates = null; + this.transitions = null; + if (useDispatchTables) { + this.__dispatch_table__ = null; + } + } + + return StateContent; + + })(); + +}).call(this); diff --git a/lib/state-event-emitter.js b/lib/state-event-emitter.js new file mode 100644 index 0000000..38af0b9 --- /dev/null +++ b/lib/state-event-emitter.js @@ -0,0 +1,167 @@ +// Generated by CoffeeScript 1.6.3 +(function() { + var O, State, StateEventEmitter, state, + __hasProp = {}.hasOwnProperty; + + state = require('./state-function'); + + State = require('./state'); + + O = state.O; + + module.exports = StateEventEmitter = (function() { + var guid, isArray; + + isArray = O.isArray; + + guid = 0; + + function StateEventEmitter(state) { + this.state = state; + this.items = {}; + this.length = 0; + } + + StateEventEmitter.prototype.get = function(id) { + return this.items[id]; + }; + + StateEventEmitter.prototype.getAll = function() { + var key, value, _ref, _results; + _ref = this.items; + _results = []; + for (key in _ref) { + if (!__hasProp.call(_ref, key)) continue; + value = _ref[key]; + _results.push(value); + } + return _results; + }; + + StateEventEmitter.prototype.set = function(id, callback) { + var items; + items = this.items; + if (!(id in items)) { + this.length += 1; + } + items[id] = callback; + return id; + }; + + StateEventEmitter.prototype.key = function(callback) { + var key, value; + if ((function() { + var _ref, _results; + _ref = this.items; + _results = []; + for (key in _ref) { + if (!__hasProp.call(_ref, key)) continue; + value = _ref[key]; + _results.push(value === callback); + } + return _results; + }).call(this)) { + return key; + } + }; + + StateEventEmitter.prototype.keys = function() { + var key, _ref, _results; + _ref = this.items; + _results = []; + for (key in _ref) { + if (!__hasProp.call(_ref, key)) continue; + _results.push(key); + } + return _results; + }; + + StateEventEmitter.prototype.add = function(callback, context, selector) { + var id; + id = guid += 1; + this.items[id] = (context != null) || (selector != null) ? [callback, context, selector] : callback; + this.length += 1; + return id; + }; + + StateEventEmitter.prototype.on = StateEventEmitter.prototype.bind = StateEventEmitter.prototype.add; + + StateEventEmitter.prototype.remove = function(id) { + var callback, items; + items = this.items; + callback = items[typeof id === 'function' ? this.key(id) : id]; + if (!callback) { + return false; + } + delete items[id]; + this.length -= 1; + return callback; + }; + + StateEventEmitter.prototype.off = StateEventEmitter.prototype.unbind = StateEventEmitter.prototype.remove; + + StateEventEmitter.prototype.empty = function() { + var n; + if (!(n = this.length)) { + return 0; + } + this.items = {}; + this.length = 0; + return n; + }; + + StateEventEmitter.prototype.emit = function(args, clientState, origin) { + var context, eventualTarget, fn, item, key, owner, selector, _ref, _ref1; + if (clientState == null) { + clientState = this.state; + } + if ((owner = clientState != null ? clientState.owner : void 0) == null) { + throw TypeError; + } + _ref = this.items; + for (key in _ref) { + if (!__hasProp.call(_ref, key)) continue; + item = _ref[key]; + fn = context = selector = null; + if (typeof item === 'string' || item instanceof State) { + eventualTarget = item; + continue; + } + if (isArray(item)) { + _ref1 = item, item = _ref1[0], context = _ref1[1], selector = _ref1[2]; + } + if (selector != null) { + if (!clientState.query(selector, origin)) { + continue; + } + } else { + if (clientState !== origin) { + continue; + } + } + if (typeof item === 'function') { + fn = item; + } else if ((item != null ? item.type : void 0) === 'state-bound-function') { + fn = item.fn; + context || (context = selector != null ? origin : clientState); + } + fn.apply(context || owner, args); + } + if (eventualTarget) { + return this.state.change(eventualTarget); + } + }; + + StateEventEmitter.prototype.trigger = StateEventEmitter.prototype.emit; + + StateEventEmitter.prototype.destroy = function() { + this.empty(); + this.state = this.items = null; + return true; + }; + + return StateEventEmitter; + + })(); + +}).call(this); diff --git a/lib/state-expression.js b/lib/state-expression.js new file mode 100644 index 0000000..eca9a9f --- /dev/null +++ b/lib/state-expression.js @@ -0,0 +1,212 @@ +// Generated by CoffeeScript 1.6.3 +(function() { + var GUARD_ACTIONS, GuardMap, O, STATE_ATTRIBUTES, STATE_ATTRIBUTE_MODIFIERS, STATE_EVENT_TYPES, STATE_EXPRESSION_CATEGORIES, STATE_EXPRESSION_CATEGORY_SYNONYMS, State, StateExpression, TransitionExpression, state, + __hasProp = {}.hasOwnProperty; + + O = require('omicron'); + + state = require('./state-function'); + + State = require('./state'); + + GuardMap = require('./guard-map'); + + TransitionExpression = require('./transition-expression'); + + STATE_ATTRIBUTES = state.STATE_ATTRIBUTES, STATE_ATTRIBUTE_MODIFIERS = state.STATE_ATTRIBUTE_MODIFIERS, STATE_EXPRESSION_CATEGORIES = state.STATE_EXPRESSION_CATEGORIES, STATE_EXPRESSION_CATEGORY_SYNONYMS = state.STATE_EXPRESSION_CATEGORY_SYNONYMS, STATE_EVENT_TYPES = state.STATE_EVENT_TYPES, GUARD_ACTIONS = state.GUARD_ACTIONS; + + module.exports = StateExpression = (function() { + var NIL, NORMAL, assign, attributeFlags, attributeMap, categoryMap, clone, decode, edit, encode, eventTypes, guardActions, interpret, invert, isArray, isNumber, isPlainObject, rxDelegatedEvent, synonymMap, untype; + + NIL = O.NIL, isNumber = O.isNumber, isPlainObject = O.isPlainObject, isArray = O.isArray; + + assign = O.assign, edit = O.edit, clone = O.clone, invert = O.invert; + + rxDelegatedEvent = state.rxDelegatedEvent; + + NORMAL = STATE_ATTRIBUTES.NORMAL; + + attributeMap = (function() { + var key, object, value, _ref; + _ref = object = assign(STATE_ATTRIBUTE_MODIFIERS); + for (key in _ref) { + value = _ref[key]; + object[key] = key.toUpperCase(); + } + return object; + })(); + + attributeFlags = (function() { + var key, object, value, _ref; + _ref = object = invert(STATE_ATTRIBUTES); + for (key in _ref) { + value = _ref[key]; + object[key] = value.toLowerCase(); + } + return object; + })(); + + categoryMap = assign(STATE_EXPRESSION_CATEGORIES); + + synonymMap = STATE_EXPRESSION_CATEGORY_SYNONYMS; + + eventTypes = assign(STATE_EVENT_TYPES); + + guardActions = assign(GUARD_ACTIONS); + + function StateExpression(attributes, expr) { + if (typeof attributes === 'string') { + expr || (expr = {}); + } else if (expr == null) { + expr = attributes; + attributes = void 0; + } + if (!(expr instanceof StateExpression)) { + expr = interpret(expr); + } + edit('deep all', this, expr); + if (attributes != null) { + if (!isNumber(attributes)) { + attributes = encode(attributes); + } + } else { + if (expr) { + attributes = expr.attributes; + } + } + this.attributes = attributes || NORMAL; + } + + interpret = function(expr) { + var category, item, key, object, result, type, value, _ref, _ref1, _ref2, _ref3; + result = assign(STATE_EXPRESSION_CATEGORIES, null); + for (key in expr) { + if (!__hasProp.call(expr, key)) continue; + value = expr[key]; + category = categoryMap[key] || synonymMap[key]; + if ((category != null) && (value != null)) { + result[category] = typeof value === 'string' ? value : isArray(value) ? value.slice(0) : clone(result[category], value); + continue; + } + if (value === state || value instanceof StateExpression) { + category = 'substates'; + } else if (value instanceof TransitionExpression) { + category = 'transitions'; + } + if (category != null) { + item = result[category] || (result[category] = {}); + item[key] = value; + continue; + } + if ((eventTypes[key] != null) || rxDelegatedEvent.test(key)) { + category = 'events'; + } else if (guardActions[key] != null) { + category = 'guards'; + } else if (typeof value === 'function' || (type = value != null ? value.type : void 0) && (type === 'state-bound-function' || type === 'state-fixed-function')) { + category = 'methods'; + } else if (value === NIL || isPlainObject(value)) { + category = 'substates'; + } else if (typeof value === 'string') { + category = 'events'; + } + if (category != null) { + item = result[category] || (result[category] = {}); + item[key] = value; + continue; + } + } + _ref = object = result.events; + for (key in _ref) { + if (!__hasProp.call(_ref, key)) continue; + value = _ref[key]; + if (!isArray(value)) { + object[key] = [value]; + } + } + _ref1 = object = result.guards; + for (key in _ref1) { + if (!__hasProp.call(_ref1, key)) continue; + value = _ref1[key]; + if (!(value === NIL || value instanceof GuardMap)) { + object[key] = new GuardMap(key, value); + } + } + _ref2 = object = result.transitions; + for (key in _ref2) { + if (!__hasProp.call(_ref2, key)) continue; + value = _ref2[key]; + if (!(value === NIL || value instanceof TransitionExpression)) { + object[key] = new TransitionExpression(value); + } + } + _ref3 = object = result.substates; + for (key in _ref3) { + if (!__hasProp.call(_ref3, key)) continue; + value = _ref3[key]; + if (value === state) { + object[key] = new StateExpression; + } else if (value instanceof State) { + object[key] = value.express(true); + } else if (!(value === NIL || value instanceof StateExpression)) { + object[key] = new StateExpression(value); + } + } + return result; + }; + + encode = function(attributes) { + var key, result, value; + if (typeof attributes === 'string') { + attributes = assign(attributes); + } + result = NORMAL; + for (key in attributes) { + if (!__hasProp.call(attributes, key)) continue; + value = attributes[key]; + if (key in attributeMap) { + result |= STATE_ATTRIBUTES[attributeMap[key]]; + } + } + return result; + }; + + decode = function(number) { + var key, value; + return ((function() { + var _results; + _results = []; + for (key in attributeFlags) { + value = attributeFlags[key]; + if (number & key) { + _results.push(value); + } + } + return _results; + })()).join(' '); + }; + + StateExpression.untype = untype = function(expr) { + var key, name, result, s, subexpr, value, _ref; + result = {}; + for (key in expr) { + if (!__hasProp.call(expr, key)) continue; + value = expr[key]; + result[key] = value; + } + _ref = s = result.states; + for (name in _ref) { + subexpr = _ref[name]; + s[name] = untype(subexpr); + } + return result; + }; + + StateExpression.encodeAttributes = encode; + + StateExpression.decodeAttributes = decode; + + return StateExpression; + + })(); + +}).call(this); diff --git a/lib/state-function.js b/lib/state-function.js new file mode 100644 index 0000000..eaac060 --- /dev/null +++ b/lib/state-function.js @@ -0,0 +1,57 @@ +// Generated by CoffeeScript 1.6.3 +(function() { + var RootState, StateExpression, define, implement, state; + + RootState = null; + + StateExpression = null; + + module.exports = state = function(owner, attributes, expression, options) { + if (arguments.length < 2) { + if (typeof owner === 'string') { + attributes = owner; + } else { + expression = owner; + } + owner = void 0; + } else { + if (typeof owner === 'string') { + options = expression; + expression = attributes; + attributes = owner; + owner = void 0; + } + if (typeof attributes !== 'string') { + options = expression; + expression = attributes; + attributes = void 0; + } + } + if (owner != null) { + return implement(owner, attributes, expression, options); + } else { + return define(attributes, expression); + } + }; + + state.define = define = function(attributes, expression) { + return new StateExpression(attributes, expression); + }; + + state.implement = implement = function(owner, attributes, expression, options) { + var initial, name, root, stateExpression; + if (options != null) { + name = options.name, initial = options.initial; + } + stateExpression = new StateExpression(attributes, expression); + root = new RootState(owner, stateExpression, name, initial); + return root._current; + }; + + (require('./export-static')).apply(state); + + RootState = require('./root-state'); + + StateExpression = require('./state-expression'); + +}).call(this); diff --git a/lib/state-metaobject.js b/lib/state-metaobject.js new file mode 100644 index 0000000..b7631d0 --- /dev/null +++ b/lib/state-metaobject.js @@ -0,0 +1,30 @@ +// Generated by CoffeeScript 1.6.3 +(function() { + var StateMetaobject, state; + + state = require('./state-function'); + + module.exports = StateMetaobject = (function() { + var useDispatchTables; + + useDispatchTables = state.options.useDispatchTables; + + function StateMetaobject() { + this.parastates = null; + this.data = null; + this.methods = null; + this.events = null; + this.guards = null; + this.substates = null; + this.transitions = null; + this.retaineePath = null; + if (useDispatchTables) { + this.__dispatch_table__ = null; + } + } + + return StateMetaobject; + + })(); + +}).call(this); diff --git a/lib/state.js b/lib/state.js new file mode 100644 index 0000000..9149ae0 --- /dev/null +++ b/lib/state.js @@ -0,0 +1,1721 @@ +// Generated by CoffeeScript 1.6.3 +(function() { + var GuardMap, O, STATE_ATTRIBUTES, State, StateEventEmitter, StateExpression, TRAVERSAL_FLAGS, TransitionExpression, state, + __hasProp = {}.hasOwnProperty, + __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }, + __slice = [].slice; + + O = require('omicron'); + + state = require('./state-function'); + + StateEventEmitter = null; + + GuardMap = null; + + StateExpression = null; + + TransitionExpression = null; + + STATE_ATTRIBUTES = state.STATE_ATTRIBUTES, TRAVERSAL_FLAGS = state.TRAVERSAL_FLAGS; + + module.exports = State = (function() { + var ABSTRACT, ABSTRACT_OR_CONCRETE, ATOMIC, CONCLUSIVE, CONCRETE, CONCURRENT, DEFAULT, DESTROYED, FINAL, FINITE, HISTORY, IMMUTABLE, INCIPIENT, INCIPIENT_OR_MUTABLE, INCIPIENT_OR_VIRTUAL, INITIAL, MUTABLE, MUTABLE_OR_FINITE, NIL, NORMAL, PARASTATIC, PROTO_HERITABLE_ATTRIBUTES, REFLECTIVE, RETAINED, SHALLOW, STATIC, VIA_ALL, VIA_NONE, VIA_PROTO, VIA_SUB, VIA_SUPER, VIA_VIRTUAL, VIRTUAL, VIRTUAL_EXPRESSION, assign, clone, createDispatcher, delta, edit, env, flatten, has, hasOwn, isArray, isEmpty, lookup, memoizeProtostates, rxDelegatedEvent, useDispatchTables, _ref, _ref1, _ref2; + + rxDelegatedEvent = state.rxDelegatedEvent; + + _ref = state.options, memoizeProtostates = _ref.memoizeProtostates, useDispatchTables = _ref.useDispatchTables; + + env = O.env, NIL = O.NIL, isArray = O.isArray, isEmpty = O.isEmpty, has = O.has, hasOwn = O.hasOwn; + + assign = O.assign, edit = O.edit, delta = O.delta, clone = O.clone, lookup = O.lookup, flatten = O.flatten; + + _ref1 = assign(State, STATE_ATTRIBUTES), INCIPIENT = _ref1.INCIPIENT, ATOMIC = _ref1.ATOMIC, DESTROYED = _ref1.DESTROYED, VIRTUAL = _ref1.VIRTUAL, PARASTATIC = _ref1.PARASTATIC, MUTABLE = _ref1.MUTABLE, FINITE = _ref1.FINITE, STATIC = _ref1.STATIC, IMMUTABLE = _ref1.IMMUTABLE, INITIAL = _ref1.INITIAL, CONCLUSIVE = _ref1.CONCLUSIVE, FINAL = _ref1.FINAL, ABSTRACT = _ref1.ABSTRACT, CONCRETE = _ref1.CONCRETE, DEFAULT = _ref1.DEFAULT, REFLECTIVE = _ref1.REFLECTIVE, HISTORY = _ref1.HISTORY, RETAINED = _ref1.RETAINED, SHALLOW = _ref1.SHALLOW, CONCURRENT = _ref1.CONCURRENT, NORMAL = _ref1.NORMAL; + + _ref2 = assign(State, TRAVERSAL_FLAGS), VIA_NONE = _ref2.VIA_NONE, VIA_SUB = _ref2.VIA_SUB, VIA_SUPER = _ref2.VIA_SUPER, VIA_PROTO = _ref2.VIA_PROTO, VIA_VIRTUAL = _ref2.VIA_VIRTUAL, VIA_ALL = _ref2.VIA_ALL; + + MUTABLE_OR_FINITE = MUTABLE | FINITE; + + ABSTRACT_OR_CONCRETE = ABSTRACT | CONCRETE; + + INCIPIENT_OR_VIRTUAL = INCIPIENT | VIRTUAL; + + INCIPIENT_OR_MUTABLE = INCIPIENT | MUTABLE; + + PROTO_HERITABLE_ATTRIBUTES = PARASTATIC | MUTABLE | FINITE | STATIC | IMMUTABLE | INITIAL | CONCLUSIVE | FINAL | ABSTRACT | CONCRETE | DEFAULT | REFLECTIVE | HISTORY | RETAINED | SHALLOW | CONCURRENT | NORMAL; + + VIRTUAL_EXPRESSION = { + attributes: VIRTUAL + }; + + State.prototype.Metaobject = null; + + State.prototype.Expression = null; + + function State(base, name, expression) { + var attributes, owner, protoAttr, protostate, root, superAttr, superstate; + this.name = name; + if (base instanceof State) { + superstate = base; + root = superstate.root; + owner = root.owner; + } else { + superstate = null; + root = this; + owner = base; + } + this.owner = owner; + this.root = root; + this.superstate = superstate; + this.protostate = protostate = this.getProtostate() || null; + this.order = null; + attributes = (expression != null ? expression.attributes : void 0) || NORMAL; + if (superstate != null) { + superAttr = superstate.attributes; + attributes |= superAttr & MUTABLE_OR_FINITE; + } + if (protostate != null) { + protoAttr = protostate.attributes & PROTO_HERITABLE_ATTRIBUTES; + if (attributes & CONCRETE) { + attributes &= ~ABSTRACT; + } + if (attributes & ABSTRACT_OR_CONCRETE) { + protoAttr &= ~ABSTRACT_OR_CONCRETE; + } + attributes |= protoAttr; + } + if (~attributes & ABSTRACT) { + attributes |= CONCRETE; + } + attributes |= (superAttr | protoAttr) & IMMUTABLE; + if (attributes & IMMUTABLE) { + attributes = attributes & ~MUTABLE | FINITE; + } + this.attributes = attributes; + if (~attributes & VIRTUAL || attributes & RETAINED) { + this.initialize(expression); + } + if (env.debug) { + this[' '] = this.path(); + this[''] = StateExpression.decodeAttributes(attributes); + } + } + + createDispatcher = (function() { + var toString; + toString = function() { + return "[dispatcher]"; + }; + return function(accessorName, methodName, original) { + var dispatcher; + dispatcher = function() { + return this[accessorName]().apply(methodName, arguments); + }; + dispatcher.isDispatcher = true; + if (env.debug) { + dispatcher.toString = toString; + } + if (original) { + dispatcher.original = original; + } + return dispatcher; + }; + })(); + + State.prototype.initialize = function(expression) { + this.attributes |= INCIPIENT; + this.realize(expression); + this.attributes &= ~INCIPIENT; + this.emit('construct', expression, VIA_PROTO); + return this; + }; + + State.prototype.realize = function(expression) { + var attributes, key, method, name, parastates, ss, substates, _base, _ref3; + attributes = this.attributes, name = this.name; + if (!(attributes & INCIPIENT_OR_VIRTUAL)) { + return this; + } + if (attributes & VIRTUAL) { + if (ss = this.superstate) { + if (ss.attributes & VIRTUAL) { + ss.realize(); + } + substates = (_base = ss._).substates != null ? (_base = ss._).substates : _base.substates = {}; + if (substates[name] != null) { + substates[name].destroy(); + } + substates[name] = this; + } + this.attributes &= ~VIRTUAL; + } + if (this._ == null) { + this._ = new this.Metaobject; + } + if (parastates = expression != null ? expression.parastates : void 0) { + if (isArray(parastates)) { + parastates = parastates.join(','); + } + if (typeof parastates !== 'string') { + throw TypeError; + } + parastates = parastates.split(/\s*,\s*/); + if (parastates.length) { + this._.parastates = parastates; + this.attributes |= PARASTATIC; + } + } + if (expression != null) { + this.mutate(expression); + } + if (this === this.root) { + _ref3 = this.owner; + for (key in _ref3) { + if (!__hasProp.call(_ref3, key)) continue; + method = _ref3[key]; + if (key !== 'constructor' && typeof method === 'function' && !method.isDispatcher && this.method(key, VIA_PROTO)) { + this.addMethod(key, method); + } + } + } + if (this === this.root || ~attributes & INCIPIENT) { + this.linearize(VIA_SUB); + } + return this; + }; + + State.prototype.virtualize = function(inheritor) { + var derivation, i, name, real, s; + if (!(inheritor instanceof State && this.owner.isPrototypeOf(inheritor.owner))) { + return null; + } + if (!(derivation = this.derivation(true)).length) { + return null; + } + i = 0; + s = inheritor.root; + while (name = derivation[i++]) { + if (!(real = s.substate(name, VIA_NONE))) { + break; + } + s = real; + } + while (name) { + s = new State(s, name, VIRTUAL_EXPRESSION); + name = derivation[i++]; + } + return s; + }; + + State.prototype.linearize = (function() { + var getParastateDeclarations, linearize, merge; + getParastateDeclarations = function() { + var head, ps, tail, _ref3, _ref4; + head = (_ref3 = this._) != null ? _ref3.parastates : void 0; + if (ps = this.protostate) { + tail = getParastateDeclarations.call(ps); + } + if (tail != null) { + return (_ref4 = head != null ? head.concat(tail) : void 0) != null ? _ref4 : tail; + } else { + return head; + } + }; + merge = function(out, lists) { + var bad, head, headList, i, index, item, list, otherList, remainingLists, _i, _j, _k, _len, _len1, _len2; + if (!lists.length) { + return out; + } + for (index = _i = 0, _len = lists.length; _i < _len; index = ++_i) { + headList = lists[index]; + if (!(headList != null)) { + continue; + } + head = headList[0]; + bad = false; + for (_j = 0, _len1 = lists.length; _j < _len1; _j++) { + otherList = lists[_j]; + if (!(otherList !== headList)) { + continue; + } + i = 1; + while (item = otherList[i++]) { + if (item === head) { + bad = true; + break; + } + } + if (bad) { + break; + } + } + if (bad) { + continue; + } + out.push(head); + remainingLists = []; + for (_k = 0, _len2 = lists.length; _k < _len2; _k++) { + list = lists[_k]; + if (list[0] === head) { + list.shift(); + } + if (list.length) { + remainingLists.push(list); + } + } + return merge(out, remainingLists); + } + throw new TypeError("Ambiguous resolution order for '" + (out.pop()) + "'"); + }; + return linearize = function(via) { + var lists, name, order, owner, parastate, parent, parents, path, paths, s, _i, _j, _len, _len1, _ref3, _ref4; + if (via == null) { + via = VIA_NONE; + } + if (this === this.root) { + order = [this]; + } else { + parents = []; + if (paths = getParastateDeclarations.call(this)) { + owner = this.owner; + for (_i = 0, _len = paths.length; _i < _len; _i++) { + path = paths[_i]; + if (!(parastate = state.own(owner, path))) { + throw new ReferenceError("Unresolvable parastate '" + path + "'"); + } + if (__indexOf.call(parents, parastate) < 0) { + parents.push(parastate); + } + } + } + parents.push(this.superstate); + lists = []; + for (_j = 0, _len1 = parents.length; _j < _len1; _j++) { + parent = parents[_j]; + lists.push(((_ref3 = parent.order) != null ? _ref3 : parent.linearize()).slice(0)); + } + lists.push(parents); + order = merge([this], lists); + } + this.order = order; + if (via & VIA_SUB) { + _ref4 = this._.substates; + for (name in _ref4) { + if (!__hasProp.call(_ref4, name)) continue; + s = _ref4[name]; + s.linearize(via); + } + } + return order; + }; + })(); + + State.prototype.express = (function() { + var cloneCategory, cloneEvents, cloneSubstates, express; + cloneCategory = function(object) { + var key, out, value; + if (object == null) { + return; + } + for (key in object) { + out = {}; + break; + } + if (out) { + for (key in object) { + value = object[key]; + out[key] = value && typeof value === 'object' ? clone(value) : value; + } + } + return out; + }; + cloneEvents = function(events) { + var emitter, out, type; + if (events == null) { + return; + } + for (type in events) { + emitter = events[type]; + if (emitter) { + out = {}; + break; + } + } + for (type in events) { + emitter = events[type]; + if (emitter) { + out[type] = clone(emitter.items); + } + } + return out; + }; + cloneSubstates = function(substates, typed) { + var name, out, substate; + if (substates == null) { + return; + } + for (name in substates) { + out = {}; + break; + } + for (name in substates) { + substate = substates[name]; + out[name] = substate.express(typed); + } + return out; + }; + return express = function(typed) { + var expression, _; + if (_ = this._) { + expression = edit({}, { + attributes: this.attributes, + data: cloneCategory(_.data), + methods: cloneCategory(_.methods), + events: cloneEvents(_.events), + guards: cloneCategory(_.guards), + states: cloneSubstates(_.substates, typed), + transitions: cloneCategory(_.transitions) + }); + } + if (typed) { + return new this.Expression(expression); + } else { + return expression; + } + }; + })(); + + State.prototype.mutate = (function() { + var diff, editEvent, isPlainObject, mutate; + NIL = O.NIL, isArray = O.isArray, isEmpty = O.isEmpty, isPlainObject = O.isPlainObject, edit = O.edit, diff = O.diff; + editEvent = function(object, emitter) { + var items, key, value, _results; + items = emitter.items; + _results = []; + for (key in object) { + if (!__hasProp.call(object, key)) continue; + value = object[key]; + if (value === NIL) { + _results.push(emitter.remove(key)); + } else if (value && value !== items[key]) { + _results.push(emitter.set(key, value)); + } else { + _results.push(void 0); + } + } + return _results; + }; + return mutate = function(expr) { + var Expression, after, attributes, before, data, element, emitter, event, events, guards, incipient, map, method, methods, mutable, name, residue, stateExpr, substates, transitionExpr, transitions, type, _base, _i, _len, _ref3, _ref4, _ref5, _ref6, _ref7, _ref8; + attributes = this.attributes, Expression = this.Expression; + incipient = attributes & INCIPIENT; + if (!incipient && attributes & IMMUTABLE) { + return; + } + mutable = incipient || attributes & MUTABLE; + if (attributes & VIRTUAL) { + this.realize(); + } + _ref3 = this._, data = _ref3.data, methods = _ref3.methods, events = _ref3.events, guards = _ref3.guards, substates = _ref3.substates, transitions = _ref3.transitions; + if (!(expr instanceof Expression)) { + expr = new Expression(expr); + } + if (!incipient) { + before = this.express(); + } + this.attributes |= ATOMIC; + if (expr.data) { + this.data(expr.data); + } + if (mutable) { + _ref4 = expr.methods; + for (name in _ref4) { + if (!__hasProp.call(_ref4, name)) continue; + method = _ref4[name]; + if (method !== NIL) { + this.addMethod(name, method); + } else { + this.removeMethod(name); + } + } + } + if (mutable) { + _ref5 = expr.events; + for (type in _ref5) { + if (!__hasProp.call(_ref5, type)) continue; + event = _ref5[type]; + events || (events = (_base = this._).events || (_base.events = {})); + emitter = events[type]; + if (event === NIL) { + if (emitter != null) { + emitter.empty(); + } + continue; + } + if (!emitter && event && !isEmpty(event)) { + emitter = events[type] = new StateEventEmitter(this, type); + } + if (isArray(event)) { + for (_i = 0, _len = event.length; _i < _len; _i++) { + element = event[_i]; + if ((element != null) && element !== NIL) { + if (isPlainObject(element)) { + editEvent(element, emitter); + } else { + this.addEvent(type, element); + } + } + } + } else { + if (isPlainObject(event)) { + editEvent(event, emitter); + } + } + if (!emitter.length) { + emitter.destroy(); + delete events[type]; + } + } + } + if (mutable) { + _ref6 = expr.guards; + for (type in _ref6) { + if (!__hasProp.call(_ref6, type)) continue; + map = _ref6[type]; + if (map === NIL) { + this.removeGuards(type); + } else { + this.addGuards(type, map); + } + } + } + _ref7 = expr.substates; + for (name in _ref7) { + if (!__hasProp.call(_ref7, name)) continue; + stateExpr = _ref7[name]; + if (substates && name in substates) { + if (stateExpr === NIL) { + this.removeSubstate(name); + } else { + substates[name].mutate(stateExpr); + } + } else { + if (stateExpr !== NIL) { + this.addSubstate(name, stateExpr); + } + } + } + if (mutable) { + _ref8 = expr.transitions; + for (name in _ref8) { + if (!__hasProp.call(_ref8, name)) continue; + transitionExpr = _ref8[name]; + if (transitions && name in transitions) { + if (transitionExpr === NIL) { + delete transitions[name]; + } else { + transitions[name] = new TransitionExpression(transitionExpr); + } + } else { + if (transitionExpr !== NIL) { + this.addTransition(name, transitionExpr); + } + } + } + } + this.attributes &= ~ATOMIC; + if (!incipient) { + after = this.express(); + residue = diff(before, after); + if (!isEmpty(residue)) { + this.emit('mutate', [expr, residue, before, after], VIA_PROTO); + } + } + return this; + }; + })(); + + State.prototype.destroy = function() { + var dispatcher, event, events, key, methods, name, owner, ownerMethod, root, substate, substates, superstate, transition, _; + owner = this.owner, root = this.root, superstate = this.superstate, _ = this._; + if (_) { + methods = _.methods, events = _.events, substates = _.substates; + } + if (transition = root._transition) { + if (this === root) { + transition.abort(); + } else { + if ((transition.origin.isIn(this)) || (transition.target.isIn(this))) { + return false; + } + } + } + for (name in substates) { + if (!__hasProp.call(substates, name)) continue; + substate = substates[name]; + substate.destroy(); + } + this.emit('destroy', VIA_PROTO); + if (events) { + for (key in events) { + event = events[key]; + event.destroy(); + delete events[key]; + } + } + if (this === root) { + for (name in methods) { + if (!(dispatcher = owner[name])) { + continue; + } + if (!dispatcher.isDispatcher) { + continue; + } + if (ownerMethod = dispatcher.original) { + owner[name] = ownerMethod; + } else { + delete owner[name]; + } + } + delete owner[this.accessorName]; + } + this._ = null; + this.attributes |= DESTROYED; + if (superstate != null) { + superstate.removeSubstate(this.name); + } + return true; + }; + + State.prototype.isVirtual = function() { + return !!(this.attributes & VIRTUAL); + }; + + State.prototype.isMutable = function() { + return !!(this.attributes & MUTABLE); + }; + + State.prototype.isFinite = function() { + return !!(this.attributes & FINITE); + }; + + State.prototype.isStatic = function() { + return !!(this.attributes & STATIC); + }; + + State.prototype.isImmutable = function() { + return !!(this.attributes & IMMUTABLE); + }; + + State.prototype.isInitial = function() { + return !!(this.attributes & INITIAL); + }; + + State.prototype.isConclusive = function() { + return !!(this.attributes & CONCLUSIVE); + }; + + State.prototype.isFinal = function() { + return !!(this.attributes & FINAL); + }; + + State.prototype.isAbstract = function() { + return !!(this.attributes & ABSTRACT); + }; + + State.prototype.isConcrete = function() { + return !!(this.attributes & CONCRETE); + }; + + State.prototype.isDefault = function() { + return !!(this.attributes & DEFAULT); + }; + + State.prototype.isReflective = function() { + return !!(this.attributes & REFLECTIVE); + }; + + State.prototype.hasHistory = function() { + return !!(this.attributes & HISTORY); + }; + + State.prototype.isRetained = function() { + return !!(this.attributes & RETAINED); + }; + + State.prototype.isShallow = function() { + return !!(this.attributes & SHALLOW); + }; + + State.prototype.isConcurrent = function() { + return !!(this.attributes & CONCURRENT); + }; + + State.prototype.substate = function(name, via) { + var s, ss, _ref3, _ref4, _ref5, _ref6; + if (via == null) { + via = VIA_PROTO; + } + s = ((_ref3 = this.root._transition) != null ? _ref3.superstate : void 0) || this.root._current; + while ((s != null) && s.attributes & VIRTUAL && (ss = s.superstate)) { + if (ss === this && s.name === name) { + return s; + } + s = ss; + } + return ((_ref4 = this._) != null ? (_ref5 = _ref4.substates) != null ? _ref5[name] : void 0 : void 0) || via & VIA_PROTO && ((_ref6 = this.protostate) != null ? _ref6.substate(name) : void 0); + }; + + State.prototype.substates = function(via, out) { + var name, s, ss, substate, viaSub, virtualDescendants, _ref3, _ref4, _ref5; + if (via == null) { + via = VIA_NONE; + } + if (out == null) { + out = {}; + } + viaSub = via & VIA_SUB; + if (via & VIA_PROTO) { + if ((_ref3 = this.protostate) != null) { + _ref3.substates(via, out); + } + } + if (via & VIA_VIRTUAL && (s = this.root._current) && s.attributes & VIRTUAL && this.isSuperstateOf(s)) { + virtualDescendants = []; + while (s !== this && s.attributes & VIRTUAL && (ss = s.superstate)) { + virtualDescendants.push(s); + s = ss; + } + while (s = virtualDescendants.pop()) { + if (viaSub) { + out[s.path()] = s; + } else { + out[s.name] = s; + break; + } + } + } + _ref5 = (_ref4 = this._) != null ? _ref4.substates : void 0; + for (name in _ref5) { + if (!__hasProp.call(_ref5, name)) continue; + substate = _ref5[name]; + if (viaSub) { + name = substate.path(); + } + out[name] = substate; + if (viaSub) { + substate.substates(via, out); + } + } + return out; + }; + + State.prototype.descendants = function(via, out) { + return this.substates(via | VIA_SUB, out); + }; + + State.prototype.addSubstate = function(name, expression) { + var attributes, substate, substates, _base; + attributes = this.attributes; + if (!(attributes & INCIPIENT)) { + if (attributes & FINITE) { + return; + } + if (!(attributes & MUTABLE)) { + return; + } + } + if (attributes & VIRTUAL) { + this.realize(); + } + substates = (_base = this._).substates || (_base.substates = {}); + if (substate = substates[name]) { + substate.destroy(); + } + substate = expression instanceof State ? expression.superstate === this ? expression.realize() : void 0 : new State(this, name, expression); + if (!substate) { + return null; + } + return substates[name] = substate; + }; + + State.prototype.removeSubstate = function(name) { + var attributes, substate, substates, transition, _ref3; + attributes = this.attributes; + if (attributes & VIRTUAL) { + return; + } + substates = (_ref3 = this._) != null ? _ref3.substates : void 0; + if (!(substate = substates != null ? substates[name] : void 0)) { + return; + } + if (!(attributes & MUTABLE || (substate != null ? substate.attributes : void 0) & DESTROYED)) { + return; + } + if ((transition = this.root._transition) && (substate.isSuperstateOf(transition) || substate === transition.origin || substate === transition.target)) { + return false; + } + if (this.root._current.isIn(substate)) { + this.change(this, { + forced: true + }); + } + delete substates[name]; + return substate; + }; + + State.prototype.derivation = function(byName) { + var results, s, ss; + results = []; + ss = this; + while ((s = ss) && (ss = s.superstate)) { + results.push(byName ? s.name || '' : s); + } + return results.reverse(); + }; + + State.prototype.path = function() { + return this.derivation(true).join('.'); + }; + + State.prototype.toString = State.prototype.path; + + State.prototype.depth = function() { + var n, s; + n = 0; + s = this; + while (s = s.superstate) { + n += 1; + } + return n; + }; + + State.prototype.common = function(other) { + var s; + if (!(other instanceof State)) { + other = this.query(other); + } + if (this.depth() > other.depth()) { + s = other; + other = this; + } else { + s = this; + } + while (s) { + if (s === other || s.isSuperstateOf(other)) { + return s; + } + s = s.superstate; + } + return null; + }; + + State.prototype.is = function(other) { + if (!(other instanceof State)) { + other = this.query(other); + } + return other === this; + }; + + State.prototype.isIn = function(other) { + if (!(other instanceof State)) { + other = this.query(other); + } + return other === this || other.isSuperstateOf(this); + }; + + State.prototype.hasSubstate = function(other) { + if (!(other instanceof State)) { + other = this.query(other); + } + return other === this || this.isSuperstateOf(other); + }; + + State.prototype.isSuperstateOf = function(other) { + var superstate; + if (!(other instanceof State)) { + other = this.query(other); + } + if (superstate = other.superstate) { + return this === superstate || this.isSuperstateOf(superstate); + } else { + return false; + } + }; + + State.prototype.defaultSubstate = function(via, first) { + var name, protostate, substate, substates, _ref3; + if (via == null) { + via = VIA_PROTO; + } + _ref3 = substates = this.substates(); + for (name in _ref3) { + substate = _ref3[name]; + if (substate.attributes & DEFAULT) { + return substate; + } + } + if (first == null) { + for (name in substates) { + first = substates[name]; + break; + } + } + if (via & VIA_PROTO && (protostate = this.protostate)) { + return protostate.defaultSubstate(VIA_PROTO); + } + return first; + }; + + State.prototype.initialSubstate = function(via) { + var attributes, i, name, queue, subject, substate, _ref3, _ref4; + if (via == null) { + via = VIA_PROTO; + } + i = 0; + queue = [this]; + while (subject = queue[i++]) { + _ref3 = subject.substates(VIA_VIRTUAL); + for (name in _ref3) { + substate = _ref3[name]; + attributes = substate.attributes; + if (attributes & INITIAL) { + if (attributes & CONCURRENT) { + return substate; + } + return (substate.initialSubstate(VIA_NONE)) || substate; + } else { + queue.push(substate); + } + } + } + if (via & VIA_PROTO) { + return (_ref4 = this.protostate) != null ? _ref4.initialSubstate(VIA_PROTO) : void 0; + } + }; + + State.prototype.getProtostate = function() { + var accessorName, getPrototypeOf, owner, path, protostate, prototype, root; + getPrototypeOf = O.getPrototypeOf; + owner = this.owner, root = this.root; + accessorName = root.accessorName; + path = this.path(); + prototype = getPrototypeOf(owner); + while (prototype) { + if (protostate = typeof prototype[accessorName] === "function" ? prototype[accessorName](path, VIA_NONE) : void 0) { + return protostate; + } + prototype = getPrototypeOf(prototype); + } + return null; + }; + + State.prototype.isProtostateOf = function(other) { + var protostate; + if (!(other instanceof State)) { + other = this.query(other); + } + if (protostate = other.protostate) { + return this === protostate || this.isProtostateOf(protostate); + } else { + return false; + } + }; + + State.prototype.query = function(selector, against, via, toBeSkipped) { + var cursor, i, l, name, next, out, parts, queue, result, subject, substate, _ref3, _ref4, _ref5; + if (typeof against === 'number') { + toBeSkipped = via; + via = against; + against = void 0; + } + if (via == null) { + via = VIA_ALL; + } + if (selector == null) { + if (against != null) { + return false; + } else { + return null; + } + } + if (selector === '.') { + if (against != null) { + return against === this; + } else { + return this; + } + } + if (selector === '') { + if (against != null) { + return against === this.root; + } else { + return this.root; + } + } + if (against && against === this.root && /^\*+$/.test(selector)) { + return true; + } + if (/^\.*\**$/.test(selector)) { + via &= ~(VIA_SUB | VIA_SUPER); + } + if (selector.charAt(0) !== '.') { + return this.root.query('.' + selector, against, VIA_SUB | VIA_PROTO); + } + selector = selector.replace(/^(\.+)\.$/, '$1'); + parts = selector.split('.'); + i = 0; + l = parts.length; + cursor = this; + while (cursor) { + i += 1; + if (i >= l) { + return (against ? against === cursor : cursor); + } + name = parts[i]; + if (name === '*') { + if (!against) { + return cursor.substates(VIA_NONE); + } + if (cursor === against.superstate) { + return true; + } + break; + } + if (name === '**') { + if (!against) { + return cursor.substates(VIA_SUB); + } + if (cursor.isSuperstateOf(against)) { + return true; + } + break; + } + if (name === '***') { + if (!against) { + out = {}; + out[cursor.path()] = cursor; + return cursor.substates(VIA_SUB, out); + } + if (cursor === against || cursor.isSuperstateOf(against)) { + return true; + } + break; + } + if (name === '') { + cursor = cursor.superstate; + } else if (next = cursor.substate(name, VIA_NONE)) { + cursor = next; + } else { + break; + } + } + if (via & VIA_SUB) { + i = 0; + queue = [this]; + while (subject = queue[i++]) { + _ref3 = subject.substates(VIA_VIRTUAL); + for (name in _ref3) { + substate = _ref3[name]; + if (substate === toBeSkipped) { + continue; + } + result = substate.query(selector, against, VIA_NONE); + if (result) { + return result; + } + queue.push(substate); + } + } + } + if (via & VIA_SUPER) { + if (result = (_ref4 = this.superstate) != null ? _ref4.query(selector, against, via & VIA_SUB | VIA_SUPER, via & VIA_SUB ? this : void 0) : void 0) { + return result; + } + } + if (via & VIA_PROTO) { + if (result = (_ref5 = this.protostate) != null ? _ref5.query(selector, against, via) : void 0) { + return result; + } + } + if (against) { + return false; + } else { + return null; + } + }; + + State.prototype.$ = function() { + var args, expr, match, method; + expr = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : []; + if (typeof expr === 'function') { + if (expr = expr()) { + return this.change.apply(this, [expr].concat(args)); + } + } else if (typeof expr === 'string' && (match = expr.match(rxTransitionArrow)) && (method = transitionArrowMethods[match[1]])) { + if (args.length) { + return this[method].apply(this, [match[2]].concat(args)); + } else { + return this[method](match[2]); + } + } + }; + + State.prototype.current = function() { + return this.root._current; + }; + + State.prototype.isCurrent = function() { + return this === this.current(); + }; + + State.prototype.isActive = function() { + var current; + return this === (current = this.current()) || this.isSuperstateOf(current); + }; + + State.prototype.isOccupied = function() { + var current; + if (!(current = this.root._current)) { + return false; + } + return this === current || this.isSuperstateOf(current); + }; + + State.prototype.change = function(target, options) { + var root; + return (root = this.root).change.apply(root, arguments); + }; + + State.prototype.go = State.prototype.change; + + State.prototype.be = State.prototype.change; + + State.prototype.data = function(via, out) { + var attributes, mutation, relative, residue, _base, _i, _ref3, _ref4, _ref5, _ref6; + if (via == null) { + via = VIA_ALL; + } + if (via !== via << 0) { + mutation = via; + } + if (mutation) { + attributes = this.attributes; + if (attributes & INCIPIENT_OR_MUTABLE && !isEmpty(mutation)) { + if (attributes & VIRTUAL) { + return this.realize().data(mutation); + } + residue = delta((_base = this._).data != null ? (_base = this._).data : _base.data = {}, mutation); + if (~attributes & ATOMIC && residue && !isEmpty(residue)) { + this.emit('mutate', [mutation, residue], VIA_PROTO); + } + } + return this; + } else { + if (out == null) { + out = {}; + } + _ref4 = (_ref3 = this.order) != null ? _ref3 : this.linearize(); + for (_i = _ref4.length - 1; _i >= 0; _i += -1) { + relative = _ref4[_i]; + if (!(via & VIA_SUPER || relative === this)) { + continue; + } + edit('deep all', out, (via & VIA_PROTO ? (_ref5 = relative.protostate) != null ? _ref5.data(VIA_PROTO, out) : void 0 : void 0), (_ref6 = relative._) != null ? _ref6.data : void 0); + } + return out; + } + }; + + State.prototype.has = function(key, via) { + var data, relative, s, viaProto, viaSuper, _i, _len, _ref3, _ref4, _ref5; + if (via == null) { + via = VIA_ALL; + } + viaSuper = via & VIA_SUPER; + viaProto = via & VIA_PROTO; + _ref4 = (_ref3 = this.order) != null ? _ref3 : this.linearize(); + for (_i = 0, _len = _ref4.length; _i < _len; _i++) { + relative = _ref4[_i]; + s = relative; + while (s != null) { + if (((data = (_ref5 = s._) != null ? _ref5.data : void 0) != null) && hasOwn.call(data, key)) { + return true; + } + if (viaProto) { + s = s.protostate; + } else { + break; + } + if (viaSuper) { + continue; + } + } + } + return false; + }; + + State.prototype.get = function(key, via) { + var data, relative, s, viaProto, viaSuper, _i, _len, _ref3, _ref4, _ref5; + if (via == null) { + via = VIA_ALL; + } + viaSuper = via & VIA_SUPER; + viaProto = via & VIA_PROTO; + _ref4 = (_ref3 = this.order) != null ? _ref3 : this.linearize(); + for (_i = 0, _len = _ref4.length; _i < _len; _i++) { + relative = _ref4[_i]; + s = relative; + while (s != null) { + if (((data = (_ref5 = s._) != null ? _ref5.data : void 0) != null) && hasOwn.call(data, key)) { + return data[key]; + } + if (viaProto) { + s = s.protostate; + } else { + break; + } + } + if (viaSuper) { + continue; + } + } + }; + + State.prototype["let"] = function(key, value) { + var attributes, data, displaced, mutation, residue, _base; + attributes = this.attributes; + if (!(attributes & INCIPIENT_OR_MUTABLE)) { + return; + } + if (attributes & VIRTUAL) { + this.realize(); + } + data = (_base = this._).data || (_base.data = {}); + if (value !== (displaced = lookup(data, key))) { + assign(data, key, value); + assign((mutation = {}).data = {}, key, value); + assign((residue = {}).data = {}, key, displaced); + this.emit('mutate', [mutation, residue], VIA_PROTO); + } + return value; + }; + + State.prototype.set = function(key, value) { + var attributes, data, relative, _i, _len, _ref3, _ref4, _ref5; + attributes = this.attributes; + if (!(attributes & INCIPIENT_OR_MUTABLE)) { + return; + } + if (attributes & VIRTUAL) { + this.realize(); + } + _ref4 = (_ref3 = this.order) != null ? _ref3 : this.linearize(); + for (_i = 0, _len = _ref4.length; _i < _len; _i++) { + relative = _ref4[_i]; + if (((data = (_ref5 = relative._) != null ? _ref5.data : void 0) != null) && hasOwn.call(data, key)) { + if (relative.attributes & MUTABLE) { + return relative["let"](key, value); + } + break; + } + } + return this["let"](key, value); + }; + + State.prototype["delete"] = function(key) { + if (!(this.attributes & MUTABLE)) { + return; + } + return NIL === this["let"](key, NIL); + }; + + State.prototype.method = function(methodName, via, out, returnBoxed) { + var attributes, context, inherited, method, realized, record, relative, table, viaProto, _base, _i, _len, _ref10, _ref3, _ref4, _ref5, _ref6, _ref7, _ref8, _ref9; + if (via == null) { + via = VIA_ALL; + } + attributes = this.attributes; + realized = ~attributes & VIRTUAL; + while (true) { + if (realized) { + if (method = (_ref3 = this._) != null ? (_ref4 = _ref3.methods) != null ? _ref4[methodName] : void 0 : void 0) { + context = this; + break; + } + if (record = (_ref5 = this._) != null ? (_ref6 = _ref5.__dispatch_table__) != null ? _ref6[methodName] : void 0 : void 0) { + method = record[0], context = record[1]; + if (method != null) { + break; + } + } + } + if (viaProto = via & VIA_PROTO) { + if (method = (_ref7 = this.protostate) != null ? _ref7.method(methodName, VIA_PROTO, out, true) : void 0) { + context = this; + inherited = true; + break; + } + } + if (via & VIA_SUPER) { + _ref9 = (_ref8 = this.order) != null ? _ref8 : this.linearize(); + for (_i = 0, _len = _ref9.length; _i < _len; _i++) { + relative = _ref9[_i]; + if (relative !== this) { + if (method = relative.method(methodName, viaProto, out, true)) { + context = (_ref10 = out != null ? out.context : void 0) != null ? _ref10 : null; + inherited = true; + break; + } + } + } + if (method != null) { + break; + } + } + context = null; + break; + } + if (method != null) { + if (typeof method === 'function') { + context = null; + } + if (realized && inherited && useDispatchTables) { + table = (_base = this._).__dispatch_table__ != null ? (_base = this._).__dispatch_table__ : _base.__dispatch_table__ = {}; + table[methodName] = [method, context]; + } + if (!returnBoxed) { + if (method.type === 'state-bound-function') { + method = method.fn; + } + } + } + if (out != null) { + out.method = method; + out.context = context; + } + return method; + }; + + State.prototype.methodNames = function() { + var methods, _ref3; + if (methods = (_ref3 = this._) != null ? _ref3.methods : void 0) { + return keys(methods); + } + }; + + State.prototype.addMethod = function(methodName, fn) { + var methods, owner, ownerMethod, root, _ref3, _ref4, _ref5; + if (!(this.attributes & INCIPIENT_OR_MUTABLE)) { + return; + } + if (typeof fn === 'object' && fn.type === 'state-fixed-function') { + fn = fn.fn(this, this.protostate); + } + if (!(typeof fn === 'function' || (fn != null ? fn.type : void 0) === 'state-bound-function')) { + throw new TypeError("Must supply a plain, bound, or fixed function"); + } + owner = this.owner; + if (!((_ref3 = (ownerMethod = owner[methodName])) != null ? _ref3.isDispatcher : void 0)) { + root = this.root; + owner[methodName] = createDispatcher(root.accessorName, methodName, ownerMethod); + if ((ownerMethod != null) && this !== root) { + methods = (_ref4 = root._) != null ? _ref4.methods || (_ref4.methods = {}) : void 0; + methods[methodName] = ownerMethod; + } + } + methods = (_ref5 = this._) != null ? _ref5.methods || (_ref5.methods = {}) : void 0; + return methods[methodName] = fn; + }; + + State.prototype.removeMethod = function(methodName) { + var fn, methods, _ref3; + if (!(this.attributes & MUTABLE && (methods = (_ref3 = this._) != null ? _ref3.methods : void 0) && (fn = methods[methodName]))) { + return; + } + delete methods[methodName]; + return fn; + }; + + State.prototype.hasMethod = function(methodName) { + var method; + return method = this.method(methodName); + }; + + State.prototype.hasOwnMethod = function(methodName) { + return !!this.method(methodName, VIA_NONE); + }; + + State.prototype.apply = function(methodName, args) { + var context, method, out, record, _ref3, _ref4; + if (record = (_ref3 = this._) != null ? (_ref4 = _ref3.__dispatch_table__) != null ? _ref4[methodName] : void 0 : void 0) { + method = record[0], context = record[1]; + if ((method != null ? method.type : void 0) === 'state-bound-function') { + method = method.fn; + } + } + if (method == null) { + if (method = this.method(methodName, VIA_ALL, out = {})) { + context = out.context; + } else { + this.emit('noSuchMethod', [methodName, args]); + this.emit('noSuchMethod:' + methodName, args); + return; + } + } + return method.apply(context || this.owner, args); + }; + + State.prototype.call = function() { + var args, methodName; + methodName = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : []; + return this.apply(methodName, args); + }; + + State.prototype.event = function(eventType, id) { + var emitter, _ref3, _ref4; + if (!(emitter = (_ref3 = this._) != null ? (_ref4 = _ref3.events) != null ? _ref4[eventType] : void 0 : void 0)) { + return; + } + if (id === void 0) { + return emitter.length; + } + if (typeof id === 'function') { + id = emitter.key(id); + } + return emitter.get(id); + }; + + State.prototype.addEvent = function(eventType, fn, context) { + var events, match, selector, _base, _ref3; + if (this.attributes & VIRTUAL) { + this.realize(); + } + if (rxDelegatedEvent.test(eventType)) { + _ref3 = rxDelegatedEvent.exec(eventType), match = _ref3[0], selector = _ref3[1], eventType = _ref3[2]; + } + events = (_base = this._).events || (_base.events = {}); + if (!hasOwn.call(events, eventType)) { + events[eventType] = new StateEventEmitter(this); + } + if (fn.type === 'state-fixed-function') { + fn = fn.fn(this, this.protostate); + } + return events[eventType].add(fn, context, selector); + }; + + State.prototype.on = State.prototype.addEvent; + + State.prototype.removeEvent = function(eventType, id) { + var _ref3, _ref4; + return (_ref3 = this._) != null ? (_ref4 = _ref3.events) != null ? _ref4[eventType].remove(id) : void 0 : void 0; + }; + + State.prototype.off = State.prototype.removeEvent; + + State.prototype.emit = (function() { + var emit, recur; + emit = function(eventType, args, context, via) { + if (via == null) { + via = VIA_ALL; + } + if (typeof eventType !== 'string') { + return; + } + if (typeof args === 'number') { + via = context; + context = args; + args = void 0; + } + if (typeof context === 'number') { + via = context; + context = void 0; + } + if ((args != null) && !isArray(args)) { + args = [args]; + } + return recur.call(this, eventType, args, context, via, this); + }; + recur = function(eventType, args, context, via, origin) { + var notViaSuper, protostate, relative, _i, _len, _ref3, _ref4, _ref5, _ref6, _ref7; + if ((_ref3 = this._) != null) { + if ((_ref4 = _ref3.events) != null) { + if ((_ref5 = _ref4[eventType]) != null) { + _ref5.emit(args, context != null ? context : this, origin); + } + } + } + if (via & VIA_PROTO && (protostate = this.protostate)) { + recur.call(protostate, eventType, args, context != null ? context : this, VIA_PROTO, origin); + } + if (via & VIA_SUPER) { + notViaSuper = via & ~VIA_SUPER; + _ref7 = (_ref6 = this.order) != null ? _ref6 : this.linearize(); + for (_i = 0, _len = _ref7.length; _i < _len; _i++) { + relative = _ref7[_i]; + if (relative !== this) { + recur.call(relative, eventType, args, context != null ? context : relative, notViaSuper, origin); + } + } + } + }; + return emit; + })(); + + State.prototype.trigger = State.prototype.emit; + + State.prototype.guard = function(guardType, selector, via, out) { + var predicate, predicates, _i, _len, _ref3, _ref4, _ref5, _ref6, _ref7; + if (via == null) { + via = VIA_PROTO; + } + if (out == null) { + out = []; + } + if (via & VIA_PROTO) { + if ((_ref3 = this.protostate) != null) { + _ref3.guard(guardType, selector, via, out); + } + } + predicates = (_ref4 = this._) != null ? (_ref5 = _ref4.guards) != null ? (_ref6 = _ref5[guardType]) != null ? (_ref7 = _ref6.map) != null ? _ref7[selector] : void 0 : void 0 : void 0 : void 0; + if (predicates != null) { + for (_i = 0, _len = predicates.length; _i < _len; _i++) { + predicate = predicates[_i]; + out.push(predicate); + } + } + if (out.length) { + return out; + } + }; + + State.prototype.guards = function(guardType, via, out) { + var predicate, predicates, predicatesOut, selector, _i, _len, _ref3, _ref4, _ref5, _ref6, _ref7; + if (via == null) { + via = VIA_PROTO; + } + if (out == null) { + out = {}; + } + if (via & VIA_PROTO) { + if ((_ref3 = this.protostate) != null) { + _ref3.guards(guardType, via, out); + } + } + _ref7 = (_ref4 = this._) != null ? (_ref5 = _ref4.guards) != null ? (_ref6 = _ref5[guardType]) != null ? _ref6.map : void 0 : void 0 : void 0; + for (selector in _ref7) { + if (!__hasProp.call(_ref7, selector)) continue; + predicates = _ref7[selector]; + if (!(predicates != null ? predicates.length : void 0)) { + continue; + } + predicatesOut = out[selector] || (out[selector] = []); + for (_i = 0, _len = predicates.length; _i < _len; _i++) { + predicate = predicates[_i]; + predicatesOut.push(predicate); + } + } + if (!isEmpty(out)) { + return out; + } + }; + + State.prototype.addGuard = function(guardType, selector, predicateList) { + var attributes, guardMap, index, metaobjectGuards, predicate, _base, _i, _len; + attributes = this.attributes; + if (!(attributes & INCIPIENT_OR_MUTABLE)) { + return; + } + if (attributes & VIRTUAL) { + this.realize(); + } + if (typeof selector !== 'string' && (predicateList == null)) { + predicateList = selector; + selector = '***'; + } + predicateList = isArray(predicateList) ? predicateList.slice(0) : [predicateList]; + for (index = _i = 0, _len = predicateList.length; _i < _len; index = ++_i) { + predicate = predicateList[index]; + if ((predicate != null ? predicate.type : void 0) === 'state-fixed-function') { + predicateList[index] = predicate.fn(this, this.protostate); + } + } + metaobjectGuards = (_base = this._).guards != null ? (_base = this._).guards : _base.guards = {}; + guardMap = metaobjectGuards[guardType] != null ? metaobjectGuards[guardType] : metaobjectGuards[guardType] = new GuardMap(guardType); + return guardMap.add(selector, predicateList); + }; + + State.prototype.addGuards = function(guardType, map) { + var attributes, predicateList, selector; + if (map == null) { + return; + } + attributes = this.attributes; + if (!(attributes & INCIPIENT_OR_MUTABLE)) { + return; + } + if (attributes & VIRTUAL) { + this.realize(); + } + if (map instanceof GuardMap || (map.guardType != null) && (map.map != null)) { + map = map.map; + } + for (selector in map) { + if (!__hasProp.call(map, selector)) continue; + predicateList = map[selector]; + if (predicateList != null) { + if (predicateList === NIL) { + if (!(attributes & INCIPIENT)) { + this.removeGuard(guardType, selector); + } + } else { + this.addGuard(guardType, selector, predicateList); + } + } + } + }; + + State.prototype.removeGuard = function(guardType, selector) { + var removed, _ref3, _ref4, _ref5; + if (!(this.attributes & MUTABLE)) { + return; + } + if (selector == null) { + return this.removeGuards(guardType); + } + if (!(removed = (_ref3 = this._) != null ? (_ref4 = _ref3.guards) != null ? (_ref5 = _ref4[guardType]) != null ? _ref5.remove(selector) : void 0 : void 0 : void 0)) { + return null; + } + if (isEmpty(guardMap)) { + delete this._.guards[guardType]; + } + return removed; + }; + + State.prototype.removeGuards = function(guardType) { + var removed, _ref3, _ref4; + if (!(this.attributes & MUTABLE)) { + return; + } + if (!(removed = (_ref3 = this._) != null ? (_ref4 = _ref3.guards) != null ? _ref4[guardType] : void 0 : void 0)) { + return null; + } + delete this._.guards[guardType]; + return removed; + }; + + State.prototype.evaluateGuards = function(guardType, againstState, via) { + var guardMap, s, _ref3, _ref4; + if (via == null) { + via = VIA_PROTO; + } + s = this; + while (s != null) { + if (guardMap = (_ref3 = s._) != null ? (_ref4 = _ref3.guards) != null ? _ref4[guardType] : void 0 : void 0) { + if (!guardMap.evaluate(againstState, this)) { + return false; + } + } + if (via & VIA_PROTO) { + s = s.protostate; + } else { + break; + } + } + return true; + }; + + State.prototype.transition = function(name) { + var _ref3, _ref4; + return (_ref3 = this._) != null ? (_ref4 = _ref3.transitions) != null ? _ref4[name] : void 0 : void 0; + }; + + State.prototype.transitions = function() { + var _ref3; + return clone((_ref3 = this._) != null ? _ref3.transitions : void 0); + }; + + State.prototype.addTransition = function(name, expression) { + var attributes, transitions, _base; + attributes = this.attributes; + if (!(attributes & INCIPIENT_OR_MUTABLE)) { + return; + } + if (attributes & VIRTUAL) { + this.realize(); + } + if (!(expression instanceof TransitionExpression)) { + expression = new TransitionExpression(expression); + } + transitions = (_base = this._).transitions || (_base.transitions = {}); + return transitions[name] = expression; + }; + + State.prototype.removeTransition = function(name) { + var attributes, transition, transitions; + attributes = this.attributes; + if (attributes & VIRTUAL) { + return; + } + if (!(attributes & MUTABLE && (transitions = this._.transitions))) { + return; + } + transition = transitions[name]; + if (transition) { + delete transitions[name]; + } + return transition; + }; + + State.prototype.decodeAttributes = function(mask) { + if (mask == null) { + mask = ~0; + } + return StateExpression.decodeAttributes(this.attributes & mask); + }; + + State.prototype.print = function(mark, context, indent) { + var attributes, i, out, path, substate, _ref3; + if (context == null) { + context = this; + } + if (indent == null) { + indent = 0; + } + attributes = this.decodeAttributes(~CONCRETE); + out = ''; + i = 0; + while (i++ < indent) { + out += ' '; + } + out += ((mark != null) && this === mark ? '▷ ' : ' '); + out += this.owner !== context.owner && '◌ ' || this.isCurrent() && '● ' || this.isOccupied() && '◍ ' || '○ '; + out += this === this.root && ("<" + this.owner.constructor.name + ">") || ("" + this.name); + out += ": " + this.constructor.name; + if (attributes) { + out += " '" + attributes + "'"; + } + out += '\n'; + _ref3 = this.substates(VIA_VIRTUAL | VIA_PROTO); + for (path in _ref3) { + substate = _ref3[path]; + out += substate.print(mark, context, indent + 1); + } + return out; + }; + + return State; + + })(); + + State.prototype.Metaobject = require('./state-metaobject'); + + State.prototype.Expression = StateExpression = require('./state-expression'); + + StateEventEmitter = require('./state-event-emitter'); + + GuardMap = require('./guard-map'); + + TransitionExpression = require('./transition-expression'); + +}).call(this); diff --git a/lib/transition-expression.js b/lib/transition-expression.js new file mode 100644 index 0000000..07ac829 --- /dev/null +++ b/lib/transition-expression.js @@ -0,0 +1,63 @@ +// Generated by CoffeeScript 1.6.3 +(function() { + var GUARD_ACTIONS, O, TRANSITION_EVENT_TYPES, TRANSITION_EXPRESSION_CATEGORIES, TRANSITION_PROPERTIES, TransitionExpression, state, + __hasProp = {}.hasOwnProperty; + + state = require('./state-function'); + + O = state.O, TRANSITION_PROPERTIES = state.TRANSITION_PROPERTIES, TRANSITION_EXPRESSION_CATEGORIES = state.TRANSITION_EXPRESSION_CATEGORIES, TRANSITION_EVENT_TYPES = state.TRANSITION_EVENT_TYPES, GUARD_ACTIONS = state.GUARD_ACTIONS; + + module.exports = TransitionExpression = (function() { + var assign, categories, clone, edit, eventTypes, guardActions, interpret, properties; + + assign = O.assign, edit = O.edit, clone = O.clone; + + properties = assign(TRANSITION_PROPERTIES, null); + + categories = assign(TRANSITION_EXPRESSION_CATEGORIES, null); + + eventTypes = assign(TRANSITION_EVENT_TYPES); + + guardActions = assign(GUARD_ACTIONS); + + function TransitionExpression(map) { + if (!(map instanceof TransitionExpression)) { + map = interpret(map); + } + edit('deep all', this, map); + } + + interpret = function(map) { + var category, events, item, key, result, value, _ref; + result = assign({}, properties, categories); + for (key in map) { + if (!__hasProp.call(map, key)) continue; + value = map[key]; + if (key in properties) { + result[key] = value; + } else if (key in categories) { + result[key] = clone(result[key], value); + } else { + category = key in eventTypes ? 'events' : key in guardActions ? 'guards' : typeof value === 'function' ? 'methods' : void 0; + if (category) { + item = result[category]; + item || (item = result[category] = {}); + item[key] = value; + } + } + } + _ref = events = result.events; + for (key in _ref) { + value = _ref[key]; + if (typeof value === 'function') { + events[key] = [value]; + } + } + return result; + }; + + return TransitionExpression; + + })(); + +}).call(this); diff --git a/lib/transition.js b/lib/transition.js new file mode 100644 index 0000000..d182620 --- /dev/null +++ b/lib/transition.js @@ -0,0 +1,83 @@ +// Generated by CoffeeScript 1.6.3 +(function() { + var State, Transition, + __hasProp = {}.hasOwnProperty, + __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; + + State = require('./state'); + + module.exports = Transition = (function(_super) { + var VIA_PROTO; + + __extends(Transition, _super); + + VIA_PROTO = Transition.VIA_PROTO; + + function Transition(target, source, expression, callback) { + var root, _ref; + this.name = expression.name || null; + this.superstate = source; + this.root = root = source.root; + if (target.root !== root) { + throw ReferenceError; + } + this.owner = root.owner; + this.target = target; + this.source = source; + this.origin = (_ref = source.origin) != null ? _ref : source; + this.callback = callback; + this.action = expression.action || null; + this._ = new this.Metaobject; + this.aborted = false; + this.initialize(expression); + } + + Transition.prototype.linearize = function() { + var superstate, _ref, _ref1; + superstate = this.superstate; + return (_ref = (_ref1 = superstate.order) != null ? _ref1.slice(0) : void 0) != null ? _ref : superstate.linearize(); + }; + + Transition.prototype.start = function() { + var action; + this.aborted = false; + this.emit('start', arguments, VIA_PROTO); + if (action = this.action) { + action.apply(this, arguments); + return this; + } else { + return this.end.apply(this, arguments); + } + }; + + Transition.prototype.abort = function() { + this.aborted = true; + this.callback = null; + this.emit('abort', arguments, VIA_PROTO); + return this; + }; + + Transition.prototype.end = function() { + var _ref; + if (!this.aborted) { + this.emit('end', arguments, VIA_PROTO); + if ((_ref = this.callback) != null) { + _ref.apply(this.root, arguments); + } + } + this.destroy(); + return this.target; + }; + + Transition.prototype.destroy = function() { + if (this.source instanceof Transition) { + this.source.destroy(); + } + return this.target = this.superstate = this.root = null; + }; + + return Transition; + + })(State); + +}).call(this); diff --git a/test/lib/api/guard-map.js b/test/lib/api/guard-map.js new file mode 100644 index 0000000..eb5dd80 --- /dev/null +++ b/test/lib/api/guard-map.js @@ -0,0 +1,177 @@ +// Generated by CoffeeScript 1.6.3 +(function() { + var GuardMap, VIA_PROTO, expect, falsePredicate, state, truePredicate; + + expect = require('chai').expect; + + state = require('state'); + + GuardMap = state.GuardMap; + + VIA_PROTO = state.TRAVERSAL_FLAGS.VIA_PROTO; + + truePredicate = function() { + return true; + }; + + falsePredicate = function() { + return false; + }; + + describe("GuardMap constructor", function() { + describe("with an object-typed `expression` argument:", function() { + var expression, gm; + gm = new GuardMap('aGuardType', expression = { + aSelector: [truePredicate, truePredicate, truePredicate], + anotherSelector: truePredicate + }); + it("writes to `guardType`", function() { + return expect(gm.guardType).to.equal('aGuardType'); + }); + it("writes the interpreted `expression` to `this.map`", function() { + return expect(Object.keys(gm.map).join(' ')).to.equal('aSelector anotherSelector'); + }); + return it("boxes a function value inside an array", function() { + expect(gm.map.aSelector).to.have.length(3); + return expect(gm.map.anotherSelector).to.have.length(1); + }); + }); + describe("with a string `expression`:", function() { + var expression, gm; + gm = new GuardMap('aGuardType', expression = 'justAString'); + return it("interprets the string as a selector and true predicate", function() { + expect(gm.guardType).to.equal('aGuardType'); + expect(gm.map.justAString).to.have.length(1); + return expect(gm.map.justAString[0]()).to.equal(true); + }); + }); + describe("with a function `expression`:", function() { + var expression, gm; + gm = new GuardMap('aGuardType', expression = function() { + return 42; + }); + it("associates with the any-state selector", function() { + return expect(gm.map['***']).to.exist; + }); + return it("instates the provided function as the lone predicate", function() { + expect(gm.map['***']).to.have.length(1); + return expect(gm.map['***'][0]()).to.equal(42); + }); + }); + describe("with an array `expression`:", function() { + var expression, gm, that; + gm = new GuardMap('aGuardType', expression = ['everything here must be coerced', /^into predicates$/, that = true]); + it("associates with the any-state selector", function() { + expect(gm.guardType).to.equal('aGuardType'); + return expect(gm.map['***']).to.have.length(3); + }); + return it("coerces elements to true predicates", function() { + var p, _i, _len, _ref, _results; + expect(typeof gm.map['***'][0]).to.equal('function'); + truePredicate = gm.map['***'][0]; + _ref = gm.map['***']; + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + p = _ref[_i]; + _results.push(expect(p).to.equal(truePredicate)); + } + return _results; + }); + }); + return describe("with any other type of `expression`:", function() { + var expression, gm; + gm = new GuardMap('aGuardType', expression = 0); + it("associates with the any-state selector", function() { + expect(gm.guardType).to.equal('aGuardType'); + return expect(gm.map['***']).to.have.length(1); + }); + return it("converts the value to a predicate thunk", function() { + return expect(gm.map['***'][0]()).to.equal(false); + }); + }); + }); + + describe("GuardMap::add", function() { + it("writes arguments to `map` property", function() { + var guardMap; + guardMap = new GuardMap('aGuardType'); + guardMap.add('A', [truePredicate, falsePredicate]); + expect(guardMap.guardType).to.equal('aGuardType'); + expect(guardMap.map.A).to.be.an.array; + expect(guardMap.map.A).to.have.length(2); + expect(guardMap.map.A[0]).to.equal(truePredicate); + return expect(guardMap.map.A[1]).to.equal(falsePredicate); + }); + return it("writes function-typed `predicates` argument as array", function() { + var guardMap; + guardMap = new GuardMap('aGuardType'); + guardMap.add('A', truePredicate); + expect(guardMap.map.A).to.be.an.array; + expect(guardMap.map.A).to.have.length(1); + return expect(guardMap.map.A[0]).to.equal(truePredicate); + }); + }); + + describe("GuardMap::remove", function() {}); + + describe("GuardMap::evaluate", function() { + var Class, object, stateA, stateB, stateC; + Class = (function() { + function Class() {} + + state(Class.prototype, 'abstract', { + A: state, + B: state, + C: state + }); + + return Class; + + })(); + object = new Class; + stateA = object.state('A'); + stateB = object.state('B'); + stateC = object.state('C'); + it("arguments are passed to the parameters of predicates", function() { + var guardMap; + guardMap = new GuardMap('aGuardType', { + 'A': function(against, as, guardType) { + return against.name === 'A' && as.name === 'B' && guardType === 'aGuardType'; + } + }); + return expect(guardMap.evaluate(stateA, stateB)).to.equal(true); + }); + it("requires all matching guards to pass", function() { + var guardMap; + guardMap = new GuardMap('aGuardType', { + '***': true, + 'A': [ + function(against, as) { + return true; + }, function(against, as, guardType) { + if (guardType === 'aGuardType') { + return true; + } + }, function(against, as) { + return as.name !== 'C'; + } + ], + 'B': false + }); + expect(guardMap.evaluate(stateC, stateB)).to.equal(true); + expect(guardMap.evaluate(stateB, stateA)).to.equal(false); + expect(guardMap.evaluate(stateA, stateB)).to.equal(true); + return expect(guardMap.evaluate(stateA, stateC)).to.equal(false); + }); + return it("requires at least one guard to pass if any are defined", function() { + var emptyMap, populatedMap; + emptyMap = new GuardMap('aGuardType', {}); + expect(emptyMap.evaluate(stateB, stateC)).to.equal(true); + populatedMap = new GuardMap('aGuardType', { + 'A': true + }); + return expect(populatedMap.evaluate(stateB, stateC)).to.equal(false); + }); + }); + +}).call(this); diff --git a/test/lib/api/root-state/get-transition-expression.js b/test/lib/api/root-state/get-transition-expression.js new file mode 100644 index 0000000..9e5d05e --- /dev/null +++ b/test/lib/api/root-state/get-transition-expression.js @@ -0,0 +1,5 @@ +// Generated by CoffeeScript 1.6.3 +(function() { + + +}).call(this); diff --git a/test/lib/api/state/data.test.js b/test/lib/api/state/data.test.js new file mode 100644 index 0000000..200ef17 --- /dev/null +++ b/test/lib/api/state/data.test.js @@ -0,0 +1,200 @@ +// Generated by CoffeeScript 1.6.3 +(function() { + var RootState, State, bind, diff, env, expect, fix, isEmpty, state, _ref, + __hasProp = {}.hasOwnProperty, + __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; + + expect = require('chai').expect; + + state = require('state'); + + env = state.env, bind = state.bind, fix = state.fix, State = state.State, RootState = state.RootState; + + _ref = state.O, isEmpty = _ref.isEmpty, diff = _ref.diff; + + describe("Data:", function() { + var Class, Superclass, expectation, instanceStateExpression, _ref1; + Superclass = (function() { + function Superclass() {} + + state(Superclass.prototype, 'mutable', { + data: { + a: 1, + b: 'shadow me', + d: 'shadow me' + }, + A: state({ + data: { + b: 2, + c: 'shadow me', + e: 'shadow me' + }, + AA: state({ + data: { + c: 3, + f: 'shadow me' + } + }) + }), + X: state('abstract', { + data: { + x: 24, + c: 'shadow me' + } + }) + }); + + return Superclass; + + })(); + Class = (function(_super) { + __extends(Class, _super); + + function Class() { + _ref1 = Class.__super__.constructor.apply(this, arguments); + return _ref1; + } + + state(Class.prototype, { + data: { + d: 4, + e: 'shadow me', + g: 'shadow me' + }, + A: state({ + data: { + e: 5, + f: 'shadow me', + h: 'shadow me' + }, + AA: state({ + data: { + f: 6, + i: 'shadow me' + } + }) + }), + Y: state('abstract', { + data: { + f: 'shadow me', + x: 'shadow me', + y: 25 + } + }) + }); + + return Class; + + })(Superclass); + instanceStateExpression = { + data: { + g: 7 + }, + A: state({ + data: { + h: 8 + }, + AA: state.extend('X, Y, Z', 'initial', { + data: { + i: 9 + } + }) + }), + Z: state('abstract', { + data: { + z: 26 + } + }) + }; + expectation = { + a: 1, + b: 2, + c: 3, + d: 4, + e: 5, + f: 6, + g: 7, + h: 8, + i: 9, + x: 24, + y: 25, + z: 26 + }; + describe("object model", function() { + return it("is structured properly", function() { + var instance; + state(instance = new Class, instanceStateExpression); + expect(instance.state('X').owner).to.equal(instance); + expect(instance.state('X').protostate.owner).to.equal(Superclass.prototype); + expect(instance.state('Y').owner).to.equal(instance); + expect(instance.state('Y').protostate.owner).to.equal(Class.prototype); + expect(instance.state('Z').owner).to.equal(instance); + return expect(instance.state('Z').protostate).to.equal(null); + }); + }); + describe("data (read):", function() { + return it("performs proper inheritance and shadowing", function() { + var instance, residue; + state(instance = new Class, instanceStateExpression); + residue = diff(instance.state().data(), expectation); + return expect(isEmpty(residue)).to.equal(true); + }); + }); + describe("has:", function() { + return it("has each expected key", function() { + var instance, key, value, _results; + state(instance = new Class, instanceStateExpression); + _results = []; + for (key in expectation) { + value = expectation[key]; + _results.push(expect(instance.state().has(key)).to.equal(true)); + } + return _results; + }); + }); + describe("get:", function() { + return it("gets each expected value", function() { + var instance, key, value, _results; + state(instance = new Class, instanceStateExpression); + _results = []; + for (key in expectation) { + value = expectation[key]; + _results.push(expect(instance.state().get(key)).to.equal(value)); + } + return _results; + }); + }); + describe("let:", function() { + return it("writes to the receiving state, does not mutate ancestors", function() { + var instance; + state(instance = new Class, instanceStateExpression); + instance.state()["let"]('a', 'mutated a'); + expect(instance.state().get('a')).to.equal('mutated a'); + expect(instance.state('').get('a')).to.equal(1); + instance.state()["let"]('x', 'mutated x'); + expect(instance.state().get('x')).to.equal('mutated x'); + return expect(instance.state('X').get('x')).to.equal(24); + }); + }); + return describe("set:", function() { + return it("mutates a property on site in state tree, not on protostate", function() { + var instance; + state(instance = new Class, instanceStateExpression); + instance.state().set('a', 'mutated a'); + expect(instance.state().get('a')).to.equal('mutated a'); + expect(instance.state('').get('a')).to.equal(1); + expect(Superclass.prototype.state('').get('a')).to.equal(1); + instance.state().set('g', 'mutated g'); + expect(instance.state('').get('g')).to.equal('mutated g'); + instance.state().set('x', 'mutated x'); + expect(instance.state().get('x')).to.equal('mutated x'); + expect(instance.state('X').get('x')).to.equal(24); + expect(Superclass.prototype.state('X').get('x')).to.equal(24); + instance.state().set('z', 'mutated z'); + expect(instance.state().get('z')).to.equal('mutated z'); + return expect(instance.state('Z').get('z')).to.equal('mutated z'); + }); + }); + }); + +}).call(this); diff --git a/test/lib/api/state/mutate.js b/test/lib/api/state/mutate.js new file mode 100644 index 0000000..2594cc8 --- /dev/null +++ b/test/lib/api/state/mutate.js @@ -0,0 +1,137 @@ +// Generated by CoffeeScript 1.6.3 +(function() { + var O, RootState, State, env, expect, state, _ref; + + _ref = state = require('state'), O = _ref.O, env = _ref.env, State = _ref.State, RootState = _ref.RootState; + + expect = require('chai').expect; + + describe("State::mutate", function() { + var NIL, unit; + NIL = O.NIL; + unit = { + expression: state('mutable'), + mutations: [ + { + A: state('initial'), + B: state('abstract') + }, { + A: { + AA: state + }, + B: state({ + BA: state, + BB: state('default') + }) + }, { + A: { + AA: { + m: function() { + return 'A.AA'; + } + } + }, + B: { + m: function() { + return 'B'; + } + } + }, { + A: { + data: { + a: 42 + } + } + }, { + A: { + data: { + a: NIL + } + } + }, { + states: { + A: NIL, + B: NIL + } + } + ], + expectations: [ + function(o) { + return o.state('A') && o.state('A').isInitial() && o.state('B'); + }, function(o) { + return o.state('A') && o.state('A.AA') && o.state('B') && o.state('B').isAbstract() && o.state('B.BA') && o.state('B.BB') && o.state('B.BB').isDefault(); + }, function(o) { + return o.state('A.AA').method('m') && o.state('B').method('m'); + }, function(o) { + var _ref1; + return ((_ref1 = o.state('A')) != null ? _ref1.has('a') : void 0) && o.state('A').get('a') === 42; + }, function(o) { + var _ref1; + return ((_ref1 = o.state('A')) != null ? _ref1.has('a') : void 0) === false; + }, function(o) { + return (o.state('') != null) && (o.state('A') == null) && (o.state('B') == null); + } + ] + }; + return describe("a virtual state", function() { + var Class, o; + o = {}; + state(o, unit.expression); + unit.mutations.forEach(function(mutation, index) { + return it("performed mutation " + index + " properly", function() { + o.state('').mutate(mutation); + return expect(unit.expectations[index](o)).to.be.ok; + }); + }); + Class = (function() { + function Class() {} + + state(Class.prototype, { + A: state('mutable'), + B: state, + C: state('immutable') + }); + + return Class; + + })(); + it("is automatically realized if mutable", function() { + o = new Class; + o.state('-> A'); + expect(o.state().isVirtual()).to.equal(true); + o.state().mutate({ + method: function() { + return 'heyo'; + } + }); + expect(o.state().isVirtual()).to.equal(false); + return expect(typeof o.method === "function" ? o.method() : void 0).to.equal('heyo'); + }); + it("is auto-realized but not mutated if weak-immutable", function() { + o = new Class; + o.state('-> B'); + expect(o.state().isVirtual()).to.equal(true); + o.state().mutate({ + method: function() { + return 'heyo'; + } + }); + expect(o.state().isVirtual()).to.equal(false); + return expect(o.method != null).to.equal(false); + }); + return it("is not automatically realized if strong-immutable", function() { + o = new Class; + o.state('-> C'); + expect(o.state().isVirtual()).to.equal(true); + o.state().mutate({ + method: function() { + return 'heyo'; + } + }); + expect(o.state().isVirtual()).to.equal(true); + return expect(o.method != null).to.equal(false); + }); + }); + }); + +}).call(this); diff --git a/test/lib/api/state/query.js b/test/lib/api/state/query.js new file mode 100644 index 0000000..927b1c8 --- /dev/null +++ b/test/lib/api/state/query.js @@ -0,0 +1,180 @@ +// Generated by CoffeeScript 1.6.3 +(function() { + var TRAVERSAL_FLAGS, VIA_ALL, VIA_NONE, VIA_PROTO, VIA_SUB, VIA_SUPER, VIA_VIRTUAL, expect, state, + __hasProp = {}.hasOwnProperty, + __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; + + expect = require('chai').expect; + + state = require('state'); + + TRAVERSAL_FLAGS = state.TRAVERSAL_FLAGS; + + VIA_NONE = TRAVERSAL_FLAGS.VIA_NONE, VIA_SUB = TRAVERSAL_FLAGS.VIA_SUB, VIA_SUPER = TRAVERSAL_FLAGS.VIA_SUPER, VIA_PROTO = TRAVERSAL_FLAGS.VIA_PROTO, VIA_VIRTUAL = TRAVERSAL_FLAGS.VIA_VIRTUAL, VIA_ALL = TRAVERSAL_FLAGS.VIA_ALL; + + describe("State::query", function() { + var Class, Superclass, _ref; + Superclass = (function() { + function Superclass() {} + + state(Superclass.prototype, 'abstract', { + A: state('default initial'), + B: state + }); + + return Superclass; + + })(); + Class = (function(_super) { + __extends(Class, _super); + + function Class() { + _ref = Class.__super__.constructor.apply(this, arguments); + return _ref; + } + + state(Class.prototype, { + A: state({ + AA: state, + AB: state + }) + }); + + return Class; + + })(Superclass); + it("queries virtual states", function() { + var o; + o = new Class; + expect(o.state('A').owner).to.equal(o); + expect(o.state('A')).to.equal(o.state()); + return expect(o.state().isVirtual()).to.equal(true); + }); + it("matches virtual states", function() { + var o, root; + o = new Class; + root = o.state().root; + expect(root.query('A', o.state())).to.equal(true); + o.state('-> AA'); + return expect(root.query('AA', o.state())).to.equal(true); + }); + it("queries and matches against prototypal states", function() { + var A, o; + o = new Class; + A = o.state(); + expect(A.query('AB')).to.equal(Class.prototype.state('AB')); + expect(A.query('B')).to.equal(Superclass.prototype.state('B')); + expect(A.query('AB', Class.prototype.state('AB'))).to.equal(true); + return expect(A.query('B', Superclass.prototype.state('B'))).to.equal(true); + }); + it("recognizes virtual states in inclusion test", function() { + var o, root; + o = new Class; + root = o.state().root; + expect(root.query('*', o.state())).to.equal(true); + o.state('-> AA'); + expect(root.query('*', o.state())).to.equal(false); + return expect(root.query('**', o.state())).to.equal(true); + }); + it("resolves the absolute single wildcard selector", function() { + var list, o, path, set; + o = new Class; + list = ((function() { + var _results; + _results = []; + for (path in Superclass.prototype.state('*')) { + _results.push(path); + } + return _results; + })()).join(' '); + expect(list).to.equal("A B"); + list = ((function() { + var _results; + _results = []; + for (path in Class.prototype.state('*')) { + _results.push(path); + } + return _results; + })()).join(' '); + expect(list).to.equal("A"); + set = (function() { + var _results; + _results = []; + for (path in o.state('*')) { + _results.push(path); + } + return _results; + })(); + list = set.join(' '); + expect(set).to.have.length(0); + return expect(list).to.equal(""); + }); + it("resolves the absolute double wildcard selector", function() { + var list, o, path, set; + o = new Class; + list = ((function() { + var _results; + _results = []; + for (path in Superclass.prototype.state('**')) { + _results.push(path); + } + return _results; + })()).join(' '); + expect(list).to.equal("A B"); + list = ((function() { + var _results; + _results = []; + for (path in Class.prototype.state('**')) { + _results.push(path); + } + return _results; + })()).join(' '); + expect(list).to.equal("A A.AA A.AB"); + set = (function() { + var _results; + _results = []; + for (path in o.state('**')) { + _results.push(path); + } + return _results; + })(); + list = set.join(' '); + expect(set).to.have.length(0); + return expect(list).to.equal(""); + }); + return it("resolves the absolute triple wildcard selector", function() { + var list, o, path, set; + o = new Class; + list = ((function() { + var _results; + _results = []; + for (path in Superclass.prototype.state('***')) { + _results.push(path); + } + return _results; + })()).join(' '); + expect(list).to.equal(" A B"); + list = ((function() { + var _results; + _results = []; + for (path in Class.prototype.state('***')) { + _results.push(path); + } + return _results; + })()).join(' '); + expect(list).to.equal(" A A.AA A.AB"); + set = (function() { + var _results; + _results = []; + for (path in o.state('***')) { + _results.push(path); + } + return _results; + })(); + list = set.join(' '); + expect(set).to.have.length(1); + return expect(list).to.equal(""); + }); + }); + +}).call(this); diff --git a/test/lib/api/state/substates.js b/test/lib/api/state/substates.js new file mode 100644 index 0000000..4a1bf68 --- /dev/null +++ b/test/lib/api/state/substates.js @@ -0,0 +1,141 @@ +// Generated by CoffeeScript 1.6.3 +(function() { + var TRAVERSAL_FLAGS, VIA_ALL, VIA_NONE, VIA_PROTO, VIA_SUB, VIA_SUPER, VIA_VIRTUAL, expect, state, + __hasProp = {}.hasOwnProperty, + __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; + + expect = require('chai').expect; + + state = require('state'); + + TRAVERSAL_FLAGS = state.TRAVERSAL_FLAGS; + + VIA_NONE = TRAVERSAL_FLAGS.VIA_NONE, VIA_SUB = TRAVERSAL_FLAGS.VIA_SUB, VIA_SUPER = TRAVERSAL_FLAGS.VIA_SUPER, VIA_PROTO = TRAVERSAL_FLAGS.VIA_PROTO, VIA_VIRTUAL = TRAVERSAL_FLAGS.VIA_VIRTUAL, VIA_ALL = TRAVERSAL_FLAGS.VIA_ALL; + + describe("State::substates", function() { + var Class, Superclass, _ref; + Superclass = (function() { + function Superclass() {} + + state(Superclass.prototype, 'abstract', { + A: state('default initial'), + B: state({ + BA: state({ + BAA: state, + BAB: state + }), + BB: state + }), + C: state + }); + + return Superclass; + + })(); + Class = (function(_super) { + __extends(Class, _super); + + function Class() { + _ref = Class.__super__.constructor.apply(this, arguments); + return _ref; + } + + state(Class.prototype, { + A: state({ + AA: state('initial'), + AB: state + }) + }); + + return Class; + + })(Superclass); + it("returns an empty set when called with no args", function() { + var o, set; + o = new Class; + set = o.state('').substates(); + return expect(set).to.be.empty; + }); + it("includes all descendants", function() { + var list, path, set, _i, _len, _results; + set = Superclass.prototype.state('').substates(VIA_SUB); + list = "A B B.BA B.BA.BAA B.BA.BAB B.BB C".split(' '); + expect((function() { + var _results; + _results = []; + for (path in set) { + _results.push(path); + } + return _results; + })()).to.have.length(list.length); + _results = []; + for (_i = 0, _len = list.length; _i < _len; _i++) { + path = list[_i]; + _results.push(expect(set[path]).to.exist); + } + return _results; + }); + it("includes all descendants inherited via protostate", function() { + var list, path, set, _i, _len, _results; + set = Class.prototype.state('').substates(VIA_SUB | VIA_PROTO); + list = "A A.AA A.AB B B.BA B.BA.BAA B.BA.BAB B.BB C".split(' '); + expect((function() { + var _results; + _results = []; + for (path in set) { + _results.push(path); + } + return _results; + })()).to.have.length(list.length); + _results = []; + for (_i = 0, _len = list.length; _i < _len; _i++) { + path = list[_i]; + _results.push(expect(set[path]).to.exist); + } + return _results; + }); + it("includes virtual states", function() { + var list, o, path, set, _i, _len, _results; + o = new Class; + set = o.state('').substates(VIA_VIRTUAL | VIA_SUB); + list = "A A.AA".split(' '); + expect((function() { + var _results; + _results = []; + for (path in set) { + _results.push(path); + } + return _results; + })()).to.have.length(list.length); + _results = []; + for (_i = 0, _len = list.length; _i < _len; _i++) { + path = list[_i]; + _results.push(expect(set[path]).to.exist); + } + return _results; + }); + it("returns virtual states and all descendants of protostates", function() { + var list, o, path, set, _i, _len; + o = new Class; + set = o.state('').substates(VIA_ALL); + list = "A A.AA A.AB B B.BA B.BA.BAA B.BA.BAB B.BB C".split(' '); + expect((function() { + var _results; + _results = []; + for (path in set) { + _results.push(path); + } + return _results; + })()).to.have.length(list.length); + for (_i = 0, _len = list.length; _i < _len; _i++) { + path = list[_i]; + expect(set[path]).to.exist; + } + expect(set['A'].isVirtual()).to.equal(true); + expect(set['A.AA'].isVirtual()).to.equal(true); + return expect(set['A.AB'].isVirtual()).to.equal(false); + }); + return it("does not specify order", function() {}); + }); + +}).call(this); diff --git a/test/lib/attributes.test.js b/test/lib/attributes.test.js new file mode 100644 index 0000000..13f8c68 --- /dev/null +++ b/test/lib/attributes.test.js @@ -0,0 +1,537 @@ +// Generated by CoffeeScript 1.6.3 +(function() { + var ABSTRACT, CONCRETE, NIL, RootState, State, TransitionExpression, expect, state, + __hasProp = {}.hasOwnProperty, + __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; + + expect = require('chai').expect; + + state = require('state'); + + NIL = state.O.NIL; + + State = state.State, RootState = state.RootState, TransitionExpression = state.TransitionExpression; + + ABSTRACT = State.ABSTRACT, CONCRETE = State.CONCRETE; + + describe("Attributes:", function() { + var Child, Parent, _ref; + Parent = (function() { + function Parent() {} + + state(Parent.prototype, 'abstract', { + A: state('default'), + B: state('initial'), + C: state('conclusive', { + CA: state, + CB: state, + CC: state('final', { + CCA: state('initial') + }) + }) + }); + + return Parent; + + })(); + Child = (function(_super) { + __extends(Child, _super); + + function Child() { + _ref = Child.__super__.constructor.apply(this, arguments); + return _ref; + } + + state(Child.prototype, 'concrete'); + + return Child; + + })(Parent); + describe("Abstraction:", function() { + describe("An empty state with no literal or inherited attributes", function() { + var o, s; + state(o = {}, null); + s = o.state(); + it("must implicitly bear the `concrete` attribute", function() { + return expect(s.isConcrete()).to.be["true"]; + }); + return it("must implicitly negate the `abstract` attribute", function() { + return expect(s.isAbstract()).to.be["false"]; + }); + }); + describe("The presence of `abstract`", function() { + return it("must negate the `concrete` attribute that a state would otherwise bear via inheritance", function() { + var s; + s = Parent.prototype.state(''); + expect(s.isAbstract()).to.be["true"]; + return expect(s.isConcrete()).to.be["false"]; + }); + }); + describe("Literal `concrete` on the epistate of an `abstract` protostate", function() { + return it("must negate the `abstract` attribute it would otherwise bear", function() { + var s; + s = Child.prototype.state(''); + expect(s.isConcrete()).to.be["true"]; + return expect(s.isAbstract()).to.be["false"]; + }); + }); + describe("An epistate with no literal attributes", function() { + return it("must bear the same abstraction attributes as its protostate", function() { + var c, s; + c = new Child; + s = c.state(''); + expect(s.isConcrete()).to.be["true"]; + expect(s.isAbstract()).to.be["false"]; + return expect(s.attributes & (ABSTRACT | CONCRETE)).to.equal(s.protostate.attributes & (ABSTRACT | CONCRETE)); + }); + }); + describe("An invalid production including both literal `abstract` and literal `concrete`", function() { + var p; + state(p = new Parent, 'abstract concrete', null); + return it("must give precedence to `concrete` and negate `abstract`", function() { + var s; + s = p.state(''); + expect(s.isConcrete()).to.be["true"]; + return expect(s.isAbstract()).to.be["false"]; + }); + }); + return describe("Comparing inheritors of the same prototype that have differing literal abstraction attributes:", function() { + describe("A second-level epistate", function() { + var c; + c = new Child; + it("must override the abstraction attributes of its protostate", function() { + return expect(c.state()).to.exist; + }); + return it("which in turn must override those of the next protostate", function() { + return expect(Child.prototype.state()).to.exist; + }); + }); + return describe("Ordering a transition to the root state:", function() { + describe("A cleanly inheriting instance", function() { + var c; + c = new Child; + it("must complete a transition", function() { + return expect(function() { + return c.state('->'); + }).to.not["throw"](Error); + }); + return it("must arrive at its root state", function() { + return expect(c.state().name).to.equal(''); + }); + }); + return describe("An overriding instance", function() { + var c; + c = new Child; + state(c, 'abstract', null); + it("must complete a transition", function() { + return expect(function() { + return c.state('->'); + }).to.not["throw"](Error); + }); + return it("must be redirected to the `default` substate", function() { + var ds, root, s; + root = c.state(''); + s = c.state(); + ds = root.defaultSubstate(); + c.state('->'); + expect(s).to.not.equal(root); + return expect(s.path()).to.equal(ds.path()); + }); + }); + }); + }); + }); + describe("Destination:", function() { + describe("The `initial` attribute", function() { + it("must be inherited by an instance from its prototype’s tree", function() { + var s; + s = (new Parent).state(); + expect(s.name).to.equal('B'); + return expect(s.isInitial()).to.be["true"]; + }); + return describe("if applied ambiguously to multiple states in a state tree", function() { + return it("must resolve in a depth-within-breadth-first manner", function() { + var s; + s = Parent.prototype.state(); + expect(s.name).to.equal('B'); + return expect(s.name).to.not.equal('CCA'); + }); + }); + }); + describe("The `conclusive` attribute", function() { + it("must be inherited via protostate", function() { + var p; + p = new Parent; + return expect(p.state('C').isConclusive()).to.be["true"]; + }); + it("must cause a state to prohibit transitions that would exit it", function() { + var p, s; + p = new Parent; + p.state('-> C'); + p.state('-> B'); + s = p.state(); + expect(s.name).to.not.equal('B'); + expect(s.name).to.equal('C'); + return expect(s.isConclusive()).to.be["true"]; + }); + return it("must cause a state to allow transitions that wouldn’t exit it", function() { + var p, s; + p = new Parent; + p.state('-> C'); + p.state('-> CB'); + s = p.state(); + return expect(s.name).to.equal('CB'); + }); + }); + return describe("The `final` attribute", function() { + it("must be inherited via protostate", function() { + var p, s; + p = new Parent; + p.state('-> CC'); + s = p.state(); + expect(s.owner).to.equal(p); + expect(s.name).to.equal('CC'); + expect(s.isFinal()).to.be["true"]; + return expect(s.isVirtual()).to.be["true"]; + }); + it("must prohibit transitions that would depart a `final` state", function() { + var p, s; + p = new Parent; + p.state('-> CC'); + p.state('-> CA'); + s = p.state(); + expect(s.name).to.equal('CC'); + expect(s.name).to.not.equal('CA'); + return expect(s.isFinal()).to.be["true"]; + }); + it("must not trap transitions that enter but do not arrive", function() { + var p, s; + p = new Parent; + p.state('-> CCA'); + s = p.state(); + expect(s.name).to.equal('CCA'); + expect(s.name).to.not.equal('CC'); + return expect(s.isFinal()).to.be["false"]; + }); + return it("must allow transitions originating from a substate to exit", function() { + var p, s; + p = new Parent; + p.state('-> CCA'); + p.state('-> CA'); + s = p.state(); + expect(s.name).to.equal('CA'); + expect(s.name).to.not.equal('CC'); + return expect(s.isFinal()).to.be["false"]; + }); + }); + }); + describe("Mutability:", function() { + var testFinitudeOf, testImmutabilityOf, testMutabilityOf; + testMutabilityOf = function(s) { + it("is marked `mutable`", function() { + return expect(s.isMutable()).to.be["true"]; + }); + it("allows mutation of properties", function() { + s["let"]('key', "value"); + expect(s.get('key')).to.equal("value"); + s["delete"]('key'); + return expect(s.get('key')).to.be.undefined; + }); + it("allows mutation of methods", function() { + var f; + s.addMethod('m', f = function() {}); + expect(f).to.equal(s.method('m')); + s.removeMethod('m'); + return expect(s.method('m')).to.be.undefined; + }); + it("allows mutation of events", function() { + var f, id; + id = s.on('exit', f = function(transition) {}); + expect(f).to.equal(s.event('exit', id)); + s.off('exit', id); + return expect(s.event('exit', id)).to.be.undefined; + }); + it("allows mutation of guards", function() { + s.addGuard('admit', function() {}); + expect(s.guards('admit')).to.be.ok; + s.removeGuard('admit'); + return expect(s.guards('admit')).to.be.undefined; + }); + it("allows mutation of substates", function() { + s.addSubstate('A', {}); + expect(s.substate('A')).to.be["instanceof"](State); + s.removeSubstate('A'); + return expect(s.substate('A')).to.be.undefined; + }); + return it("allows mutation of transitions", function() { + s.addTransition('T', {}); + expect(s.transition('T')).to.be["instanceof"](TransitionExpression); + s.removeTransition('T'); + return expect(s.transition('T')).to.be.undefined; + }); + }; + testFinitudeOf = function(s) { + it("is marked `finite`", function() { + return expect(s.isFinite()).to.be["true"]; + }); + it("allows mutation of properties", function() { + s["let"]('key', "value"); + expect(s.get('key')).to.equal("value"); + s["delete"]('key'); + return expect(s.get('key')).to.be.undefined; + }); + it("allows mutation of methods", function() { + var f; + s.addMethod('m', f = function() {}); + expect(f).to.equal(s.method('m')); + s.removeMethod('m'); + return expect(s.method('m')).to.be.undefined; + }); + it("allows mutation of events", function() { + var f, id; + id = s.on('exit', f = function(transition) {}); + expect(f).to.equal(s.event('exit', id)); + s.off('exit', id); + return expect(s.event('exit', id)).to.be.undefined; + }); + it("allows mutation of guards", function() { + s.addGuard('admit', function() {}); + expect(s.guards('admit')).to.be.ok; + s.removeGuard('admit'); + return expect(s.guards('admit')).to.be.undefined; + }); + it("prohibits mutation of substates", function() { + s.addSubstate('A', {}); + return expect(s.substate('A')).to.be.undefined; + }); + return it("allows mutation of transitions", function() { + s.addTransition('T', {}); + expect(s.transition('T')).to.be["instanceof"](TransitionExpression); + s.removeTransition('T'); + return expect(s.transition('T')).to.be.undefined; + }); + }; + testImmutabilityOf = function(s, weak) { + if (!weak) { + it("is marked `immutable`", function() { + return expect(s.isImmutable()).to.be["true"]; + }); + } + it("prohibits mutation of properties", function() { + s["let"]('key', "value"); + return expect(s.get('key')).to.be.undefined; + }); + it("prohibits mutation of methods", function() { + s.addMethod('m', function() {}); + return expect(s.method('m')).to.be.undefined; + }); + it("does allow mutation of events", function() { + var f, id; + id = s.on('exit', f = function(transition) {}); + expect(f).to.equal(s.event('exit', id)); + s.off('exit', id); + return expect(s.event('exit', id)).to.be.undefined; + }); + it("prohibits mutation of guards", function() { + s.addGuard('admit', function() {}); + return expect(s.guard('admit')).to.be.undefined; + }); + it("prohibits mutation of substates", function() { + s.addSubstate('A', {}); + return expect(s.substate('A')).to.be.undefined; + }); + return it("prohibits mutation of transitions", function() { + s.addTransition('T', {}); + return expect(s.transition('T')).to.be.undefined; + }); + }; + describe("A state unaffected by mutability attributes", function() { + return describe("is weakly immutable, which", function() { + var o, s, weak; + state(o = {}, null); + s = o.state(''); + it("does not mark the state as `immutable`", function() { + return expect(s.isImmutable()).to.be["false"]; + }); + return testImmutabilityOf(s, weak = true); + }); + }); + describe("The `mutable` attribute:", function() { + describe("A state that is literal `mutable`", function() { + var o; + state(o = {}, 'mutable', null); + return testMutabilityOf(o.state('')); + }); + describe("A state that inherits `mutable` via superstate", function() { + var o; + state(o = {}, 'mutable', { + A: state + }); + return testMutabilityOf(o.state('A')); + }); + describe("A state that inherits `mutable` via protostate", function() { + var Class, o; + Class = (function() { + function Class() {} + + state(Class.prototype, 'mutable', null); + + return Class; + + })(); + o = new Class; + return testMutabilityOf(o.state('')); + }); + return describe("A state that inherits `mutable` via super- and proto-", function() { + var Class, o; + Class = (function() { + function Class() {} + + state(Class.prototype, 'mutable', { + A: state + }); + + return Class; + + })(); + o = new Class; + return testMutabilityOf(o.state('A')); + }); + }); + describe("The `finite` attribute:", function() { + describe("in superior overruling position relative to `mutable`", function() { + var o; + state(o = {}, 'finite', { + A: state('mutable') + }); + return testFinitudeOf(o.state('A')); + }); + describe("in inferior overruling position relative to `mutable`", function() { + var o; + state(o = {}, 'mutable', { + A: state('finite') + }); + return testFinitudeOf(o.state('A')); + }); + describe("in prototypal overruling position relative to `mutable`", function() { + var Class, o; + Class = (function() { + function Class() {} + + state(Class.prototype, 'finite'); + + return Class; + + })(); + state(o = new Class, 'mutable'); + return testFinitudeOf(o.state('')); + }); + return describe("in epitypal overruling position relative to `mutable`", function() { + var Class, o; + Class = (function() { + function Class() {} + + state(Class.prototype, 'mutable'); + + return Class; + + })(); + state(o = new Class, 'finite'); + return testFinitudeOf(o.state('')); + }); + }); + return describe("The `immutable` attribute:", function() { + describe("in superior overruling position relative to `mutable`", function() { + var o; + state(o = {}, 'immutable', { + A: state('mutable') + }); + return testImmutabilityOf(o.state('A')); + }); + describe("in inferior overruling position relative to `mutable`", function() { + var o; + state(o = {}, 'mutable', { + A: state('immutable') + }); + return testImmutabilityOf(o.state('A')); + }); + describe("in prototypal overruling position relative to `mutable`", function() { + var Class, o; + Class = (function() { + function Class() {} + + state(Class.prototype, 'immutable'); + + return Class; + + })(); + state(o = new Class, 'mutable'); + return testImmutabilityOf(o.state('')); + }); + return describe("in epitypal overruling position relative to `mutable`", function() { + var Class, o; + Class = (function() { + function Class() {} + + state(Class.prototype, 'mutable'); + + return Class; + + })(); + state(o = new Class, 'immutable'); + return testImmutabilityOf(o.state('')); + }); + }); + }); + return describe("Retained:", function() { + var Class; + Class = (function() { + function Class() {} + + state(Class.prototype, { + A: state('abstract retained', { + AA: state('initial'), + AB: state('default') + }), + B: state + }); + + return Class; + + })(); + it("initializes to own state", function() { + var AA, o; + o = new Class; + AA = o.state(); + expect(AA.name).to.equal('AA'); + expect(AA.owner).to.equal(o); + expect(AA.superstate.owner).to.equal(o); + return expect(o.state('AA')).to.equal(AA); + }); + it("applies the retained attribute", function() { + var o; + o = new Class; + return expect(o.state('A').isRetained()).to.equal(true); + }); + it("autorealizes the retained state", function() { + var o; + o = new Class; + return expect(o.state('A').isVirtual()).to.equal(false); + }); + it("stores retainee path upon exit", function() { + var o; + o = new Class; + o.state('-> B'); + return expect(o.state('A')._.retaineePath).to.equal('A.AA'); + }); + return it("restores currency of state upon arrival", function() { + var o; + o = new Class; + o.state('-> B'); + o.state('-> A'); + return expect(o.state().name).to.equal('AA'); + }); + }); + }); + +}).call(this); diff --git a/test/lib/creation.test.js b/test/lib/creation.test.js new file mode 100644 index 0000000..1c07d03 --- /dev/null +++ b/test/lib/creation.test.js @@ -0,0 +1,114 @@ +// Generated by CoffeeScript 1.6.3 +(function() { + var RootState, State, expect, state; + + expect = require('chai').expect; + + state = require('state'); + + State = state.State, RootState = state.RootState; + + describe("Creating `State` implementations using the `state` function:", function() { + describe("Implementing state on an object", function() { + var object, root; + object = {}; + state(object, null); + root = object.state(''); + it("adds a new `state` method to the object", function() { + return expect(object).to.have.ownProperty('state'); + }); + it("creates a new root state, whose name is the empty string", function() { + return expect(root).to.be["instanceof"](RootState); + }); + return it("sets the object’s current state to the root", function() { + return expect(object.state()).to.equal(root); + }); + }); + describe("Prototypal state implementations:", function() { + var Class; + Class = (function() { + function Class() {} + + state(Class.prototype, null); + + return Class; + + })(); + describe("Implementing state for a constructor’s prototype", function() { + var object; + object = new Class; + it("exposes a `state` method to an instance", function() { + return expect(object).to.have.property('state'); + }); + return it("does not create a `state` method for the instance", function() { + return expect(object).not.to.have.ownProperty('state'); + }); + }); + return describe("Side-effects of calling an inheritor’s `state` method", function() { + var object, root; + object = new Class; + object.state(); + root = object.state(''); + it("add a new own `state` method for the instance", function() { + expect(object).to.have.ownProperty('state'); + return expect(object.state).to.be.a["function"]; + }); + it("create a new root state for the instance", function() { + expect(root).to.be["instanceof"](RootState); + return expect(root.owner).to.equal(object); + }); + return it("define a *protostate* relation between the two root states", function() { + return expect(root.protostate).to.equal(Class.prototype.state('')); + }); + }); + }); + return describe("State tree declarations:", function() { + describe("Declaring a shallow tree of states for an object", function() { + var object, root; + object = {}; + state(object, { + A: state, + B: state + }); + root = object.state(''); + it("creates a root state", function() { + return expect(root).to.be["instanceof"](RootState); + }); + it("creates substates as children of the root", function() { + var a, b; + expect(a = root.substate('A')).to.be["instanceof"](State); + expect(a.superstate).to.equal(root); + expect(b = root.substate('B')).to.be["instanceof"](State); + return expect(b.superstate).to.equal(root); + }); + return it("allows direct querying of substates by name", function() { + var a, b; + expect(a = object.state('A')).to.equal(root.substate('A')); + return expect(b = object.state('B')).to.equal(root.substate('B')); + }); + }); + return describe("Declaring a deep tree of states for an object", function() { + var object, root; + object = {}; + state(object, { + A: state, + B: state({ + BA: state, + BB: state({ + BBA: state + }) + }) + }); + root = object.state(''); + return it("creates chains of deeply nested descendant `State`s", function() { + var b, bb, bba; + expect(bba = object.state('B.BB.BBA')).to.be["instanceof"](State); + expect(bb = bba.superstate).to.equal(object.state('B.BB')); + expect(b = bb.superstate).to.equal(object.state('B')); + return expect(b.superstate).to.equal(root); + }); + }); + }); + }); + +}).call(this); diff --git a/test/lib/events.test.js b/test/lib/events.test.js new file mode 100644 index 0000000..d789596 --- /dev/null +++ b/test/lib/events.test.js @@ -0,0 +1,454 @@ +// Generated by CoffeeScript 1.6.3 +(function() { + var O, RootState, State, bind, env, expect, state, + __hasProp = {}.hasOwnProperty, + __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; + + expect = require('chai').expect; + + state = require('state'); + + O = state.O, env = state.env, State = state.State, RootState = state.RootState, bind = state.bind; + + describe("Events:", function() { + describe("Context and arguments", function() { + var fix; + bind = state.bind, fix = state.fix; + it("converts raw arguments to an array", function() { + var o; + state(o = {}, { + A: state({ + enter: function(transition, args) { + return expect(args.join(' ')).to.equal("one two three"); + } + }) + }); + return (function(a, b, c) { + return o.state('-> A', arguments); + })('one', 'two', 'three'); + }); + (function() { + var o; + state(o = {}, { + A: state({ + enter: function(transition, args) { + var currentState, + _this = this; + currentState = this.state(); + it("binds context to the owner", function() { + return expect(_this).to.equal(o); + }); + return it("provides correct arguments to transition and args params", function() { + expect(transition).to.equal(currentState); + return expect(args.join(' ')).to.equal("one two three"); + }); + }, + exit: bind(function(transition, args) { + var currentState, + _this = this; + currentState = this.current(); + it("binds context to the local state", function() { + return expect(_this).to.equal(o.state('A')); + }); + return it("provides correct arguments to transition and args params", function() { + expect(transition).to.equal(currentState); + return expect(args.join(' ')).to.equal("one two three"); + }); + }) + }) + }); + describe("with a normal function", function() { + return o.state('-> A', ['one', 'two', 'three']); + }); + return describe("with a state-bound function", function() { + return o.state('->', ['one', 'two', 'three']); + }); + })(); + return (function() { + var Class, Subclass, instance, _ref; + Class = (function() { + function Class() {} + + state(Class.prototype, { + A: state + }); + + return Class; + + })(); + Subclass = (function(_super) { + __extends(Subclass, _super); + + function Subclass() { + _ref = Subclass.__super__.constructor.apply(this, arguments); + return _ref; + } + + state(Subclass.prototype, { + A: state({ + enter: fix(function(autostate, protostate) { + return function(transition, args) { + var currentState, + _this = this; + currentState = this.state(); + it("binds context for a state-fixed function to the instance", function() { + return expect(_this).to.equal(instance); + }); + it("closes over the proper autostate and protostate", function() { + expect(autostate).to.equal(Subclass.prototype.state('A')); + return expect(protostate).to.equal(Class.prototype.state('A')); + }); + return it("provides correct arguments to transition and args params", function() { + expect(transition).to.equal(currentState); + return expect(args.join(' ')).to.equal("one two three"); + }); + }; + }), + exit: fix(function(autostate, protostate) { + return bind(function(transition, args) { + var currentState, + _this = this; + currentState = this.current(); + it("binds context for a fixed-bound function to the state", function() { + expect(_this.isVirtual()).to.be.ok; + return expect(_this.protostate).to.equal(Subclass.prototype.state('A')); + }); + it("closes over the proper autostate and protostate", function() { + expect(autostate).to.equal(Subclass.prototype.state('A')); + return expect(protostate).to.equal(Class.prototype.state('A')); + }); + return it("provides correct arguments to transition and args params", function() { + expect(transition).to.equal(currentState); + return expect(args.join(' ')).to.equal("one two three"); + }); + }); + }) + }) + }); + + return Subclass; + + })(Class); + instance = new Subclass; + describe("with a state-fixed function", function() { + return instance.state('-> A', ['one', 'two', 'three']); + }); + return describe("with a fixed and bound function", function() { + return instance.state('->', ['one', 'two', 'three']); + }); + })(); + }); + describe("Delegation:", function() { + var recordEvent, recordEventWithPrefix; + recordEvent = function() { + return this.owner.eventRecords.push(this.name); + }; + recordEventWithPrefix = function(prefix) { + if (prefix == null) { + prefix = ''; + } + return function() { + return this.owner.eventRecords.push(prefix + this.name); + }; + }; + it("emits events on behalf of a substate", function() { + var o; + o = { + eventRecords: [] + }; + state(o, { + A: state('initial'), + B: state + }); + o.state('').on('enter', bind(recordEvent)); + o.state('').on(':enter', bind(recordEvent)); + o.state('').on('B:enter', bind(recordEvent)); + o.state('A').on('..B:enter', bind(recordEvent)); + o.state('-> B'); + expect(o.eventRecords).to.have.length(1); + return expect(o.eventRecords[0]).to.equal('B'); + }); + it("can be expressed as a structured `StateExpression`", function() { + var o; + o = { + eventRecords: [] + }; + state(o, { + A: state('initial'), + B: state, + events: { + enter: bind(recordEvent), + ':enter': bind(recordEvent), + 'B:enter': bind(recordEvent) + } + }); + o.state('-> B'); + expect(o.eventRecords).to.have.length(1); + return expect(o.eventRecords[0]).to.equal('B'); + }); + it("can be expressed as a shorthand `StateExpression`", function() { + var o; + o = { + eventRecords: [] + }; + state(o, { + A: state('initial'), + B: state, + enter: bind(recordEvent), + ':enter': bind(recordEvent), + 'B:enter': bind(recordEvent) + }); + o.state('-> B'); + expect(o.eventRecords).to.have.length(1); + return expect(o.eventRecords[0]).to.equal('B'); + }); + it("traverses the protostate–epistate relation", function() { + var Class, Superclass, o, _ref; + Superclass = (function() { + function Superclass() { + this.eventRecords = []; + } + + state(Superclass.prototype, { + A: state('initial'), + B: state, + enter: bind(recordEvent), + ':enter': bind(recordEvent), + 'B:enter': bind(recordEvent) + }); + + return Superclass; + + })(); + Class = (function(_super) { + __extends(Class, _super); + + function Class() { + _ref = Class.__super__.constructor.apply(this, arguments); + return _ref; + } + + return Class; + + })(Superclass); + o = new Class; + o.state('-> B'); + expect(o.eventRecords).to.have.length(1); + return expect(o.eventRecords[0]).to.equal('B'); + }); + it("emits events on behalf of arbitrary substates", function() { + var Class, Superclass, o, _ref; + Superclass = (function() { + function Superclass() { + this.eventRecords = []; + } + + state(Superclass.prototype, { + A: state('initial'), + B: state, + '*:enter': bind(recordEvent) + }); + + return Superclass; + + })(); + Class = (function(_super) { + __extends(Class, _super); + + function Class() { + _ref = Class.__super__.constructor.apply(this, arguments); + return _ref; + } + + return Class; + + })(Superclass); + o = new Class; + o.state('-> B'); + o.state('-> A'); + o.state('-> B'); + return expect(o.eventRecords.join(' ')).to.equal("B A B"); + }); + it("emits events on behalf of arbitrary substate descendants", function() { + var Class, Superclass, o, _ref; + Superclass = (function() { + function Superclass() { + this.eventRecords = []; + } + + state(Superclass.prototype, { + A: state('initial', { + AA: state + }), + B: state({ + BA: state, + BB: state({ + BBA: state + }) + }), + '**:enter': bind(recordEvent) + }); + + return Superclass; + + })(); + Class = (function(_super) { + __extends(Class, _super); + + function Class() { + _ref = Class.__super__.constructor.apply(this, arguments); + return _ref; + } + + state(Class.prototype, { + 'B.***:enter': bind(recordEventWithPrefix('^')), + C: state + }); + + return Class; + + })(Superclass); + o = new Class; + o.state('-> AA'); + o.state('-> BA'); + o.state('-> BBA'); + o.state('-> C'); + return expect(o.eventRecords.join(' ')).to.equal("AA\n^B B ^BA BA\n^BB BB ^BBA BBA\nC".split('\n').join(' ')); + }); + return it("emits delegated events inherited via parastate and superstate", function() { + var Class, Superclass, o, _ref; + Superclass = (function() { + function Superclass() { + this.eventRecords = []; + } + + state(Superclass.prototype, 'abstract', { + A: state('abstract', { + '*:enter': bind(recordEventWithPrefix('(A)')) + }), + B: state('abstract', { + '**:enter': bind(recordEventWithPrefix('(B)')) + }) + }); + + return Superclass; + + })(); + Class = (function(_super) { + __extends(Class, _super); + + function Class() { + _ref = Class.__super__.constructor.apply(this, arguments); + return _ref; + } + + state(Class.prototype, { + '**:enter': bind(recordEvent), + C: state.extend('A, B', 'default', { + CA: state({ + CAA: state, + CAB: state + }), + CB: state + }), + D: state.extend('B, A') + }); + + return Class; + + })(Superclass); + o = new Class; + o.state('-> CAA'); + o.state('-> D'); + o.state('-> CAB'); + return expect(o.eventRecords.join(' ')).to.equal("(B)CA CA (B)CAA CAA\n(B)D (A)D D\n(A)C (B)C C (B)CA CA (B)CAB CAB".split('\n').join(' ')); + }); + }); + describe("Each transitional event (`depart`, `exit`, `enter`, `arrive`)", function() { + var addEvents, callback, log, unit; + callback = function(e) { + return function(transition, args) { + return this.log("" + transition.superstate.name + ":" + e); + }; + }; + addEvents = function(root, callbackFactory) { + var e, name, s, _ref, _results; + if (callbackFactory == null) { + callbackFactory = callback; + } + _ref = root.descendants(null, { + '': root + }); + _results = []; + for (name in _ref) { + s = _ref[name]; + _results.push((function() { + var _i, _len, _ref1, _results1; + _ref1 = ['depart', 'exit', 'enter', 'arrive']; + _results1 = []; + for (_i = 0, _len = _ref1.length; _i < _len; _i++) { + e = _ref1[_i]; + _results1.push(s.on(e, callbackFactory(e))); + } + return _results1; + })()); + } + return _results; + }; + log = function(value) { + this.store.push(value); + return value; + }; + unit = { + expression: state({ + A: state('initial'), + B: state({ + BA: state, + BB: state + }) + }), + traverse: function(o) { + o.state('->'); + o.state('-> B'); + o.state('-> BA'); + return o.state('-> BB'); + }, + expectation: "A:depart\nA:exit\n:arrive\n:depart\nB:enter\nB:arrive\nB:depart\nBA:enter\nBA:arrive\nBA:depart\nBA:exit\nBB:enter\nBB:arrive" + }; + it("is emitted properly from an object’s own state tree", function() { + var o; + o = { + store: [], + log: log + }; + state(o, unit.expression); + addEvents(o.state('')); + unit.traverse(o); + return expect(o.store.join('\n')).to.equal(unit.expectation); + }); + return it("is emitted properly via prototype", function() { + var Class, o; + Class = (function() { + function Class() { + this.store = []; + } + + Class.prototype.log = log; + + state(Class.prototype, unit.expression); + + addEvents(Class.prototype.state('')); + + return Class; + + })(); + state(o = new Class); + unit.traverse(o); + return expect(o.store.join('\n')).to.equal(unit.expectation); + }); + }); + describe("Each existential event (`construct`, `destroy`)", function() {}); + return describe("The `mutate` event", function() {}); + }); + +}).call(this); diff --git a/test/lib/guards.test.js b/test/lib/guards.test.js new file mode 100644 index 0000000..860d01a --- /dev/null +++ b/test/lib/guards.test.js @@ -0,0 +1,130 @@ +// Generated by CoffeeScript 1.6.3 +(function() { + var expect, state, + __hasProp = {}.hasOwnProperty, + __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; + + expect = require('chai').expect; + + state = require('state'); + + describe("Guards:", function() { + describe("a state guard", function() { + it("resolves across superstates", function() { + var o; + state(o = {}, 'abstract', { + A: state('initial', { + admit: function(fromState) { + return false; + }, + AA: state, + AB: state({ + admit: 'AA' + }), + AC: state + }), + B: state({ + admit: 'A.*', + release: { + '.*': function(toState) { + return true; + } + }, + BA: state + }) + }); + o.state('-> AA'); + o.state('-> A'); + expect(o.state().name).to.equal('AA'); + o.state('-> AB'); + expect(o.state().name).to.equal('AB'); + o.state('-> B'); + expect(o.state().name).to.equal('B'); + o.state('-> AC'); + expect(o.state().name).to.equal('B'); + o.state('-> BA'); + expect(o.state().name).to.equal('BA'); + o.state('-> A'); + return expect(o.state().name).to.equal('BA'); + }); + return it("resolves across protostates", function() { + var Class, Superclass, o, _ref; + Superclass = (function() { + function Superclass() {} + + state(Superclass.prototype, 'abstract', { + A: state('initial', { + admit: function(fromState) { + return false; + } + }), + B: state({ + release: function(toState) { + if (toState.name === 'D') { + return true; + } + } + }), + C: state, + D: state + }); + + return Superclass; + + })(); + Class = (function(_super) { + __extends(Class, _super); + + function Class() { + _ref = Class.__super__.constructor.apply(this, arguments); + return _ref; + } + + return Class; + + })(Superclass); + o = new Class; + o.state('-> B'); + expect(o.state().name).to.equal('B'); + o.state('-> A'); + expect(o.state().name).to.equal('B'); + o.state('-> C'); + expect(o.state().name).to.equal('B'); + o.state('-> D'); + expect(o.state().name).to.equal('D'); + o.state('-> A'); + return expect(o.state().name).to.equal('D'); + }); + }); + return 0 && describe("a transition guard", function() { + return it("", function() { + var o; + o = { + data: [] + }; + return state(o, 'abstract', { + A: state('default initial'), + B: state, + C: state, + transitions: { + X: { + origin: 'A', + target: 'B', + admit: function() { + return true; + } + }, + Y: { + origin: 'B', + target: '*', + admit: function() { + return true; + } + } + } + }); + }); + }); + }); + +}).call(this); diff --git a/test/lib/linearization.test.js b/test/lib/linearization.test.js new file mode 100644 index 0000000..47a47d2 --- /dev/null +++ b/test/lib/linearization.test.js @@ -0,0 +1,132 @@ +// Generated by CoffeeScript 1.6.3 +(function() { + var expect, state; + + expect = require('chai').expect; + + state = require('state'); + + describe("Linearization:", function() { + var expressions, orderOf, reducer; + reducer = function(out, s) { + if (out) { + out += ' '; + } + return out += s.name || ''; + }; + orderOf = function(s) { + return s.linearize().reduce(reducer, ''); + }; + expressions = { + 'long diamond': state({ + A: state, + B: state, + C: state.extend('A'), + D: state.extend('B'), + E: state.extend('C, D') + }), + 'folded triple diamond': state({ + A: state, + B: state, + C: state, + D: state.extend('A, B'), + E: state.extend('A, C'), + F: state.extend('D, E') + }), + 'tesselated triple diamond': state({ + A: state, + B: state, + C: state, + D: state.extend('A, B'), + E: state.extend('B, C'), + F: state.extend('D, E') + }), + 'hierarchical TTD': state({ + A: state, + B: state({ + D: state.extend('A') + }), + C: state({ + E: state.extend('B', { + F: state.extend('D') + }) + }) + }) + }; + it("computes the monotonic order of parastates and superstates", function() { + var o; + state(o = {}, expressions['long diamond']); + expect(orderOf(o.state(''))).to.equal(''); + expect(orderOf(o.state('A'))).to.equal('A '); + expect(orderOf(o.state('B'))).to.equal('B '); + expect(orderOf(o.state('C'))).to.equal('C A '); + expect(orderOf(o.state('D'))).to.equal('D B '); + return expect(orderOf(o.state('E'))).to.equal('E C A D B '); + }); + it("computes monotonic order equivalently via protostates", function() { + var Class, o; + Class = (function() { + function Class() {} + + state(Class.prototype, expressions['long diamond']); + + return Class; + + })(); + o = new Class; + expect(orderOf(o.state(''))).to.equal(''); + expect(orderOf(o.state('A'))).to.equal('A '); + expect(orderOf(o.state('B'))).to.equal('B '); + expect(orderOf(o.state('C'))).to.equal('C A '); + expect(orderOf(o.state('D'))).to.equal('D B '); + return expect(orderOf(o.state('E'))).to.equal('E C A D B '); + }); + it("computes order via protostates, parastates, and superstates", function() { + var Class, o; + Class = (function() { + function Class() {} + + state(Class.prototype, expressions['hierarchical TTD']); + + return Class; + + })(); + o = new Class; + state(o, { + C: { + E: { + G: state.extend('D') + } + } + }); + expect(orderOf(o.state('G'))).to.equal('G D A E B C '); + o = new Class; + state(o, { + C: { + E: { + G: { + H: state.extend('D') + } + } + } + }); + return expect(orderOf(o.state('H'))).to.equal('H D A G E B C '); + }); + it("resolves the folded-triple-diamond formation", function() { + var o; + state(o = {}, expressions['folded triple diamond']); + return expect(orderOf(o.state('F'))).to.equal('F D E A B C '); + }); + it("resolves the tesselated-triple-diamond formation", function() { + var o; + state(o = {}, expressions['tesselated triple diamond']); + return expect(orderOf(o.state('F'))).to.equal('F D A E B C '); + }); + return it("preserves monotonicity with parastates-precede-superstate rule", function() { + var o; + state(o = {}, expressions['hierarchical TTD']); + return expect(orderOf(o.state('F'))).to.equal('F D A E B C '); + }); + }); + +}).call(this); diff --git a/test/lib/methods.test.js b/test/lib/methods.test.js new file mode 100644 index 0000000..094a92b --- /dev/null +++ b/test/lib/methods.test.js @@ -0,0 +1,240 @@ +// Generated by CoffeeScript 1.6.3 +(function() { + var RootState, State, bind, env, expect, fix, state; + + expect = require('chai').expect; + + state = require('state'); + + env = state.env, bind = state.bind, fix = state.fix, State = state.State, RootState = state.RootState; + + describe("Methods:", function() { + var Child, Parent, c_Am, c_Az, cm, p_AAm, p_Am, p_m, pm, setup; + pm = p_m = p_Am = p_AAm = cm = c_Am = c_Az = null; + Parent = (function() { + function Parent() {} + + Parent.prototype.m = pm = function() { + return 'Pm0'; + }; + + state(Parent.prototype, { + m: p_m = function() { + return 'Pm1'; + }, + A: { + m: p_Am = function() { + return 'Pm2'; + }, + AA: { + m: p_AAm = function() { + return 'Pm3'; + } + } + } + }); + + return Parent; + + })(); + Child = (function() { + function Child() {} + + Child.prototype.m = cm = function() { + return 'Cm0'; + }; + + state(Child.prototype, { + A: { + m: c_Am = function() { + return 'Cm2'; + }, + z: c_Az = function() { + return 'Cz2'; + } + } + }); + + return Child; + + })(); + setup = function() { + var f, m, o; + o = new Child; + o.f = f = function() { + return 'of0'; + }; + o.m = m = function() { + return 'om0'; + }; + return { + o: o, + f: f, + m: m + }; + }; + describe("Own accessor", function() { + return it("is created upon first invocation of inherited accessor", function() { + var f, m, o, _ref; + _ref = setup(), o = _ref.o, f = _ref.f, m = _ref.m; + expect(o).to.not.have.ownProperty('state'); + o.state(); + return expect(o).to.have.ownProperty('state'); + }); + }); + describe("Dispatchers:", function() { + it("creates dispatcher", function() { + expect(Child.prototype.m).to.have.property('isDispatcher'); + return expect(Child.prototype.m.isDispatcher).to.be.ok; + }); + it("saves original owner method using expando `original` on dispatcher", function() { + expect(Child.prototype.m).to.have.property('original'); + return expect(Child.prototype.m.original).to.equal(cm); + }); + it("out-of-state methods are callable, void-typed, heritable via protostate, and have the side-effect of creating an own accessor", function() { + var f, m, o, _ref; + _ref = setup(), o = _ref.o, f = _ref.f, m = _ref.m; + expect(o).to.not.have.ownProperty('state'); + expect(o.z()).to.be.undefined; + return expect(o).to.have.ownProperty('state'); + }); + it("swizzles owner methods to the root state if there exists a stateful implementation of the method", function() { + var f, m, o, _ref; + _ref = setup(), o = _ref.o, f = _ref.f, m = _ref.m; + o.state(); + expect(m).to.equal(o.state('').method('m')); + return expect(o.m()).to.equal('om0'); + }); + return it("ignores owner methods for which no stateful implementation exists", function() { + var f, m, o, _ref; + _ref = setup(), o = _ref.o, f = _ref.f, m = _ref.m; + o.state(); + return expect(f).to.equal(o.f); + }); + }); + describe("Context: state-binding and state-fixing", function() { + var methods, test; + methods = { + normal: function() { + return this; + }, + bound: bind(function() { + return this; + }), + fixed: fix(function() { + return function() { + return this; + }; + }), + both: fix(function() { + return bind(function() { + return this; + }); + }) + }; + test = function(o) { + it("works for normal functions", function() { + return expect(o.normal()).to.equal(o); + }); + it("works for bound functions", function() { + return expect(o.bound()).to.equal(o.state('A')); + }); + it("works for fixed functions", function() { + return expect(o.fixed()).to.equal(o); + }); + return it("works for functions that are both bound and fixed", function() { + return expect(o.both()).to.equal(o.state('A')); + }); + }; + describe("from the autostate", function() { + var o; + state(o = {}, { + A: state('initial', methods) + }); + return test(o); + }); + describe("from a substate", function() { + var o; + state(o = {}, { + A: state({ + methods: methods, + AA: state('initial') + }) + }); + return test(o, true); + }); + describe("from an epistate", function() { + return test(new ((function() { + function _Class() {} + + state(_Class.prototype, { + A: state('initial', methods) + }); + + return _Class; + + })())); + }); + return describe("from an episubstate", function() { + return test(new ((function() { + function _Class() {} + + state(_Class.prototype, { + A: state({ + methods: methods, + AA: state('initial') + }) + }); + + return _Class; + + })())); + }); + }); + describe("Destroying an object’s entire state tree", function() { + return it("must revert an object to its “nascent” condition; any methods previously subsumed into the root state must be returned to their original location on the object", function() { + var f, m, o, _ref; + _ref = setup(), o = _ref.o, f = _ref.f, m = _ref.m; + env.debug = true; + o.state(); + env.debug = false; + expect(f).to.equal(o.f); + expect(m).to.not.equal(o.m); + o.state('').destroy(); + expect(f).to.equal(o.f); + return expect(m).to.equal(o.m); + }); + }); + return describe("Parastate resolution:", function() { + var Class; + Class = (function() { + function Class() {} + + state(Class.prototype, { + A: state({ + m: function() { + return 'A'; + }, + D: state.extend('B') + }), + B: state, + C: state({ + E: state.extend('A', { + F: state.extend('D') + }) + }) + }); + + return Class; + + })(); + return it("works", function() { + var o; + o = new Class; + o.state('-> F'); + return expect(o.m()).to.equal('A'); + }); + }); + }); + +}).call(this); diff --git a/test/lib/perf.test.js b/test/lib/perf.test.js new file mode 100644 index 0000000..76ef648 --- /dev/null +++ b/test/lib/perf.test.js @@ -0,0 +1,179 @@ +// Generated by CoffeeScript 1.6.3 +(function() { + var log, now, state; + + state = require('state'); + + log = function() { + return console.log.apply(console, arguments); + }; + + now = (typeof performance !== "undefined" && performance !== null ? performance.now : void 0) != null ? function() { + return performance.now(); + } : function() { + var frac, int, _ref; + _ref = process.hrtime(), int = _ref[0], frac = _ref[1]; + return int + 1e-9 * frac; + }; + + 0 && describe.only("Perf:", function() { + var n, results, + _this = this; + n = 1000; + results = { + creation: {}, + invocation: {} + }; + (function() { + var Class, i, o, t, _i; + Class = (function() { + function Class() {} + + Class.prototype.m = function() { + return 1; + }; + + return Class; + + })(); + t = now(); + for (i = _i = 0; 0 <= n ? _i < n : _i > n; i = 0 <= n ? ++_i : --_i) { + o = new Class; + } + return results.creation.stateless = now() - t; + })(); + (function() { + var Class, i, o, t, _i; + Class = (function() { + function Class() {} + + return Class; + + })(); + t = now(); + for (i = _i = 0; 0 <= n ? _i < n : _i > n; i = 0 <= n ? ++_i : --_i) { + state(o = new Class, { + A: { + m: function() { + return 1; + } + } + }); + } + return results.creation.stateful_ = now() - t; + })(); + (function() { + var Class, i, o, t, _i; + Class = (function() { + function Class() {} + + state(Class.prototype, { + A: { + m: function() { + return 1; + } + } + }); + + return Class; + + })(); + t = now(); + for (i = _i = 0; 0 <= n ? _i < n : _i > n; i = 0 <= n ? ++_i : --_i) { + o = new Class; + o.state(); + } + return results.creation.inherited = now() - t; + })(); + (function() { + var Class, i, o, t, _i; + Class = (function() { + function Class() {} + + Class.prototype.m = function() { + return 1; + }; + + return Class; + + })(); + o = new Class; + t = now(); + for (i = _i = 0; 0 <= n ? _i < n : _i > n; i = 0 <= n ? ++_i : --_i) { + o.m(); + } + return results.invocation.stateless = now() - t; + })(); + (function() { + var Class, i, o, t, _i; + Class = (function() { + function Class() {} + + state(Class.prototype, { + A: state('initial', { + m: function() { + return 1; + } + }) + }); + + return Class; + + })(); + o = new Class; + t = now(); + for (i = _i = 0; 0 <= n ? _i < n : _i > n; i = 0 <= n ? ++_i : --_i) { + o.m(); + } + return results.invocation.inherited = now() - t; + })(); + (function() { + var Class, i, o, t, _i; + Class = (function() { + function Class() {} + + state(Class.prototype, { + A: state('initial', { + m: function() { + return 1; + } + }) + }); + + return Class; + + })(); + o = new Class; + t = now(); + for (i = _i = 0; 0 <= n ? _i < n : _i > n; i = 0 <= n ? ++_i : --_i) { + o.m(); + } + return results.invocation.hotcode__ = now() - t; + })(); + (function() { + var Class, i, o, t, _i; + Class = (function() { + function Class() {} + + state(Class.prototype, { + A: state('initial', { + m: function() { + return 1; + } + }) + }); + + return Class; + + })(); + o = new Class; + t = now(); + for (i = _i = 0; 0 <= n ? _i < n : _i > n; i = 0 <= n ? ++_i : --_i) { + o.m(); + } + return results.invocation.hotcode_2 = now() - t; + })(); + return log(results); + }); + +}).call(this); diff --git a/test/lib/querying.test.js b/test/lib/querying.test.js new file mode 100644 index 0000000..a98d5c4 --- /dev/null +++ b/test/lib/querying.test.js @@ -0,0 +1,122 @@ +// Generated by CoffeeScript 1.6.3 +(function() { + var RootState, State, expect, state; + + expect = require('chai').expect; + + state = require('state'); + + State = state.State, RootState = state.RootState; + + describe("Querying `State`s", function() { + describe("within an object’s state tree", function() { + var a, b, ba, bb, bba, object, root; + object = {}; + state(object, { + A: state, + B: state({ + BA: state, + BB: state({ + BBA: state, + BBB: state + }) + }), + BBB: state + }); + root = object.state(''); + a = b = ba = bb = bba = null; + it("resolves top-level substates by name", function() { + expect(a = object.state('A')).to.be["instanceof"](State); + expect(a.superstate).to.equal(root); + expect(b = object.state('B')).to.be["instanceof"](State); + return expect(b.superstate).to.equal(root); + }); + it("resolves descendant substates by fully-qualified path", function() { + expect(ba = object.state('B.BA')).to.be["instanceof"](State); + expect(ba.superstate).to.equal(b); + expect(bb = object.state('B.BB')).to.be["instanceof"](State); + expect(bb.superstate).to.equal(b); + expect(bba = object.state('B.BB.BBA')).to.be["instanceof"](State); + return expect(bba.superstate).to.equal(bb); + }); + it("resolves non-ambiguously named descendants by name", function() { + bba = object.state('B.BB.BBA'); + return expect(object.state('BBA')).to.equal(bba); + }); + it("resolves absolute paths to ambiguously named descendants", function() { + var bbb; + bbb = object.state('BBB'); + bb = object.state('BB'); + expect(bbb.superstate).to.equal(root); + return expect(bbb.superstate).to.not.equal(bb); + }); + it("employs recursive descent to disambiguate relative paths", function() { + var path, s1, s2; + b = object.state('B'); + path = '.BBB'; + expect(s1 = b.query(path)).to.not.equal(s2 = root.query(path)); + return expect(s1.superstate.superstate.superstate).to.equal(s2.superstate); + }); + return it("employs recursive ascent", function() { + a = object.state('A'); + bb = object.state('BB'); + return expect(bb.query('.A')).to.equal(a); + }); + }); + return describe("across prototypes:", function() { + var Child, Parent; + Parent = (function() { + function Parent() {} + + state(Parent.prototype, { + A: state, + B: state + }); + + return Parent; + + })(); + Child = (function() { + function Child() {} + + state(Child.prototype, { + B: { + BA: state, + BB: { + BBA: state + } + } + }); + + return Child; + + })(); + return describe("An inheritor", function() { + var object; + object = new Child; + return it("initializes to a realized root state", function() { + var current; + current = object.state(); + expect(current).to.be["instanceof"](RootState); + return expect(current.isVirtual()).to.equal(false); + }); + }); + }); + }); + + /* + + do -> + class Class + state @::, + A: state + B: state + BA: state + BB: state + BBA: state + + describe "", -> + */ + + +}).call(this);