O = require 'omicron'
state = require './state-function'
State = require './state'
TransitionExpression = require './transition-expression'
{
STATE_ATTRIBUTES
STATE_ATTRIBUTE_MODIFIERS
STATE_EXPRESSION_CATEGORIES
STATE_EXPRESSION_CATEGORY_SYNONYMS
STATE_EVENT_TYPES
GUARD_ACTIONS
} =
state
module.exports =StateExpression
A state expression is a data structure that formalizes a definition of a state’s contents.
States are declared by calling the module’s exported state() function and
passing it a descriptive plain object map. This input may be expressed in a
shorthand format, in which case it is rewritten into an unambiguous long form
that is used internally to create State instances.
class StateExpression
{ NIL, isNumber, isPlainObject, isArray } = O
{ assign, edit, clone, invert } = O
{ NORMAL } = STATE_ATTRIBUTES
attributeMap = do ->
for key, value of object = assign STATE_ATTRIBUTE_MODIFIERS
object[ key ] = key.toUpperCase()
object
attributeFlags = do ->
for key, value of object = invert STATE_ATTRIBUTES
object[ key ] = value.toLowerCase()
object
categoryMap = assign STATE_EXPRESSION_CATEGORIES
synonymMap = STATE_EXPRESSION_CATEGORY_SYNONYMS
eventTypes = assign STATE_EVENT_TYPES
guardActions = assign GUARD_ACTIONS constructor: ( attributes, expr ) ->
if typeof attributes is 'string' then expr or = {}
else unless expr? then expr = attributes; attributes = undefined
expr = interpret expr unless expr instanceof StateExpression
edit 'deep all', this, expr
if attributes?
attributes = encode attributes unless isNumber attributes
else { attributes } = expr if expr
@attributes = attributes or NORMALPrivate functions
interpret
Transforms a plain-object expr into a well-formed StateExpression, making
the appropriate type inferences for any shorthand notation encountered.
interpret = ( expr ) ->Start with a null-valued result object keyed with the category names.
result = assign STATE_EXPRESSION_CATEGORIES, nullCategorization
for own key, value of exprPriority 1: Recognize an explicitly named category object.
category = categoryMap[ key ] or synonymMap[ key ]
if category? and value?
result[ category ] =
if typeof value is 'string' then value
else if isArray value then value.slice 0
else clone result[ category ], value
continuePriority 2: Do a nominative type match for explicit expression instances.
The state function serves as a sentinel value indicating empty-expression.
category =
if value is state or value instanceof StateExpression
'substates'
else if value instanceof TransitionExpression
'transitions'
if category?
item = result[ category ] or = {}
item[ key ] = value
continuePriority 3: Use keys and value types to infer implicit categorization.
category =
if eventTypes[ key ]? or typeof value is 'string'
'events'
else if guardActions[ key ]?
'guards'
else if typeof value is 'function' or ( type = value?.type ) and
( type is 'state-bound-function' or
type is 'state-fixed-function' )
'methods'
else if value is NIL or isPlainObject value
'substates'
if category?
item = result[ category ] or = {}
item[ key ] = value
continueCoersion
Event values are coerced into an array.
for own key, value of object = result.events when not isArray value
object[ key ] = [ value ]Guards are represented as an object keyed by selector, so non-object values are
coerced into a single-element object with the value keyed to the wildcard
selector *.
for own key, value of object = result.guards
object[ key ] = '*': value unless isPlainObject valueTransition values must be a TransitionExpression.
for own key, value of object = result.transitions
unless value is NIL or value instanceof TransitionExpression
object[ key ] = new TransitionExpression valueState values must resolve to a StateExpression. They may be supplied as a
plain object, or as a live State instance, which is automatically expressed
to a formal StateExpression.
for own key, value of object = result.substates
if value is state
object[ key ] = new StateExpression
else if value instanceof State
object[ key ] = value.express true
else unless value is NIL or value instanceof StateExpression
object[ key ] = new StateExpression value
resultencode
Returns the bit-field integer represented by the provided set of attributes.
encode = ( attributes ) ->
attributes = assign attributes if typeof attributes is 'string'
result = NORMAL
for own key, value of attributes when key of attributeMap
result |= STATE_ATTRIBUTES[ attributeMap[ key ] ]
resultdecode
Returns the space-delimited set of attribute names represented by the provided
bit-field integer number.
decode = ( number ) ->
( value for key, value of attributeFlags when number & key ).join ' ' @untype = untype = ( expr ) ->
result = {}
result[ key ] = value for own key, value of expr
s[ name ] = untype subexpr for name, subexpr of s = result.states
result @encodeAttributes = encode @decodeAttributes = decode