O                     = require 'omicron'
state                 = require './state-function'
StateEventEmitter     = null
StateExpression       = null
TransitionExpression  = null

{ STATE_ATTRIBUTES, TRAVERSAL_FLAGS } = state

module.exports =

State

A state defines a subset of behavior for its owner object. Each State holds a reference to a superstate, from which it may inherit more generic behavior, forming a state tree rooted by a single RootState.

States Object model Superstates and substates

The owner’s RootState designates exactly one of the States in its tree as its current state. This reference may be transitioned to a different State in the tree, causing the owner’s projected behavior to change.

An owner and its tree of States are also heritable along the owner’s prototype chain. Inheritors of a stateful prototype effectively possess all of the prototype’s States, but each inheritor can adopt its own current state and instigate transitions independently of the prototype.

Protostates and epistates

class State

  { memoizeProtostates, useDispatchTables } = state.options

  { env, NIL, isArray, isEmpty, has, hasOwn } = O
  { assign, edit, delta, clone, lookup, flatten } = O

Bit field constants will be used extensively throughout the class’s constructor and methods, so make them available as free variables.

  {
    INCIPIENT, ATOMIC, DESTROYED
    VIRTUAL
    PARASTATIC
    MUTABLE, FINITE, STATIC, IMMUTABLE
    INITIAL, CONCLUSIVE, FINAL
    ABSTRACT, CONCRETE, DEFAULT
    REFLECTIVE
    HISTORY, RETAINED, SHALLOW
    CONCURRENT
    NORMAL
  } =
      assign this, STATE_ATTRIBUTES

For methods that query related states, the default behavior is to recurse through substates, superstates, and protostates.

  { VIA_NONE, VIA_SUB, VIA_SUPER, VIA_PROTO, VIA_ALL } =
      assign this, TRAVERSAL_FLAGS

Precompute certain useful attribute combinations.

  MUTABLE_OR_FINITE     = MUTABLE | FINITE
  ABSTRACT_OR_CONCRETE  = ABSTRACT | CONCRETE
  INCIPIENT_OR_VIRTUAL  = INCIPIENT | VIRTUAL
  INCIPIENT_OR_MUTABLE  = INCIPIENT | MUTABLE

A bit mask indicates the attributes that can be inherited via protostates.

  PROTO_HERITABLE_ATTRIBUTES =
    PARASTATIC  |
    MUTABLE     |  FINITE      |  STATIC     |  IMMUTABLE  |
    INITIAL     |  CONCLUSIVE  |  FINAL      |
    ABSTRACT    |  CONCRETE    |  DEFAULT    |
    REFLECTIVE  |
    HISTORY     |  RETAINED    |  SHALLOW    |
    CONCURRENT  |
    NORMAL

Supporting classes

These are keyed here as a placeholder, and will be valued with a forward import at the end of this class definition.

  Metaobject: null
  Expression: null

Constructor

The constructor is limited to setting the references that define this State’s relations, and then computing its attributes bit mask based on those relations and the intrinsic heritability of each attribute.

Only afterward is the State’s Metaobject, as defined in a provided expression, then initialized into the instance, if necessary.

  constructor: ( base, @name, expression ) ->
Relation definitions

The base argument can specify either a superstate from which to inherit, or an owner for which to act as a new root state.

    if base instanceof State
    then superstate = base; root = superstate.root; owner = root.owner
    else superstate = null; root = this; owner = base

    @owner = owner
    @root = root
    @superstate = superstate
    @protostate = protostate = @getProtostate() or null

The linearized resolution order for this will be set later, either when the instance is realized, or on demand if a lookup is performed and the order is not set.

    @order = null
Attribute inheritance masking

Explicitly defined literal attributes for this state are encoded as a bit field integer within expression, and then superimposed atop the inherited attribute values acquired from this state’s superstate and protostate.

    attributes = expression?.attributes or NORMAL

The mutable and finite attributes can be inherited from the superstate straight away.

    if superstate?
      superAttr = superstate.attributes
      attributes |= superAttr & MUTABLE_OR_FINITE

A subset of the attributes may be inherited from protostates.

    if protostate?
      protoAttr = protostate.attributes & PROTO_HERITABLE_ATTRIBUTES

Literal concrete forcibly contradicts literal abstract; if a bad production includes both attributes, negate abstract.

      attributes &= ~ABSTRACT if attributes & CONCRETE

Literal abstract may override inherited concrete, and vice versa, so filter those attributes out of the protostate before inheriting.

      if attributes & ABSTRACT_OR_CONCRETE
        protoAttr &= ~ABSTRACT_OR_CONCRETE
      attributes |= protoAttr

If at this point the state is not abstract, then it is definitely concrete.

    attributes |= CONCRETE if ~attributes & ABSTRACT

Literal or inherited immutable contradicts mutable absolutely, and implies finite.

    attributes |= ( superAttr | protoAttr ) & IMMUTABLE
    if attributes & IMMUTABLE
      attributes = attributes & ~MUTABLE | FINITE

    @attributes = attributes
Offloaded initialization
    @initialize expression unless attributes & VIRTUAL
Debug properties

For easy viewing in the inspector.

    if env.debug
      @[' <path>']       = @path()
      @['<attributes>']  = StateExpression.decodeAttributes attributes

Private entities

createDispatcher

For each method defined in any of the owner’s states, a dispatcher must be created and assigned on the owner itself at the methodName key. Calls to owner.methodName are then delegated by the dispatcher to the owner’s current state, from which the appropriate implementation for the method will be located and applied, and its result returned to the call site.

addMethod Dispatchers

  createDispatcher = do ->
    toString = -> "[dispatcher]"
    ( accessorName, methodName, original ) ->
      dispatcher = -> @[ accessorName ]().apply methodName, arguments
      dispatcher.isDispatcher = yes
      dispatcher.toString = toString if env.debug
      dispatcher.original = original if original
      dispatcher

Essential methods

initialize

Builds out the state’s members based on the expression provided.

Constructor

  initialize: ( expression ) ->
    { attributes } = this
    return if attributes & VIRTUAL

    @attributes |= INCIPIENT
    @realize expression
    @attributes &= ~INCIPIENT

    @emit 'construct', expression, VIA_PROTO
    this

realize

Transforms an incipient or virtual State into a real state.

Initialization of a State’s contents is offloaded from the constructor via initialize to here.

virtualize realize Virtual epistates

  realize: ( expression ) ->
    { attributes, name } = this
    return this unless attributes & INCIPIENT_OR_VIRTUAL

Begin realization higher in the superstate chain if necessary, adding each newly realized state to the substates collection of its real superstate.

    if attributes & VIRTUAL
      if ss = @superstate
        do ss.realize if ss.attributes & VIRTUAL
        substates = ss._.substates ?= {}
        do substates[ name ].destroy if substates[ name ]?
        substates[ name ] = this
      @attributes &= ~VIRTUAL

A State’s Metaobject is stored in an underscore _ property; the existence of _ in this implies that this must be a realized state.

    @_ ?= new @Metaobject

Normalize any declarations of parastates. These will be used later in the outer realize function to recursively compute a linearization for this state and for its descendant substates.

    if parastates = expression?.parastates
      if isArray parastates then parastates = parastates.join ','
      throw TypeError unless typeof parastates is 'string'
      parastates = parastates.split /\s*,\s*/
      if parastates.length
        @_.parastates = parastates
        @attributes |= PARASTATIC

Populate the rest of the empty metaobject by “mutating” against expression.

    @mutate expression if expression?

Realizing a root state requires that, for each of the owner’s own methods, if there exists at least one stateful implementation of that method located higher in the owner’s prototype chain, then the owner’s implementation of that method must be copied into the root, where it defines the owner’s default behavior.

    if this is @root then for own key, method of @owner
      @addMethod key, method if key isnt 'constructor' and
        typeof method is 'function' and not method.isDispatcher and
        @method key, VIA_PROTO

The superstate and parastates of this State are linearized into a defined order. Because the States of a state tree are constructed bottom-up, in the case of an incipient state, the linearization step must be deferred to the root, which will then recursively linearize its descendants from the top down, after each one has been instantiated and situated in the tree.

    @linearize VIA_SUB if this is @root or ~attributes & INCIPIENT

    this

virtualize

Creates, if necessary, a virtualized epistate of this protostate within the state tree to which inheritor belongs, and also creates as many virtual ancestor superstates as necessary to reach a real State within that tree.

Returns the state on inheritor’s state tree for which this is a protostate. This will be the newly created virtual state, unless virtualization was unnecessary, in which case it will be the extant real epistate of this.

Virtual epistates

  virtualize: ( inheritor ) ->

Verify relation between respective owners of inheritor and this.

    return null unless inheritor instanceof State and
      @owner.isPrototypeOf inheritor.owner

Get the derivation list for this.

    return null unless ( derivation = @derivation yes ).length

Traverse the real states of the inheriting state tree to their furthest depth.

    i = 0; s = inheritor.root
    while name = derivation[ i++ ]
      break unless real = s.substate name, VIA_NONE
      s = real

If derivation extends beyond the inheriting state tree’s real states, then add virtual states to it until the whole superstate chain is represented.

    expr = attributes: VIRTUAL
    while name
      s = new State s, name, expr
      name = derivation[ i++ ]
    s

linearize

Computes, records, and returns the array of States that define the order of resolution, or linearization, for inheritance of this amongst itself and ancestors that share a common owner (and thus inhabit a common state tree).

This method adapts the C3 linearization algorithm to the State model, where the returned list consists of this followed by a monotonic ordering of its parastate and superstate ancestors.

By definition a State’s protostates are excluded from order. Traversal VIA_ALL proceeds over the concatenation of the protostate chains for each State in order; this set inherently preserves the monotonicity of order.

Parastates are specified by the selector paths contained in the parastates array of the metaobject this._. Epistates inherit any parastate paths defined by their protostates, and may override their ordering.

  linearize: do ->
getParastateDeclarations

Returns the concatenation of this State’s own and protostate-derived declarations of parastates.

    getParastateDeclarations = ->
      head = @_?.parastates
      tail = getParastateDeclarations.call ps if ps = @protostate
      if tail? then ( head?.concat tail ) ? tail else head
merge

Creates and returns a monotonic ordered set of States from the provided lists, where lists is an array of arrays, consisting of an array of “parent” states, preceded by arrays that represent, in order, the linearization of each “parent” state.

    merge = ( out, lists ) ->
      return out unless lists.length
      for headList, index in lists when headList?
        head = headList[0]; bad = no
        for otherList in lists when otherList isnt headList
          i = 1; while item = otherList[ i++ ]
            if item is head then bad = yes; break
          break if bad
        continue if bad
        out.push head
        remainingLists = []; for list in lists
          do list.shift if list[0] is head
          remainingLists.push list if list.length
        return merge out, remainingLists
      throw new TypeError "Ambiguous resolution order for '#{ out.pop() }'"
linearize

Computes the linearization of this from its named parastates and referenced superstate, and saves this array as this.order.

    linearize = ( via = VIA_NONE ) ->
      if this is @root then order = [this] else

Determine the ordered set of States from which this inherits. By rule any parastates precede the superstate. Declarations of parastates include those inherited from protostates; all parastates are resolved to unique own States of the prevailing owner’s state tree.

        parents = []
        if paths = getParastateDeclarations.call this
          { owner } = this
          for path in paths
            unless parastate = state.own owner, path
              throw new ReferenceError "Unresolvable parastate '#{ path }'"
            parents.push parastate unless parastate in parents
        parents.push @superstate

Create an array of the linearizations for each parent, followed by a list of the parents themselves, then merge each of these lists to create a single ordered set that defines the monotonic linearization of this.

        lists = []
        for parent in parents
          lists.push ( parent.order ? parent.linearize() )[..]
        lists.push parents
        order = merge [this], lists

Set the determined order, then recurse downward if so directed (e.g., this is instigated from the root state as the last step of realization, after all of its substates and descendants are in place).

      @order = order
      s.linearize via for own name, s of @_.substates if via & VIA_SUB
      order

express

Returns an expression of this state — a data structure that contains an exported snapshot of the state’s own contents.

By default the returned expression is returned as a plain object; if typed is truthy, the expression is a formally typed StateExpression.

  express: do ->
    cloneCategory = ( object ) ->
      return unless object?
      ( out = {}; break ) for key of object
      if out then for key, value of object
        out[ key ] = if value and typeof value is 'object'
        then clone value
        else value
      out

    cloneEvents = ( events ) ->
      return unless events?
      ( out = {}; break ) for type, emitter of events when emitter
      for type, emitter of events when emitter
        out[ type ] = clone emitter.items
      out

    cloneSubstates = ( substates, typed ) ->
      return unless substates?
      ( out = {}; break ) for name of substates
      for name, substate of substates
        out[ name ] = substate.express typed
      out

    return express = ( typed ) ->
      if _ = @_ then expression = edit {}, {  # Why `edit`???
        @attributes
        data        : cloneCategory   _.data
        methods     : cloneCategory   _.methods
        events      : cloneEvents     _.events
        guards      : cloneCategory   _.guards
        states      : cloneSubstates  _.substates, typed
        transitions : cloneCategory   _.transitions
      }
      if typed then new @Expression expression else expression

mutate

Transactionally mutates this state by adding, updating, or removing items as specified by the expression provided in expr.

  mutate: do ->
    { NIL, isArray, isEmpty, isPlainObject, edit, diff } = O

    editEvent = ( object, emitter ) ->
      { items } = emitter
      for own key, value of object
        if value is NIL then emitter.remove key
        else if value and value isnt items[ key ]
          emitter.set key, value

    return mutate = ( expr ) ->
      { attributes, Expression } = this
Preparation steps

Booleans to determine whether mutation of particular categories is permissible; all content is mutable for the special case of a state being initialized.

      incipient = attributes & INCIPIENT
      return if not incipient and attributes & IMMUTABLE
      mutable = incipient or attributes & MUTABLE

Realize this state if necessary, then load the category collections.

      do @realize if attributes & VIRTUAL
      { data, methods, events, guards, substates, transitions } = @_

Validate the provided state expression.

      expr = new Expression expr unless expr instanceof Expression

Hold onto an expression of a state’s contents before the mutation, to be used for comparison later on the mutate event. This step does not pertain to an incipient state, which will be “mutating” against nothing, and so will suppress the emission of a mutate event.

      before = @express() unless incipient

Since mutate is transactional, the ATOMIC flag must be used to signal the methods utilized here that add or remove content to temporarily suppress their usual emission of a mutate event.

      @attributes |= ATOMIC
Data mutations

Data is already set up to handle differentials that contain NIL values.

      @data expr.data if expr.data
Method mutations

Methods are stored as a simple key mapping, and addMethod can be used both to create an entry and to update an existing entry, without any additional side-effects, so method expressions can simply be compared against the NIL value.

      if mutable then for own name, method of expr.methods
        if method isnt NIL
        then @addMethod name, method
        else @removeMethod name
Event mutations

Event listeners for a given event type might be expressed as either:

  • a simple Array of items to be added;
  • a plain Object that maps items to specific event ids in the internal emitter that should be added, updated, or deleted; or
  • an Array that also includes one or more such Objects.
      if mutable then for own type, event of expr.events
        events or = @_.events or = {}
        emitter = events[ type ]
        if event is NIL then do emitter?.empty; continue

If event contains items to be added, and an emitter does not already exist for this event type, then one must be created.

        if not emitter and event and not isEmpty event
          emitter = events[ type ] = new StateEventEmitter this, type

        if isArray event
        then for element in event when element? and element isnt NIL
          if isPlainObject element
          then editEvent element, emitter
          else @addEvent type, element
        else editEvent event, emitter if isPlainObject event

        unless emitter.length
          do emitter.destroy
          delete events[ type ]
Guard mutations

Guards are stored as simple objects, and altering them causes no side-effects, so a deep edit is sufficient.

      if mutable and expr.guards
        guards or = @_.guards or = {}
        edit 'deep', guards, expr.guards
Substate mutations

Substates are instances of State, which are either created, destroyed, or recursively updated in place, as specified by expr.states.

By default, a state is weakly immutable, in which case its direct contents cannot be altered, although any of its substates may yet be mutable, so any submutations must therefore still be applied recursively to their corresponding substates.

      for own name, stateExpr of expr.substates
        if substates and name of substates
          if stateExpr is NIL
          then @removeSubstate name
          else substates[ name ].mutate stateExpr
        else @addSubstate name, stateExpr if stateExpr isnt NIL
Transition mutations

Transitions, as held by a State, are instances of TransitionExpression, which are either created, deleted, or replaced, as specified by expr.transitions.

      if mutable then for own name, transitionExpr of expr.transitions
        if transitions and name of transitions
          if transitionExpr is NIL
          then delete transitions[ name ]
          else transitions[ name ] =
            new TransitionExpression transitionExpr
        else @addTransition name, transitionExpr if transitionExpr isnt NIL
Cleanup and export

The transaction is complete, so clear ATOMIC to signal the add... methods to emit individual mutate events as usual.

      @attributes &= ~ATOMIC

Finally the before snapshot is used to acquire the residue of the mutation, which is emitted as part of a mutate event.

      unless incipient
        after = @express()
        residue = diff before, after
        unless isEmpty residue
          @emit 'mutate', [ expr, residue, before, after ], VIA_PROTO

      this

destroy

Attempts to cleanly destroy this state and all of its substates. A destroy event is issued to each state after it has been destroyed.

destroy (method) destroy (event)

  destroy: ->
    { owner, root, superstate, _ } = this
    { methods, events, substates } = _ if _

If a transition is underway that involves any state other than the root, then the state cannot be destroyed.

    if transition = root._transition
      if this is root then do transition.abort
      else return no if ( transition.origin.isIn this ) or
        ( transition.target.isIn this )

Descendant states are destroyed bottom-up.

    do substate.destroy for own name, substate of substates

The final event emitted is destroy.

    @emit 'destroy', VIA_PROTO
    if events then for key, event of events
      do event.destroy
      delete events[ key ]

When the root state is destroyed, the owner gets back its original methods, and the corresponding dispatcher for each such method is destroyed, along with the owner’s accessor method.

    if this is root
      for name of methods when dispatcher = owner[ name ]
        continue unless dispatcher.isDispatcher
        if ownerMethod = dispatcher.original
        then owner[ name ] = ownerMethod
        else delete owner[ name ]
      delete owner[ @accessorName ]

The metaobject should be cleared explicitly, as it may contain a cyclical reference to this at the head of its linearization array. This should be sufficient to allow garbage-collection since only this should ever hold a reference to the metaobject.

    @_ = null

A flag is set that can be observed later by anything retaining a reference to this state (e.g. a memoization) which would be withholding it from being garbage-collected.

    @attributes |= DESTROYED

A non-root state must remove itself from its superstate.

    superstate?.removeSubstate @name

    yes

Attribute methods

Methods that inspect a state’s attributes.

Attributes Attributes

  isVirtual:     -> !!( @attributes & VIRTUAL )
  isMutable:     -> !!( @attributes & MUTABLE )
  isFinite:      -> !!( @attributes & FINITE )
  isStatic:      -> !!( @attributes & STATIC )
  isImmutable:   -> !!( @attributes & IMMUTABLE )
  isInitial:     -> !!( @attributes & INITIAL )
  isConclusive:  -> !!( @attributes & CONCLUSIVE )
  isFinal:       -> !!( @attributes & FINAL )
  isAbstract:    -> !!( @attributes & ABSTRACT )
  isConcrete:    -> !!( @attributes & CONCRETE )
  isDefault:     -> !!( @attributes & DEFAULT )
  isReflective:  -> !!( @attributes & REFLECTIVE )
  hasHistory:    -> !!( @attributes & HISTORY )
  isRetained:    -> !!( @attributes & RETAINED )
  isShallow:     -> !!( @attributes & SHALLOW )
  isConcurrent:  -> !!( @attributes & CONCURRENT )

Relational methods

substate

Retrieves the named substate of this state. If no such substate exists in the local state, any identically named substate held on a protostate will be returned.

substate

  substate: ( name, via = VIA_PROTO ) ->

First scan for any virtual active substates in the local state tree.

    s = @root._current
    while s?.attributes & VIRTUAL and ss = s.superstate
      return s if ss is this and s.name is name
      s = ss

Otherwise retrieve a real substate, either locally or from a protostate.

    @_?.substates?[ name ] or
    via & VIA_PROTO and @protostate?.substate name

substates

Returns an array of this state’s immediate substates. If the boolean virtual is true, any active virtual epistates will be included as well.

substates Virtual epistates

  substates: ( virtual, deep ) ->
    result = []

Include virtual substates in the returned set, if any are present.

    if virtual and ( s = @root._current ) and s.attributes & VIRTUAL and
        @isSuperstateOf s
      while s and s isnt this and s.attributes & VIRTUAL and
          ss = s.superstate
        result.unshift s if deep or ss is this
        s = ss

Include real substates.

    for own name, substate of @_?.substates
      result.push substate
      result = result.concat substate.substates undefined, yes if deep

    result

descendants

Returns a depth-first flattened array containing all of this state’s descendant substates.

descendants

  descendants: ( virtual ) -> @substates virtual, yes

addSubstate

Creates a state from the supplied expression and adds it as a substate of this state. If a substate with the same name already exists, that state is first destroyed and then displaced.

addSubstate

  addSubstate: ( name, expression ) ->
    { attributes } = this
    unless attributes & INCIPIENT
      return if attributes & FINITE
      return unless attributes & MUTABLE
    do @realize if attributes & VIRTUAL

    substates = @_.substates or = {}
    do substate.destroy if substate = substates[ name ]

    substate = if expression instanceof State
    then expression.realize() if expression.superstate is this
    else new State this, name, expression

    return null unless substate

    substates[ name ] = substate

removeSubstate

Removes the named substate from the local state, if possible.

removeSubstate

  removeSubstate: ( name ) ->
    { attributes } = this
    return if attributes & VIRTUAL

    substates = @_?.substates
    return unless substate = substates?[ name ]
    return unless attributes & MUTABLE or substate?.attributes & DESTROYED

If a transition is underway involving substate, the removal must fail.

    return no if ( transition = @root._transition ) and (
      substate.isSuperstateOf( transition ) or
      substate is transition.origin or substate is transition.target
    )

Currency must be evacuated before the state can be removed.

    @change this, forced: yes if @root._current.isIn substate

    delete substates[ name ]

    substate

derivation

Returns a State array of this state’s superstate chain, starting after the root state and ending at this. If byName is set to true, a string array of the states’ names is returned instead.

derivation

  derivation: ( byName ) ->
    results = []; ss = this; while ( s = ss ) and ss = s.superstate
      results.push if byName then s.name or '' else s
    results.reverse()

path

Returns this state’s fully qualified name.

Alias: toString

path

  path: -> @derivation( yes ).join '.'
  toString: @::path

depth

Returns the number of superstates this state has. The root state returns 0, its immediate substates return 1, etc.

depth

  depth: ->
    n = 0; s = this
    n += 1 while s = s.superstate
    n

common

Returns the least common ancestor of this and other. If this is itself an ancestor of other, or vice versa, then that ancestor is returned.

common

  common: ( other ) ->
    other = @query other unless other instanceof State
    if @depth() > other.depth() then s = other; other = this else s = this
    while s
      return s if s is other or s.isSuperstateOf other
      s = s.superstate
    null

is

Determines whether this is other.

  is: ( other ) ->
    other = @query other unless other instanceof State
    other is this

isIn

Determines whether this is or is a substate of other.

  isIn: ( other ) ->
    other = @query other unless other instanceof State
    other is this or other.isSuperstateOf this

hasSubstate

Determines whether this is or is a superstate of other.

  hasSubstate: ( other ) ->
    other = @query other unless other instanceof State
    other is this or @isSuperstateOf other

isSuperstateOf

Determines whether this is a superstate of other.

  isSuperstateOf: ( other ) ->
    other = @query other unless other instanceof State
    if superstate = other.superstate
      this is superstate or @isSuperstateOf superstate
    else no

defaultSubstate

Returns the first substate marked default, or simply the first substate. Recursion continues into the protostate only if no local substates are marked default.

defaultSubstate

  defaultSubstate: ( via = VIA_PROTO, first ) ->
    for s in substates = @substates()
      return s if s.attributes & DEFAULT
    first or substates.length and first = substates[0]
    if via & VIA_PROTO and protostate = @protostate
      return protostate.defaultSubstate VIA_PROTO
    first

initialSubstate

Performs a “depth-within-breadth-first” recursive search to locate the most deeply nested initial state by way of the greatest initial descendant state. Recursion continues into the protostate only if no local descendant states are marked initial.

initialSubstate

  initialSubstate: ( via = VIA_PROTO ) ->
    i = 0; queue = [ this ]
    while subject = queue[ i++ ]
      for s in substates = subject.substates undefined, !!VIA_PROTO
        return s.initialSubstate( VIA_NONE ) or s if s.attributes & INITIAL
        queue.push s
    if via & VIA_PROTO and protostate = @protostate
      return protostate.initialSubstate VIA_PROTO

getProtostate

getProtostate

Returns this state’s protostate, the State that both:

  1. belongs to the nearest possible prototype of @owner; and
  2. is taxonomically analogous to this, the inheriting epistate.

If the owner does not share an analogous state tree with its immediate prototype, or if that prototype’s tree does not contain a State analogous to this, then the search is iterated up the owner’s prototype chain.

  getProtostate: ->
    { getPrototypeOf } = O
    { owner, root } = this
    { accessorName } = root
    path = @path()

Walk up the prototype chain, and, starting at each prototype’s root state, use this state’s path to locate the nearest analogous protostate.

    prototype = getPrototypeOf owner
    while prototype
      if protostate = prototype[ accessorName ]? path, VIA_NONE
        return protostate
      prototype = getPrototypeOf prototype
    null

isProtostateOf

Determines whether this is a state analogous to state on any object in the prototype chain of state’s owner.

isProtostateOf

  isProtostateOf: ( other ) ->
    other = @query other unless other instanceof State
    if protostate = other.protostate
      this is protostate or @isProtostateOf protostate
    else no

query

Matches a selector string with the state or states it represents, evaluated first in the context of this, then its substates, and then its superstates, until all locations in the state tree have been searched for a match of selector.

Returns the matched State, or an Array containing the set of matched states. If a state to be tested against is provided, a Boolean is returned, indicating whether against is the matched state or is included in the matching set.

If no matching state is found relative to the context of this state, then the query is recursed via the substates, superstates, and protostates of this, unless otherwise directed, such that a uniquely named state can be located by name alone from anywhere in the state tree.

Selectors query

  query: ( selector, against, via, toBeSkipped ) ->
    if typeof against is 'number'
      toBeSkipped = via; via = against; against = undefined
    via = VIA_ALL unless via?

A few exceptional cases may be resolved early.

    unless selector?
      return if against is undefined then null else no
    if selector is '.'
      return if against is undefined then this else against is this
    if selector is ''
      return if against is undefined then @root else against is @root

Absolute wildcard expressions compared against the root state pass immediately.

    return yes if against and against is @root and /^\*+$/.test selector

Pure ./* expressions should not be recursed.

    via &= ~( VIA_SUB | VIA_SUPER ) if /^\.*\**$/.test selector

If selector is an absolute path, evaluate it from the root state as a relative path.

    if selector.charAt(0) isnt '.'
      return @root.query '.' + selector, against, VIA_SUB | VIA_PROTO

An all-. selector must have one . trimmed to parse correctly.

    selector = selector.replace /^(\.+)\.$/, '$1'

Split selector into tokens, consume the leading empty-string straight away, then parse the remaining tokens. A cursor reference to a matching State in the tree is kept, beginning with the context state (this), and updated as each token is consumed.

    parts = selector.split '.'
    i = 0; l = parts.length; cursor = this
    while cursor
      i += 1

Upon reaching the end of token stream, return the State currently referenced by cursor.

      return ( if against then against is cursor else cursor ) if i >= l

Consume a token.

      name = parts[i]

Interpret a single wildcard as any immediate substate of the cursor state parsed thus far.

      if name is '*'
        return cursor.substates() unless against
        return yes if cursor is against.superstate
        break

Interpret a double wildcard as any descendant state of the cursor state parsed thus far.

      if name is '**'
        return cursor.substates undefined, yes unless against
        return yes if cursor.isSuperstateOf against
        break

Empty string, the product of leading/consecutive .s, implies cursor’s superstate.

      if name is '' then cursor = cursor.superstate

Interpret any other token as an identifier that names a specific substate of cursor.

      else if next = cursor.substate name then cursor = next

If no matching substate exists, the query fails for this context.

      else break

Recursively descend the tree, breadth-first, and retry the query with a different context.

    if via & VIA_SUB
      i = 0; queue = [ this ]
      while subject = queue[ i++ ]
        for substate in subject.substates yes
          continue if substate is toBeSkipped
          result = substate.query selector, against, VIA_NONE
          return result if result
          queue.push substate

Recursively ascend the tree and retry the query, but skip this subtree during the subsequent descent, since it’s already been searched.

    if via & VIA_SUPER
      return result if result = @superstate?.query( selector, against,
        via & VIA_SUB | VIA_SUPER, this if via & VIA_SUB )

Retry the query on the protostate.

    if via & VIA_PROTO
      return result if result = @protostate?.query selector, against, via

All possibilities exhausted; no matches exist.

    return if against then no else null

$

Convenience method that either aliases to change if passed a function for the first argument, or aliases to query if passed a string — thereby mimicking the behavior of the object’s accessor method.

createAccessor

  $: ( expr, args... ) ->
    if typeof expr is 'function'
      return @change.apply this, [ expr ].concat args if expr = expr()
    else if typeof expr is 'string' and
        ( match = expr.match rxTransitionArrow ) and
          method = transitionArrowMethods[ match[1] ]
      return if args.length
      then @[ method ].apply this, [ match[2] ].concat args
      else @[ method ] match[2]

Currency methods

Methods that inspect or affect the owner’s current state.

current

Gets the local state tree’s current state, which is authoritatively determined by the root state.

current

  current: -> @root._current

isCurrent

Returns a Boolean indicating whether this is the owner’s current state.

isCurrent

  isCurrent: -> this is @current()

isActive

Returns a Boolean indicating whether this or one of its substates is the owner’s current state.

isActive

  isActive: -> this is ( current = @current() ) or @isSuperstateOf current

change

Forwards a change command to root and returns its result. Calling with no arguments implicitly directs the root to change to this state.

Aliases: go, be

change

  change: ( target, options ) ->
    ( root = @root ).change.apply root, arguments

  go: @::change
  be: @::change

Data methods

data

Either retrieves or edits a block of data associated with this state.

If provided no argument, or an integer bit field, then data returns a copy of the data attached to this state, including all data from inherited states, unless specified otherwise by the via query flags.

If called with an object-typed argument, data edits the data held on this state. For keys in mutation whose values are set to the NIL directive, the matching keys in the state’s data are deleted. If the operation results in a change to the state’s data, a mutate event is emitted for this state.

Data data (read) data (write)

  data: ( via = VIA_ALL, out ) ->

If the provided via argument is not a flags integer mask, and presumably an object instead, then interpret this call as a write operation, and refer to the parameter as mutation.

    mutation = via if via isnt via << 0
    if mutation
      { attributes } = this
      if attributes & INCIPIENT_OR_MUTABLE and not isEmpty mutation
        return @realize().data mutation if attributes & VIRTUAL
        residue = delta @_.data ?= {}, mutation
        if ~attributes & ATOMIC and residue and not isEmpty residue
          @emit 'mutate', [ mutation, residue ], VIA_PROTO
      this

Otherwise read and return a copy of this state’s data, including data inherited via superstates and protostates, unless directed otherwise.

    else
      out ?= {}
      for relative in @order ? @linearize() by -1
        continue unless via & VIA_SUPER or relative is this
        edit 'deep all', out,
          ( relative.protostate?.data VIA_PROTO, out if via & VIA_PROTO ),
          relative._?.data
      out

has

Determines whether a property with the given key is contained within this state’s data storage, or that of an ancestral protostate, parastate, or superstate, unless directed otherwise by via.

  has: ( key, via = VIA_ALL ) ->
    viaSuper = via & VIA_SUPER
    viaProto = via & VIA_PROTO
    for relative in @order ? @linearize()
      s = relative; while s?
        return yes if ( data = s._?.data )? and hasOwn.call data, key
        if viaProto then s = s.protostate else break
        continue if viaSuper
    no

get

Retrieves the value of a key within this state’s data storage, or from that of an ancestral protostate, parastate, or superstate, unless directed otherwise by via.

  get: ( key, via = VIA_ALL ) ->
    viaSuper = via & VIA_SUPER
    viaProto = via & VIA_PROTO
    for relative in @order ? @linearize()
      s = relative; while s?
        if ( data = s._?.data )? and hasOwn.call data, key
          return data[ key ]
        if viaProto then s = s.protostate else break
      continue if viaSuper

let

Assigns a value to a key within this state’s data storage. If no such key already exists, it is added.

set let

  let: ( key, value ) ->
    { attributes } = this
    return unless attributes & INCIPIENT_OR_MUTABLE  # should warn
    do @realize if attributes & VIRTUAL

Assignment proceeds only if the value being written is not the same as the displaced data that is being overwritten.

    data = @_.data or = {}
    if value isnt displaced = lookup data, key
      assign data, key, value
      assign ( mutation = {} ).data = {}, key, value
      assign ( residue = {} ).data = {}, key, displaced
      @emit 'mutate', [ mutation, residue ], VIA_PROTO

    value

set

Assigns a value to an existing key within the data storage of the nearest mutable ancestral parastate or superstate of this. Returns the equivalent of a let operation on this if no such ancestor can be affected.

Only States that belong to this.owner can be affected by set; their protostates cannot.

let set

  set: ( key, value ) ->
    { attributes } = this
    return unless attributes & INCIPIENT_OR_MUTABLE
    do @realize if attributes & VIRTUAL

Find the nearest State along the linearization path of this whose data storage contains a property with the given key, and mutate that property with the given value if allowed to do so. If these steps do not complete, delegate to let.

    for relative in @order ? @linearize()
      if ( data = relative._?.data )? and hasOwn.call data, key
        return relative.let key, value if relative.attributes & MUTABLE
        break
    @let key, value

delete

Removes a property from this state’s data storage, provided that this is mutable.

  delete: ( key ) ->
    return unless @attributes & MUTABLE
    NIL is @let key, NIL

Method methods

method

Retrieves the named method for this state. Providing an optional out object allows the appropriate context for a state-bound method to be delivered as a property of out; if included, context is confined to the local state tree.

method

  method: ( methodName, via = VIA_ALL, out, returnBoxed ) ->
    { attributes } = this
    realized = ~attributes & VIRTUAL

During the pseudo-loop block, the context reference should be considered provisional, because its potential value is in part a product of the manner in which its accompanying method was retrieved. After breaking out of the block, if method is a state-bound function, then context will be either kept and exported via out, or discarded otherwise.

    loop # once

First seek a local or memoized inherited implementation of the named method.

      if realized
        if method = @_?.methods?[ methodName ]
          context = this
          break
        if record = @_?.__dispatch_table__?[ methodName ]
          [ method, context ] = record
          break if method?

If an implementation is not available locally, seek an inherited method along the protostate chain. If this succeeds, the provisional context will be the epistate that inherits the method.

      if viaProto = via & VIA_PROTO
        if method = @protostate?.method methodName, VIA_PROTO, out, yes
          context = this
          inherited = yes
          break

If no local or protostatic method exists, seek an inherited method along the linearization path. If this succeeds, the provisional context will be the superstate or parastate from which the method is inherited.

      if via & VIA_SUPER
        for relative in @order ? @linearize() when relative isnt this
          if method = relative.method methodName, viaProto, out, yes
            context = out?.context ? null
            inherited = yes
            break
        break if method?

The method does not exist.

      context = null
      break # always

    if method?

If method is a function, it is not state-bound, so context is unnecessary.

      if typeof method is 'function'
        context = null

Iff this is a realized State, inherited lookup results can be memoized in the local dispatch table.

      if realized and inherited and useDispatchTables
        table = @_.__dispatch_table__ ?= {}
        table[ methodName ] = [ method, context ]

Unbox a state-bound function unless directed otherwise.

      unless returnBoxed
        method = method.fn if method.type is 'state-bound-function'

Export method and context together if the out reference was provided.

Callers who know that method will be unbound need not provide an out.

    if out?
      out.method = method
      out.context = context

    method

methodNames

Returns an Array of names of methods defined for this state.

methodNames

  methodNames: ->
    keys methods if methods = @_?.methods

addMethod

Adds a method to this state. The provided fn may be any of:

  1. A proper function, which will be called in the context of @owner;
  2. A boxing of a bound function as prepared by state.bind, which will cause the boxed function to be called in the context of either this State or the epistate inheriting the method;
  3. A boxing of a fixed function as prepared by state.fix, whose boxed function will be closed over hard references to this as autostate, and to the protostate of this, as protostate.

If a method called methodName does not already exist in the state tree, then the owner is provided a dispatcher to accommodate calls to the appropriate state’s implementation of this method.

state.bind state.fix addMethod

  addMethod: ( methodName, fn ) ->
    return unless @attributes & INCIPIENT_OR_MUTABLE

If fn boxes a state-fixed function, then partially apply that function to extract the actual method, closed over references to the locality of this.

    if typeof fn is 'object' and fn.type is 'state-fixed-function'
      fn = fn.fn this, @protostate

    unless typeof fn is 'function' or fn?.type is 'state-bound-function'
      throw new TypeError "Must supply a plain, bound, or fixed function"

    { owner } = this

Skip ahead if the owner is already set up with a dispatcher for this method.

    unless ( ownerMethod = owner[ methodName ] )?.isDispatcher
      { root } = this

Create a new dispatcher method for owner. Its original method, if it has one, will be retained by the dispatcher, so that if the state tree is destroyed later, the method can be reinstated on owner.

      owner[ methodName ] =
        createDispatcher root.accessorName, methodName, ownerMethod

Unless we’re adding directly to root, copy ownerMethod to root — which will not have an implementation for this method. (If it did, then ownerMethod would already be a dispatcher.) From the root state it can still serve as owner’s default implementation, available to any of its States that do not override that method.

      if ownerMethod? and this isnt root
        methods = root._?.methods or = {}
        methods[ methodName ] = ownerMethod

    methods = @_?.methods or = {}
    methods[ methodName ] = fn

removeMethod

Dissociates the named method from this state object and returns its function.

removeMethod

  removeMethod: ( methodName ) ->
    return unless @attributes & MUTABLE and ( methods = @_?.methods ) and
      fn = methods[ methodName ]
    delete methods[ methodName ]
    fn

hasMethod

Determines whether this possesses or inherits a method named methodName.

hasMethod

  hasMethod: ( methodName ) ->
    method = @method methodName

hasOwnMethod

Determines whether this directly possesses a method named methodName.

hasOwnMethod

  hasOwnMethod: ( methodName ) ->
    !! @method methodName, VIA_NONE

apply

Finds a state method and applies it in the appropriate context.

If the named method does not exist locally and cannot be inherited, then noSuchMethod events are emitted, and the call returns undefined.

apply

  apply: ( methodName, args ) ->

First try to resolve the method quickly from the local dispatch table.

    if record = @_?.__dispatch_table__?[ methodName ]
      [ method, context ] = record
      method = method.fn if method?.type is 'state-bound-function'

Resort to a proper lookup if the fast way turns up nothing.

    unless method?
      if method = @method methodName, VIA_ALL, out = {}
        { context } = out

Bail out gracefully if the method definitively cannot be resolved.

      else
        @emit 'noSuchMethod', [ methodName, args ]
        @emit 'noSuchMethod:' + methodName, args
        return

If at this point a context is provided, this means that method is a state-bound function, and context will be the appropriately bound State. If no context exists, this means that method is a typical non-state-bound function, which is meant to be invoked in the usual fashion, just as if it were called directly as a method of this state’s owner.

    method.apply context or @owner, args

call

Variadic apply.

call

  call: ( methodName, args... ) ->
    @apply methodName, args

Event methods

event

Returns a registered event listener, or the number of listeners registered, for a given event type.

If an id as returned by addEvent is provided, the event listener associated with that id is returned. If no id is provided, the number of event listeners registered to type is returned.

event

  event: ( eventType, id ) ->
    return unless emitter = @_?.events?[ eventType ]
    return emitter.length if id is undefined
    id = emitter.key id if typeof id is 'function'
    emitter.get id

addEvent

Binds an event listener to the specified eventType and returns a unique identifier for the listener.

Alias: on

addEvent

  addEvent: ( eventType, fn, context ) ->
    do @realize if @attributes & VIRTUAL

    events = @_.events or = {}
    unless hasOwn.call events, eventType
      events[ eventType ] = new StateEventEmitter this

    if fn.type is 'state-fixed-function'
      fn = fn.fn this, @protostate

    events[ eventType ].add fn, context

  on: @::addEvent

removeEvent

Unbinds the event listener with the specified id that was supplied by addEvent.

Alias: off

removeEvent

  removeEvent: ( eventType, id ) ->
    @_?.events?[ eventType ].remove id

  off: @::removeEvent

emit

Invokes all listeners bound to the given event type. Callbacks inherited from protostates, parastates, and superstates are also invoked, unless otherwise directed by the traversal flags of via.

Arguments to be supplied to callbacks can be passed as an array to args.

Unless context is provided explicitly, a provisional State context is determined for event callbacks that are state-bound functions. This context will be either this or an epistate that inherits events from this.

Normal, unbound callbacks are invoked in the context of this.owner as usual.

Alias: trigger

emit

  emit: ( eventType, args, context, via = VIA_ALL ) ->
    return if typeof eventType isnt 'string'

    if typeof args is 'number'
      via = context; context = args; args = undefined
    if typeof context is 'number'
      via = context; context = undefined

    args = [args] if args? and not isArray args

Provisional context is flattened onto this.owner’s state tree.

    @_?.events?[ eventType ]?.emit args, context ? this
    if via & VIA_PROTO
      @protostate?.emit eventType, args, context ? this, VIA_PROTO
    if via & VIA_SUPER
      for relative in @order ? @linearize() when relative isnt this
        relative.emit eventType, args, context ? relative

    return

  trigger: @::emit

Guard methods

guard

Gets a guard entity for this state. A guard is a value or function that will be evaluated during a transition to determine whether an owner’s currency will be admitted into or released from the State to which the guard is applied.

Guards are inherited from protostates, but not from parastates or superstates.

evaluateGuard guard

  guard: ( guardType ) ->
    if guard = @_?.guards?[ guardType ] then clone guard
    else @protostate?.guard( guardType ) or undefined

addGuard

Adds a guard to this state, or augments an existing guard with additional entries.

addGuard

  addGuard: ( guardType, guard ) ->
    { attributes } = this
    return unless attributes & INCIPIENT_OR_MUTABLE
    do @realize if attributes & VIRTUAL
    guards = @_.guards or = {}
    edit guards[ guardType ] or = {}, guard

removeGuard

Removes a guard from this state, or removes specific entries from an existing guard.

removeGuard

  removeGuard: ( guardType, args... ) ->
    { attributes } = this
    return if attributes & VIRTUAL
    return unless attributes & MUTABLE and guards = @_.guards
    return null unless guard = guards[ guardType ]
    return ( guard if delete guards[ guardType ] ) unless args.length

    for key in flatten args when typeof key is 'string'
      entry = guard[ key ]
      return entry if delete guard[ key ]

Transition methods

A State may hold transition expressions that describe a Transition involving itself or any descendant State.

transition

Returns the named transition expression held on this state.

transition

  transition: ( name ) -> @_?.transitions?[ name ]

transitions

Returns an object containing all of the transition expressions defined on this state.

transitions

  transitions: -> clone @_?.transitions

addTransition

Registers a transition expression to this state.

addTransition

  addTransition: ( name, expression ) ->
    { attributes } = this
    return unless attributes & INCIPIENT_OR_MUTABLE
    do @realize if attributes & VIRTUAL
    unless expression instanceof TransitionExpression
      expression = new TransitionExpression expression
    transitions = @_.transitions or = {}
    transitions[ name ] = expression

removeTransition

Removes a transition expression from this state.

removeTransition

  removeTransition: ( name ) ->
    { attributes } = this
    return if attributes & VIRTUAL
    return unless attributes & MUTABLE and transitions = @_.transitions
    transition = transitions[ name ]
    delete transitions[ name ] if transition
    transition
State::Metaobject     = require './state-metaobject'
State::Expression =
StateExpression       = require './state-expression'
StateEventEmitter     = require './state-event-emitter'
TransitionExpression  = require './transition-expression'