Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Converted annotate-scxml-json to JavaScript. Removed dead coffeescrip…

…t code.
  • Loading branch information...
commit 562660cb51fa3cf764d7ef4c6cb8e2bad5a38332 1 parent 531a42e
@jbeard4 authored
View
540 lib/scxml/SCXML.coffee
@@ -1,540 +0,0 @@
-# Copyright 2011-2012 Jacob Beard, INFICON, and other SCION contributors
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-
-ArraySet = require './set/ArraySet'
-stateKinds = require './state-kinds-enum'
-setupDefaultOpts = require './setup-default-opts'
-scxmlPrefixTransitionSelector = require './scxml-dynamic-name-match-transition-selector'
-
-#imports
-
-flatten = (l) ->
- a = []
- for list in l
- a = a.concat(list)
-
- return a
-
-
-#technique adapted from http://javascript.crockford.com/prototypal.html
-create = (o) ->
- if Object.create
- Object.create(o)
- else
- F = ->
- F.prototype = o
- return new F()
-
-# -> Priority: Source-Child
-getTransitionWithHigherSourceChildPriority = (model) ->
- ([t1,t2]) ->
- """
- compare transitions based first on depth, then based on document order
- """
- if model.getDepth(t1.source) < model.getDepth(t2.source)
- return t2
- else if model.getDepth(t2.source) < model.getDepth(t1.source)
- return t1
- else
- if t1.documentOrder < t2.documentOrder
- return t1
- else
- return t2
-
-class SCXMLInterpreter
-
- constructor : (@model,@opts={}) ->
-
- if @opts.printTrace
- console.debug "initializing SCXML interpreter with opts:"
- for own k,v of opts
- v = if typeof v is "function" then v.toString() else v
- console.debug k,v
-
- #default args
- #@opts.onlySelectFromBasicStates
- #@opts.printTrace = true
- #@opts.evaluationContext #sets the this object for script evaluation
- #@opts.transitionSelector ?= defaultTransitionSelector()
- #@opts.model ?= m
- #@opts.TransitionSet ?= ArraySet
- #@opts.StateSet ?= ArraySet
- #@opts.BasicStateSet ?= ArraySet
- @opts.StateIdSet ?= ArraySet
- @opts.EventSet ?= ArraySet
- @opts.TransitionPairSet ?= ArraySet
- @opts.priorityComparisonFn ?= getTransitionWithHigherSourceChildPriority(@opts.model)
- @opts.globalEval ?= window?.executeScript or eval #we parameterize this in case we want to use, e.g. jquery.globalEval
-
- @_configuration = new @opts.BasicStateSet() #full configuration, or basic configuration? what kind of set implementation?
- @_historyValue = {}
- @_innerEventQueue = []
- @_isInFinalState = false
- @_datamodel = create @model.datamodel #FIXME: should these be global, or declared at the level of the big step, like the eventQueue?
- @_timeoutMap = {}
-
-
- start: ->
- #perform big step without events to take all default transitions and reach stable initial state
- if @opts.printTrace then console.debug("performing initial big step")
- @_configuration.add(@model.root.initial)
-
- #eval top-level scripts
- #we treat these differently than other scripts. they get evaled in global scope, and without explicit scripting interface
- #this is necessary in order to, e.g., allow js function declarations that are visible to scxml script tags later.
- for script in @model.scripts
- `with(this._datamodel){ this.opts.globalEval.call(null,script) }`
-
- #initialize top-level datamodel expressions. simple eval
- for k,v of @_datamodel
- if v then @_datamodel[k] = eval(v)
-
- @_performBigStep()
- return @getConfiguration()
-
- getConfiguration: -> new @opts.StateIdSet(s.id for s in @_configuration.iter())
-
- getFullConfiguration: -> new @opts.StateIdSet(s.id for s in (flatten([s].concat @opts.model.getAncestors(s) for s in @_configuration.iter())))
-
- isIn: (stateName) -> @getFullConfiguration().contains(stateName)
-
- _performBigStep: (e) ->
- if e then @_innerEventQueue.push(new @opts.EventSet([e]))
-
- keepGoing = true
-
- while keepGoing
- eventSet = if @_innerEventQueue.length then @_innerEventQueue.shift() else new @opts.EventSet()
-
- #create new datamodel cache for the next small step
- datamodelForNextStep = {}
-
- selectedTransitions = @_performSmallStep(eventSet,datamodelForNextStep)
-
- keepGoing = not selectedTransitions.isEmpty()
-
- nonFinalStates = (s for s of @_configuration.iter() when s.kind is not stateKinds.FINAL)
-
- if nonFinalStates.length is 0
- @_isInFinalState = true
-
- _performSmallStep: (eventSet,datamodelForNextStep) ->
-
- if @opts.printTrace then console.debug("selecting transitions with eventSet: " , eventSet)
-
- selectedTransitions = @_selectTransitions(eventSet,datamodelForNextStep)
-
- if @opts.printTrace then console.debug("selected transitions: " , selectedTransitions)
-
- if not selectedTransitions.isEmpty()
-
- if @opts.printTrace then console.debug("sorted transitions: ", selectedTransitions)
-
- #we only want to enter and exit states from transitions with targets
- #filter out targetless transitions here - we will only use these to execute transition actions
- selectedTransitionsWithTargets = new @opts.TransitionSet (t for t in selectedTransitions.iter() when t.targets)
- [basicStatesExited,statesExited] = @_getStatesExited selectedTransitionsWithTargets
- [basicStatesEntered,statesEntered] = @_getStatesEntered selectedTransitionsWithTargets
-
- if @opts.printTrace then console.debug("basicStatesExited " , basicStatesExited)
- if @opts.printTrace then console.debug("basicStatesEntered " , basicStatesEntered)
- if @opts.printTrace then console.debug("statesExited " , statesExited)
- if @opts.printTrace then console.debug("statesEntered " , statesEntered)
-
- eventsToAddToInnerQueue = new @opts.EventSet()
-
- #operations will be performed in the order described in Rhapsody paper
-
- #update history states
-
- if @opts.printTrace then console.debug("executing state exit actions")
- for state in statesExited
- if @opts.printTrace then console.debug("exiting " , state)
-
- #peform exit actions
- for action in state.onexit
- @_evaluateAction(action,eventSet,datamodelForNextStep,eventsToAddToInnerQueue)
-
- #update history
- if state.history
- if state.history.isDeep
- f = (s0) => s0.kind is stateKinds.BASIC and s0 in @opts.model.getDescendants(state)
- else
- f = (s0) -> s0.parent is state
-
- @_historyValue[state.history.id] = (s for s in statesExited when f(s))
-
- # -> Concurrency: Number of transitions: Multiple
- # -> Concurrency: Order of transitions: Explicitly defined
- sortedTransitions = selectedTransitions.iter().sort( (t1,t2) -> t1.documentOrder - t2.documentOrder)
-
- if @opts.printTrace then console.debug("executing transitition actions")
- for transition in sortedTransitions
- if @opts.printTrace then console.debug("transitition " , transition)
- for action in transition.actions
- @_evaluateAction(action,eventSet,datamodelForNextStep,eventsToAddToInnerQueue)
-
- if @opts.printTrace then console.debug("executing state enter actions")
- for state in statesEntered
- if @opts.printTrace then console.debug("entering " , state)
- for action in state.onentry
- @_evaluateAction(action,eventSet,datamodelForNextStep,eventsToAddToInnerQueue)
-
- #update configuration by removing basic states exited, and adding basic states entered
- if @opts.printTrace then console.debug("updating configuration ")
- if @opts.printTrace then console.debug("old configuration " , @_configuration)
-
- @_configuration.difference basicStatesExited
- @_configuration.union basicStatesEntered
-
- if @opts.printTrace then console.debug("new configuration " , @_configuration)
-
- #add set of generated events to the innerEventQueue -> Event Lifelines: Next small-step
- if not eventsToAddToInnerQueue.isEmpty()
- if @opts.printTrace then console.debug("adding triggered events to inner queue " , eventsToAddToInnerQueue)
-
- @_innerEventQueue.push(eventsToAddToInnerQueue)
-
- #update the datamodel
- if @opts.printTrace then console.debug("updating datamodel for next small step :")
- for own key of datamodelForNextStep
- if @opts.printTrace then console.debug("key " , key)
- if key of @_datamodel
- if @opts.printTrace then console.debug("old value " , @_datamodel[key])
- else
- if @opts.printTrace then console.debug("old value is null")
- if @opts.printTrace then console.debug("new value " , datamodelForNextStep[key])
-
- @_datamodel[key] = datamodelForNextStep[key]
-
- #if selectedTransitions is empty, we have reached a stable state, and the big-step will stop, otherwise will continue -> Maximality: Take-Many
- return selectedTransitions
-
- _evaluateAction: (action,eventSet,datamodelForNextStep,eventsToAddToInnerQueue) ->
-
- _constructEventData = =>
- data = {}
-
- #namelist
- if action.namelist
- for name in action.namelist
- data[name] = @_datamodel[name]
-
- #params
- for param in action.params
- data[param.name] =
- if param.expr then @_eval param.expr,datamodelForNextStep,eventSet
- else if param.location then @_datamodel[param.location]
- else ""
-
- #content
- data['content'] = action.content
-
- return data
-
- switch action.type
- when "raise"
- if @opts.printTrace then console.debug "sending event",action.event,"with content",action.contentexpr
-
- eventsToAddToInnerQueue.add { name : action.event }
- when "assign"
- #FIXME: parameterize this so that it can assign to datamodel for next step
- @_datamodel[action.location] = @_eval action,datamodelForNextStep,eventSet
- when "script"
- @_eval action,datamodelForNextStep,eventSet,true
- when "log"
- log = @_eval action,datamodelForNextStep,eventSet
- console.log(log) #the one place where we use straight console.log
- when "send"
- #data = if action.contentexpr then @_eval(action,datamodelForNextStep,eventSet) else null
- #TODO: handle namelist,content,params
- #TODO: deep copy
- if @_send then @_send(
- {
- target : if action.targetexpr then @_eval action.targetexpr,datamodelForNextStep,eventSet else action.target
- name : if action.eventexpr then @_eval action.eventexpr,datamodelForNextStep,eventSet else action.event
- data : _constructEventData()
- origin : @opts.origin
- type : if action.typeexpr then @_eval action.typeexpr,datamodelForNextStep,eventSet else action.sendType
- },
- {
- delay : if action.delayexpr then @_eval action.delayexpr,datamodelForNextStep,eventSet else action.delay
- sendId : if action.idlocation then @_datamodel[action.idlocation] else action.id
- }
- )
- when "cancel"
- if @_cancel then @_cancel action.sendid
-
- _eval : (action,datamodelForNextStep,eventSet,allowWrite) ->
- #get the scripting interface
- n = @_getScriptingInterface(datamodelForNextStep,eventSet,allowWrite)
-
- action.evaluate.call(@opts.evaluationContext,n.getData,n.setData,n.In,n.events,@_datamodel)
-
- _getScriptingInterface: (datamodelForNextStep,eventSet,allowWrite=false) ->
- setData : if allowWrite then (name,value) -> datamodelForNextStep[name] = value else ->
- getData : (name) => @_datamodel[name]
- In : (s) => @isIn(s)
- events : eventSet.iter()
-
- _getStatesExited: (transitions) ->
- statesExited = new @opts.StateSet()
- basicStatesExited = new @opts.BasicStateSet()
-
- for transition in transitions.iter()
- lca = @opts.model.getLCA(transition)
- desc = @opts.model.getDescendants(lca)
-
- for state in @_configuration.iter()
- if state in desc
- basicStatesExited.add(state)
- statesExited.add(state)
- for anc in @opts.model.getAncestors(state,lca)
- statesExited.add(anc)
-
- sortedStatesExited = statesExited.iter().sort((s1,s2) => @opts.model.getDepth(s2) - @opts.model.getDepth(s1))
-
- return [basicStatesExited,sortedStatesExited]
-
- _getStatesEntered: (transitions) ->
- statesToRecursivelyAdd = flatten((state for state in transition.targets) for transition in transitions.iter())
- if @opts.printTrace then console.debug "statesToRecursivelyAdd :",statesToRecursivelyAdd
- statesToEnter = new @opts.StateSet()
- basicStatesToEnter = new @opts.BasicStateSet()
-
- while statesToRecursivelyAdd.length
- for state in statesToRecursivelyAdd
- @_recursiveAddStatesToEnter(state,statesToEnter,basicStatesToEnter)
-
- #add children of parallel states that are not already in statesToEnter to statesToRecursivelyAdd
- childrenOfParallelStatesInStatesToEnter = flatten(s.children for s in statesToEnter.iter() when s.kind is stateKinds.PARALLEL)
- statesToRecursivelyAdd = (s for s in childrenOfParallelStatesInStatesToEnter when not s.kind is stateKinds.HISTORY and not statesToEnter.contains s)
-
- sortedStatesEntered = statesToEnter.iter().sort((s1,s2) => @opts.model.getDepth(s1) - @opts.model.getDepth(s2))
-
- return [basicStatesToEnter,sortedStatesEntered]
-
- _recursiveAddStatesToEnter: (s,statesToEnter,basicStatesToEnter) ->
- if s.kind is stateKinds.HISTORY
- if s.id of @_historyValue
- for historyState in @_historyValue[s.id]
- @_recursiveAddStatesToEnter(historyState,statesToEnter,basicStatesToEnter)
- else
- statesToEnter.add(s)
- basicStatesToEnter.add(s)
- else
- statesToEnter.add(s)
-
- if s.kind is stateKinds.PARALLEL
- for child in s.children
- if not (child.kind is stateKinds.HISTORY) #don't enter history by default
- @_recursiveAddStatesToEnter(child,statesToEnter,basicStatesToEnter)
-
- else if s.kind is stateKinds.COMPOSITE
-
- #FIXME: problem: this doesn't check cond of initial state transitions
- #also doesn't check priority of transitions (problem in the SCXML spec?)
- #TODO: come up with test case that shows other is broken
- #what we need to do here: select transitions...
- #for now, make simplifying assumption. later on check cond, then throw into the parameterized choose by priority
- @_recursiveAddStatesToEnter(s.initial,statesToEnter,basicStatesToEnter)
-
- else if s.kind is stateKinds.INITIAL or s.kind is stateKinds.BASIC or s.kind is stateKinds.FINAL
- basicStatesToEnter.add(s)
-
-
- _selectTransitions: (eventSet,datamodelForNextStep) ->
-
- if @opts.onlySelectFromBasicStates
- states = @_configuration.iter()
- else
- statesAndParents = new @opts.StateSet
-
- #get full configuration, unordered
- #this means we may select transitions from parents before children
- for basicState in @_configuration.iter()
- statesAndParents.add(basicState)
-
- for ancestor in @opts.model.getAncestors(basicState)
- statesAndParents.add(ancestor)
-
- states = statesAndParents.iter()
-
- n = @_getScriptingInterface(datamodelForNextStep,eventSet)
- e = (t) => t.evaluateCondition.call(@opts.evaluationContext,n.getData,n.setData,n.In,n.events,@_datamodel)
-
- eventNames = (event.name for event in eventSet.iter())
-
- usePrefixMatchingAlgorithm = (name for name in eventNames when "." in name).length
-
- transitionSelector = if usePrefixMatchingAlgorithm then scxmlPrefixTransitionSelector else @opts.transitionSelector
-
- #debugger
- enabledTransitions = new @opts.TransitionSet
- for state in states
- for t in transitionSelector state,eventNames,e
- enabledTransitions.add t
-
- if @opts.printTrace then console.debug("allTransitionsForEachState",allTransitionsForEachState)
- priorityEnabledTransitions = @_selectPriorityEnabledTransitions enabledTransitions
- if @opts.printTrace then console.debug("priorityEnabledTransitions",priorityEnabledTransitions)
- return priorityEnabledTransitions
-
- _selectPriorityEnabledTransitions: (enabledTransitions) ->
- priorityEnabledTransitions = new @opts.TransitionSet()
-
- [consistentTransitions, inconsistentTransitionsPairs] = @_getInconsistentTransitions enabledTransitions
- priorityEnabledTransitions.union consistentTransitions
-
- if @opts.printTrace then console.debug "enabledTransitions",enabledTransitions
- if @opts.printTrace then console.debug "consistentTransitions",consistentTransitions
- if @opts.printTrace then console.debug "inconsistentTransitionsPairs",inconsistentTransitionsPairs
- if @opts.printTrace then console.debug "priorityEnabledTransitions",priorityEnabledTransitions
-
- while not inconsistentTransitionsPairs.isEmpty()
-
- enabledTransitions = new @opts.TransitionSet(@opts.priorityComparisonFn t for t in inconsistentTransitionsPairs.iter())
-
- [consistentTransitions, inconsistentTransitionsPairs] = @_getInconsistentTransitions enabledTransitions
-
- priorityEnabledTransitions.union consistentTransitions
-
- if @opts.printTrace then console.debug "enabledTransitions",enabledTransitions
- if @opts.printTrace then console.debug "consistentTransitions",consistentTransitions
- if @opts.printTrace then console.debug "inconsistentTransitionsPairs",inconsistentTransitionsPairs
- if @opts.printTrace then console.debug "priorityEnabledTransitions",priorityEnabledTransitions
-
- return priorityEnabledTransitions
-
- _getInconsistentTransitions: (transitions) ->
-
- allInconsistentTransitions = new @opts.TransitionSet()
- inconsistentTransitionsPairs = new @opts.TransitionPairSet() #set of tuples
-
- #better to use iterators, because not sure how to encode "order doesn't matter" to list comprehension
- transitionList = transitions.iter()
- if @opts.printTrace then console.debug("transitions",transitionList)
-
- for i in [0...transitionList.length]
- for j in [i+1...transitionList.length]
- t1 = transitionList[i]
- t2 = transitionList[j]
-
- if @_conflicts(t1,t2)
- allInconsistentTransitions.add t1
- allInconsistentTransitions.add t2
- inconsistentTransitionsPairs.add [t1,t2]
-
- consistentTransitions = transitions.difference allInconsistentTransitions
-
- return [consistentTransitions,inconsistentTransitionsPairs]
-
-
- #this would be parameterizable
- # -> Transition Consistency: Small-step consistency: Source/Destination Orthogonal
- # -> Interrupt Transitions and Preemption: Non-preemptive
- _conflicts: (t1,t2) -> not @_isArenaOrthogonal t1,t2
-
- _isArenaOrthogonal: (t1,t2) ->
- #if {t1,t2}.targets is blank, then t1 and t2 are targetless transitions, a.k.a. "static reactions" in statemate/rhapsody vocabulary
- #we treat their source state as the arena
- t1LCA = if t1.targets then @opts.model.getLCA(t1) else t1.source
- t2LCA = if t2.targets then @opts.model.getLCA(t2) else t2.source
- isOrthogonal = @opts.model.isOrthogonalTo t1LCA,t2LCA
- if @opts.printTrace
- console.debug "transition LCAs",t1LCA.id,t2LCA.id
- console.debug "transition LCAs are orthogonal?",isOrthogonal
- return isOrthogonal
-
-class SimpleInterpreter extends SCXMLInterpreter
-
- _isStepping : false
-
- constructor: (model,opts) ->
-
- #set up send and cancel
- #these may be passed in as options if, e.g., we 're using an external communication layer
- #these are the defaults if an external communication layer is not being used.
- @_send = opts.send or (event,options) ->
- if @opts.setTimeout
- if @opts.printTrace then console.debug "sending event",event.name,"with content",event.data,"after delay",options.delay
-
- callback = => @gen event
-
- timeoutId = @opts.setTimeout callback,options.delay
-
- if options.sendid
- @_timeoutMap[options.sendid] = timeoutId
- else
- throw new Error("setTimeout function not set")
-
- @_cancel = opts.cancel or (sendid) ->
- if @opts.clearTimeout
- if sendid of @_timeoutMap
- if @opts.printTrace then console.debug "cancelling ",sendid," with timeout id ",@_timeoutMap[sendid]
- @opts.clearTimeout @_timeoutMap[sendid]
- else
- throw new Error("clearTimeout function not set")
-
- super model,opts
-
- #External Event Communication: Asynchronous
- gen: (e) ->
- #console.debug("received event ", e)
-
- if not e?.name
- throw new Error "gen must be passed an event object."
-
- if @_isStepping
- throw new Error "gen called before previous call to gen could complete. if executed in single-threaded environment, this means it was called recursively, which is illegal, as it would break SCION step semantics."
-
- @_isStepping = true
-
- #pass it straight through
- @_performBigStep(e)
-
- @_isStepping = false
-
- return @getConfiguration()
-
-class BrowserInterpreter extends SimpleInterpreter
- constructor : (model,opts={}) ->
-
- #defaults
- setupDefaultOpts opts
-
- #need to wrap setTimeout and clearTimeout, otherwise complains
- opts.setTimeout ?= (callback,timeout) -> window.setTimeout callback,timeout
- opts.clearTimeout ?= (timeoutId) -> window.clearTimeout timeoutId
-
- super model,opts
-
-class NodeInterpreter extends SimpleInterpreter
- constructor : (model,opts={}) ->
-
-
- #defaults
- setupDefaultOpts opts
-
- opts.setTimeout = setTimeout
- opts.clearTimeout = clearTimeout
-
- super model,opts
-
-module.exports =
- SimpleInterpreter:SimpleInterpreter
- BrowserInterpreter:BrowserInterpreter
- NodeInterpreter:NodeInterpreter
- SCXMLInterpreter:SCXMLInterpreter
View
15 lib/scxml/default-transition-selector.coffee
@@ -1,15 +0,0 @@
-# Copyright 2011-2012 Jacob Beard, INFICON, and other SCION contributors
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-module.exports = (state,eventNames,evaluator) -> (t for t in state.transitions when (not t.event or t.event in eventNames) and (not t.cond or evaluator(t)))
View
102 lib/scxml/json2model.coffee
@@ -1,102 +0,0 @@
-# Copyright 2011-2012 Jacob Beard, INFICON, and other SCION contributors
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-#TODO:move this out
-getDelayInMs = (delayString) ->
- if not delayString
- return 0
- else
- if delayString[-2..] == "ms"
- return parseFloat(delayString[..-3])
- else if delayString[-1..] == "s"
- return parseFloat(delayString[..-2]) * 1000
- else
- #assume milliseconds
- return parseFloat(delayString)
-
-#we alias _event to _events[0] here for convenience
-makeEvaluationFn = (s,isExpression) -> new Function("getData","setData","In","_events","datamodel","var _event = _events[0]; with(datamodel){#{if isExpression then "return" else ""} #{s}}")
-
-stateToString = -> @id
-
-transitionToString = -> "#{@source.id} -> [#{target.id for target in @targets}]"
-
-module.exports = (json) ->
- #build up map of state ids to state objects
- idToStateMap = {}
- for state in json.states
- idToStateMap[state.id] = state
-
- for transition in json.transitions
- transition.toString = transitionToString #tag him with a toString method for more readable trace
- transition.evaluateCondition = makeEvaluationFn transition.cond,true
-
- for state in json.states
- state.toString = stateToString #tag him with a toString method for more readable trace
-
- state.transitions = (json.transitions[transitionNum] for transitionNum in state.transitions)
-
- #TODO: move this block out, make it cleaner
- actions = state.onentry.concat state.onexit
- for transition in state.transitions
- for action in transition.actions
- actions.push action
-
- if transition.lca
- transition.lca = idToStateMap[transition.lca]
-
- for action in actions
- switch action.type
- when "script"
- action.evaluate = makeEvaluationFn action.script
-
- when "assign"
- action.evaluate = makeEvaluationFn action.expr,true
-
- when "send"
-
- for attributeName in ['contentexpr', 'eventexpr', 'targetexpr', 'typeexpr', 'delayexpr']
- if action[attributeName]
- action[attributeName] = { evaluate : makeEvaluationFn action[attributeName],true }
-
- for param in action.params
- if param.expr
- param.expr = { evaluate : makeEvaluationFn param.expr,true }
- when "log"
- action.evaluate = makeEvaluationFn action.expr,true
-
- if action.type is "send" and action.delay
- action.delay = getDelayInMs action.delay
-
-
- state.initial = idToStateMap[state.initial]
- state.history = idToStateMap[state.history]
-
- state.children = (idToStateMap[stateId] for stateId in state.children)
-
- state.parent = idToStateMap[state.parent]
-
- if state.ancestors
- state.ancestors = (idToStateMap[stateId] for stateId in state.ancestors)
-
- if state.descendants
- state.descendants = (idToStateMap[stateId] for stateId in state.descendants)
-
- for t in state.transitions
- t.source = idToStateMap[t.source]
- t.targets = t.targets and (idToStateMap[stateId] for stateId in t.targets)
-
- json.root = idToStateMap[json.root]
-
- return json
View
86 lib/scxml/model.coffee
@@ -1,86 +0,0 @@
-# Copyright 2011-2012 Jacob Beard, INFICON, and other SCION contributors
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-stateKinds = require './state-kinds-enum'
-
-module.exports =
-
- getDepth: (s) ->
- if s.depth isnt undefined then return s.depth
- else
- count = 0
- state = s.parent
- while state
- count = count + 1
- state = state.parent
-
- return count
-
- getAncestors: (s,root) ->
- if s.ancestors
- index = s.ancestors.indexOf(root)
- if index is -1
- s.ancestors
- else
- s.ancestors.slice(0,index)
- else
- ancestors = []
-
- state = s.parent
- while state and not (state is root)
- ancestors.push(state)
- state = state.parent
-
- return ancestors
-
- getAncestorsOrSelf: (s,root) -> [s].concat @getAncestors(s,root)
-
- getDescendants: (s) ->
- if s.descendants then return s.descendants
- else
- descendants = []
- queue = s.children.slice()
-
- while queue.length
- state = queue.shift()
- descendants.push(state)
-
- for child in state.children
- queue.push(child)
-
- return descendants
-
- getDescendantsOrSelf: (s) -> [s].concat @getDescendants(s)
-
- isOrthogonalTo: (s1,s2) ->
- #Two control states are orthogonal if they are not ancestrally
- #related, and their smallest, mutual parent is a Concurrent-state.
- return not @isAncestrallyRelatedTo(s1,s2) and @getLCA(s1,s2).kind is stateKinds.PARALLEL
-
- isAncestrallyRelatedTo: (s1,s2) ->
- #Two control states are ancestrally related if one is child/grandchild of another.
- return s1 in @getAncestorsOrSelf(s2) or s2 in @getAncestorsOrSelf(s1)
-
- getLCA: (tOrS1,s2) ->
- if tOrS1.lca then return tOrS1.lca
- else
- #can take one or two arguments: either 1 transition, or two states
- if arguments.length is 1
- transition = tOrS1
- return @getLCA transition.source,transition.targets[0]
- else
- s1 = tOrS1
- commonAncestors = (a for a in @getAncestors(s1) when s2 in @getDescendants(a))
- return commonAncestors[0]
-
View
13 lib/scxml/scxml-dynamic-name-match-transition-selector.coffee
@@ -1,13 +0,0 @@
-eventNameReCache = {}
-
-#this will convert, e.g. "foo.bar.bat" to RegExp "^foo\.bar\.bat(\.[0-9a-zA-Z]+)*$"
-eventNameToRe = (name) -> new RegExp "^#{name.replace(/\./g,"\\.")}(\\.[0-9a-zA-Z]+)*$"
-
-retrieveEventRe = (name) -> eventNameReCache[name] ?= eventNameToRe name
-
-nameMatch = (t,eventNames) ->
- tEvents = t.events
- f = if "*" in tEvents then -> true else (name) -> (tEvent for tEvent in tEvents when retrieveEventRe(tEvent).test(name)).length
- (name for name in eventNames when f name).length
-
-module.exports = (state,eventNames,evaluator) -> (t for t in state.transitions when (not t.events or nameMatch t,eventNames) and (not t.cond or evaluator(t)))
View
70 lib/scxml/set/ArraySet.coffee
@@ -1,70 +0,0 @@
-# Copyright 2011-2012 Jacob Beard, INFICON, and other SCION contributors
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-class ArraySet
- constructor: (l=[]) ->
- @o = []
-
- for x in l
- @add x
-
- add: (x) ->
- if not @contains x
- @o.push x
-
- remove: (x) ->
- for i in [0...@o.length]
- if @o[i] is x
- @o.splice i,1
- return true
- return false
-
- union: (l) ->
- l = if l.iter then l.iter() else l
-
- for i in l
- @add(i)
-
- return @
-
- difference: (l) ->
- l = if l.iter then l.iter() else l
-
- for i in l
- @remove(i)
-
- return @
-
- contains: (x) -> x in @o
-
- iter: -> @o
-
- isEmpty : -> !@o.length
-
- equals : (s2) ->
- l2 = s2.iter()
-
- for v in @o
- if not s2.contains(v)
- return false
-
- for v in l2
- if not @contains(v)
- return false
-
- return true
-
- toString : -> "Set(" + @o.toString() + ")"
-
-module.exports = ArraySet
View
118 lib/scxml/set/BitVector.coffee
@@ -1,118 +0,0 @@
-# Copyright 2011-2012 Jacob Beard, INFICON, and other SCION contributors
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-#this code based on a snippet found here: http://acc6.its.brooklyn.cuny.edu/~gurwitz/core5/nav2tool.html
-decimalToBinary = (x) ->
- answer=[]
- x2 = x
- log2 = 0
- while(x2 >= 2)
- x2 = x2/2
- log2 = log2+1
-
- for l2 in [log2..0]
- power=Math.pow(2,l2)
- if ( x >= power)
- answer.push 1
- x = x-power
- else answer.push 0
-
- answer.join("")
-
-defaultKeyProp="basicDocumentOrder"
-defaultKeyValueMap=[]
-
-class BitVectorSet
- constructor: (l=[],@keyProp=BitVectorSet.defaultKeyProp,@keyValueMap=BitVectorSet.defaultKeyValueMap) ->
- #TODO: deal with overflow, aggregate multiple ints
- #based on the length of the keyValueMap
- @o = 0
-
- #console.log "creating new BitVectorSet from list",l
-
- for x in l
- @add x
-
- hash:(x) ->
- Math.pow 2,x[@keyProp]
-
- add: (x) ->
- #console.log "x.id : #{x.id}"
- #console.log "keyValueMap : #{v.id for v in @keyValueMap}"
- #console.log "keyprop : #{@keyProp}"
- #console.log "x[keyprop] : #{x[@keyProp]}"
- #console.log "adding #{decimalToBinary @hash x} (#{@hash x})"
- #console.log "now: #{v.id for v in @iter()}"
-
- @o |= @hash x
-
- remove: (x) ->
- @o &= ~(@hash x)
-
- union: (l) ->
- if l instanceof BitVectorSet
- t = l.o
- else
- t = 0
-
- l = if l.iter then l.iter() else l
-
- for i in l
- t |= @hash i
-
-
- @o |= t
-
- return @
-
- difference: (l) ->
- if l instanceof BitVectorSet
- t = l.o
- else
- t = 0
-
- l = if l.iter then l.iter() else l
-
- for i in l
- t |= @hash i
-
- @o &= ~t
-
- return @
-
- contains: (x) -> @o & @hash x
-
- iter: ->
- toReturn = []
- t1 = @o
- i = 0
- while t1
- if t1 & 1
- if not @keyValueMap[i]
- throw new Error("undefined value in keyvaluemap")
- toReturn.push @keyValueMap[i]
-
- t1 = t1 >>> 1
- i++
-
- return toReturn
-
- isEmpty : -> !!!@o
-
- equals : (s2) -> s2.o == @o
-
- toString : -> "Set(" + decimalToBinary(@o) + ")"
-
-
-module.exports = BitVectorSet
View
67 lib/scxml/set/BooleanArray.coffee
@@ -1,67 +0,0 @@
-# Copyright 2011-2012 Jacob Beard, INFICON, and other SCION contributors
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-class BooleanArraySet
- constructor: (l=[],@keyProp="basicDocumentOrder",hashSize=0) ->
- #two ways to do this: static preallocation using "new Array(len)" (or array comprehension)
- #or lazy allocation by index into it whenever we need it
- #do we give all implementations their own interface, or make them have the same interface?
- @o = new Array(hashSize)
- @union(l)
-
- add: (x) ->
- @o[x[@keyProp]] = x
-
- remove: (x) ->
- delete @o[x[@keyProp]]
-
- union: (l) ->
- l = if l.iter then l.iter() else l
- for x in l
- @add(x)
-
- return @
-
- difference: (l) ->
- l = if l.iter then l.iter() else l
- for x in l
- @remove(x)
-
- return @
-
- contains: (x) -> @o[x[@keyProp]] is x
-
- iter: -> v for v in @o when v
-
- isEmpty : ->
- for v in @o when v
- return false
- return true
-
- equals : (s2) ->
- l1 = @iter()
- l2 = s2.iter()
-
- for v in l1
- if not s2.contains(v)
- return false
-
- for v in l2
- if not @contains(v)
- return false
-
- return true
-
-
-module.exports = BooleanArraySet
View
73 lib/scxml/set/ObjectSet.coffee
@@ -1,73 +0,0 @@
-# Copyright 2011-2012 Jacob Beard, INFICON, and other SCION contributors
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-defaultKeyProp = "id"
-
-class ObjectSet
- constructor: (l=[],@keyProp=defaultKeyProp) ->
- @o = {}
- @union(l)
-
- add: (x) ->
- @o[x[@keyProp]] = x
-
- remove: (x) ->
- delete @o[x[@keyProp]]
-
- union: (l) ->
- if l instanceof ObjectSet
- for k,v of l.o
- @add(v)
- else
- l = if l.iter then l.iter() else l
- for x in l
- @add(x)
-
- return @
-
- difference: (l) ->
- if l instanceof ObjectSet
- for k,v of l.o
- @remove(v)
- else
- l = if l.iter then l.iter() else l
- for x in l
- @remove(x)
-
- return @
-
- contains: (x) -> @o[x[@keyProp]] is x
-
- iter: -> v for own k,v of @o
-
- isEmpty : ->
- for own k,v of @o
- return false
- return true
-
- equals : (s2) ->
- l1 = @iter()
- l2 = s2.iter()
-
- for v in l1
- if not s2.contains(v)
- return false
-
- for v in l2
- if not @contains(v)
- return false
-
- return true
-
-module.exports = ObjectSet
View
12 lib/scxml/setup-default-opts.coffee
@@ -1,12 +0,0 @@
-selector = require './scxml-dynamic-name-match-transition-selector'
-ArraySet = require './set/ArraySet'
-m = require './model'
-
-module.exports = (opts={}) ->
- opts.TransitionSet ?= ArraySet
- opts.StateSet ?= ArraySet
- opts.BasicStateSet ?= ArraySet
- opts.transitionSelector ?= selector
- opts.model ?= m
-
- return opts
View
7 lib/scxml/state-kinds-enum.coffee
@@ -1,7 +0,0 @@
-module.exports =
- BASIC : 0
- COMPOSITE : 1
- PARALLEL : 2
- HISTORY : 3
- INITIAL : 4
- FINAL : 5
View
481 lib/util/annotate-scxml-json.coffee
@@ -1,481 +0,0 @@
-###
- Copyright 2011-2012 Jacob Beard, INFICON, and other SCION contributors
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
-###
-
-###
-This file transforms an SCXML document converted to JsonML so that it is easier for a JavaScript-based SCXML interpreter to parse and interpret.
-###
-
-###
-Example SCXML document basic1.scxml stripped of whitespace and converted to JsonML.
-
-[
- "scxml",
- {
- "id": "root",
- "profile": "ecmascript",
- "version": "1.0"
- },
- [
- "initial",
- {
- "id": "intitial1"
- },
- [
- "transition",
- {
- "target": "a"
- }
- ]
- ],
- [
- "state",
- {
- "id": "a"
- },
- [
- "transition",
- {
- "event": "t",
- "target": "b"
- }
- ]
- ],
- [
- "state",
- {
- "id": "b"
- }
- ]
-]
-###
-
-stateKinds = require "../scxml/state-kinds-enum"
-
-STATES_THAT_CAN_BE_CHILDREN = ["state", "parallel", "history", "final", "initial"]
-STATE_TAGS = STATES_THAT_CAN_BE_CHILDREN.concat "scxml"
-
-#variable declarations outside of scope of transform
-states = basicStates = uniqueEvents = transitions = idToStateMap = onFoundStateIdCallbacks = datamodel = undefined
-
-exports.transformAndSerialize = transformAndSerialize = (root,genDepth,genAncestors,genDescendants,genLCA) ->
- JSON.stringify transform(root,genDepth,genAncestors,genDescendants,genLCA)
-
-#we expect depth to actually only be at most 100, so we can use regular recursion
-exports.transform = transform = (root,genDepth,genAncestors,genDescendants,genLCA) ->
- #initialize variables we want to track
- states = []
- basicStates = []
- uniqueEvents = {}
- transitions = []
- idToStateMap = {}
- onFoundStateIdCallbacks = []
- datamodel = {}
-
- rootState = transformStateNode root,[],genDepth,genAncestors,genDescendants,genLCA
-
- [tagName,attributes,children] = deconstructNode root
-
- #reverse ancestors (needs to be in reverse document order)
- if genAncestors or genLCA then state.ancestors.reverse() for state in states
-
- #reverse descendants (needs to be in reverse document order)
- if genDescendants or genLCA then state.descendants.reverse() for state in states
-
- #generate LCAs on transitions
- if genLCA
- for transition in transitions when transition.targets
- source = idToStateMap[transition.source]
- targets = (idToStateMap[target] for target in transition.targets)
- if not source
- #console.debug transition
- new Error "source missing"
- else if not targets.length
- #console.debug transition
- new Error "target missing"
-
- transition.lca = getLCA(source,targets[0])
-
- states : states
- transitions : transitions
- root : rootState.id
- events : genEventsEnum uniqueEvents
- scripts : genRootScripts children
- profile : attributes.profile
- version : attributes.version
- datamodel : datamodel
-
-genRootScripts = (rootChildren) ->
- toReturn = []
- for child in rootChildren
- [tagName,attributes,grandChildren] = deconstructNode child
- if tagName is "script"
- for scriptNode in grandChildren when typeof scriptNode is "string"
- toReturn.push scriptNode
-
- return toReturn
-
-
-genEventsEnum = (uniqueEvents) ->
- eventDocumentOrder = 0
- toReturn = {}
-
- for event of uniqueEvents
- toReturn[event] =
- name : event
- documentOrder : eventDocumentOrder++
-
- return toReturn
-
-deconstructNode = (node,filterText) ->
- tagName = node[0]
-
- n1 = node[1]
- if n1 and typeof n1 is "object" and not (isArray n1 or typeof n1 is "string")
- #process his attributes? do we care?
- attributes = n1
- children = node[2..]
- else
- attributes = {}
- children = node[1..]
-
- if filterText
- children = (child for child in children when typeof child isnt "string")
-
- return [tagName,attributes,children]
-
-stripStarFromEventNameRe = /^((([a-zA-Z0-9]+)\.)*([a-zA-Z0-9]+))(\.\*)?$/
-
-transformTransitionNode = (transitionNode,parentState,genDepth,genAncestors,genDescendants,genLCA) ->
- [tagName,attributes,children] = deconstructNode transitionNode,true
-
-
- #wildcard "*" event will show up on transition.events, but will not show up in uniqueEvents
- #default transitions (those without events) will have events set to undefined (rather than empty array)
- if attributes.event
- events =
- if attributes.event is "*" then [attributes.event]
- else for event in attributes.event.trim().split(/\s+/)
- #strip off trailing ".*"
- #TODO: split up space-delimited events
- m = event.match stripStarFromEventNameRe
- if m
- normalizedEvent = m[1]
- if not (m and normalizedEvent) then throw new Error "Unable to parse event: #{event}" else event
-
- for event in events
- if event isnt "*" then uniqueEvents[event] = true
-
- transition =
- documentOrder : transitions.length
- id : transitions.length
- source : parentState.id
- cond : attributes.cond
- events : events
- actions : (transformActionNode child for child in children)
- targets : attributes?.target?.trim().split(/\s+/) #this will either be a list, or undefined
-
- transitions.push transition
-
- #set up LCA later
-
- return transition
-
-processParam = (param) ->
- [tagName,attributes,children] = deconstructNode param
- { name : attributes.name, expr : attributes.expr, location: attributes.location }
-
-
-transformActionNode = (node) ->
- [tagName,attributes,children] = deconstructNode node
-
- switch tagName
- when "if"
- "type" : "if"
- "cond" : attributes.cond
- "actions" : (transformActionNode child for child in children)
-
- when "elseif"
- "type" : "elseif"
- "cond" : attributes.cond
- "actions" : (transformActionNode child for child in children)
-
- when "else"
- "type" : "else"
- "actions" : (transformActionNode child for child in children)
-
- when "log"
- "type" : "log"
- "expr" : attributes.expr
- "label" : attributes.label
-
- when "script"
- "type" : "script",
- "script" : children.join "\n"
-
- when "send"
- "type" : "send",
- "sendType" : attributes.type
- "delay" : attributes.delay
- "id" : attributes.id
- "event" : attributes.event
- "target" : attributes.target
- "idlocation" : attributes.idlocation
- #data
- "namelist" : attributes?.namelist?.trim().split(new RegExp " +")
- "params" : (processParam child for child in children when child[0] is "param")
- "content" : (deconstructNode(child)[2][0] for child in children when child[0] is "content")[0]
- #exprs
- "eventexpr" : attributes.eventexpr
- "targetexpr" : attributes.targetexpr
- "typeexpr" : attributes.typeexpr
- "delayexpr" : attributes.delayexpr
-
- when "cancel"
- "type" : "cancel",
- "sendid" : attributes.sendid
-
- when "assign"
- "type" : "assign",
- "location" : attributes.location
- "expr" : attributes.expr
-
- when "raise"
- "type" : "raise"
- "event" : attributes.event
-
- when "invoke"
- #TODO
- throw new Error("Element #{tagName} not yet supported")
- when "finalize"
- #TODO
- throw new Error("Element #{tagName} not yet supported")
-
-transformDatamodel = (node,ancestors,genDepth,genAncestors,genDescendants,genLCA) ->
- [tagName,attributes,children] = deconstructNode node,true
-
- for child in children when child[0] is "data"
- [childTagName,childAttributes,childChildren] = deconstructNode child,true
- if childAttributes.id
- datamodel[childAttributes.id] = childAttributes.expr or null
-
-transformStateNode = (node,ancestors,genDepth,genAncestors,genDescendants,genLCA) ->
- [tagName,attributes,children] = deconstructNode node,true
-
- #generate id if necessary
- id = attributes?.id or genId tagName
-
- #console.warn "Processing #{tagName} node with id '#{id}'"
- #console.warn "ancestors",ancestors
-
- kind = switch tagName
- when "state"
- if (child for child in children when child[0] in STATE_TAGS).length then stateKinds.COMPOSITE
- else stateKinds.BASIC
- when "scxml" then stateKinds.COMPOSITE
- when "initial" then stateKinds.INITIAL
- when "parallel" then stateKinds.PARALLEL
- when "final" then stateKinds.FINAL
- when "history" then stateKinds.HISTORY
-
- #stub out the state
- state =
- id : id
- kind : kind
- descendants : [] #descendants gets populated later
-
- idToStateMap[id] = state
-
- if ancestors.length
- state.parent = ancestors[ancestors.length - 1]
-
- if kind is stateKinds.HISTORY
- state.isDeep = if attributes.type is "deep" then true else false
-
- state.documentOrder = states.length
- states.push state
-
- if kind is stateKinds.BASIC or kind is stateKinds.INITIAL or kind is stateKinds.HISTORY
- state.basicDocumentOrder = basicStates.length
- basicStates.push state
-
- #special stuff
- if genDepth
- state.depth = ancestors.length
-
- if genAncestors or genLCA
- state.ancestors = ancestors.slice()
-
- if genDescendants or genLCA
- #walk back up ancestors and add this state to lists of descendants
- (idToStateMap[anc].descendants.push state.id for anc in ancestors)
-
- #need to do some work on his children
- onExitChildren = []
- onEntryChildren = []
- transitionChildren = []
- stateChildren = []
-
- nextAncestors = ancestors.concat state.id #we are copying ancestors twice. may be more efficient way?
-
- processedInitial = false
- firstStateChild = null
-
- processInitialState = (initialState) ->
- child = transformStateNode initialState,nextAncestors,genDepth,genAncestors,genDescendants,genLCA
- state.initial = child.id
- stateChildren.push child
- processedInitial = true
-
-
- for child in children when isArray child #they should all be tuples. too bad this is expensive.
- [childTagName,childAttributes,childChildren] = deconstructNode child,true
- switch childTagName
- #apply recursively
- when "transition"
- transitionChildren.push transformTransitionNode child,state
- when "onentry"
- (onEntryChildren.push transformActionNode actionNode for actionNode in childChildren)
- when "onexit"
- (onExitChildren.push transformActionNode actionNode for actionNode in childChildren)
- when "initial"
- if not processedInitial
- processInitialState child
- else
- throw new Error("Encountered duplicate initial states in state #{state.id}")
- when "history"
- child = transformStateNode child,nextAncestors,genDepth,genAncestors,genDescendants,genLCA
- state.history = child.id
- stateChildren.push child
- when "datamodel"
- transformDatamodel child,nextAncestors,genDepth,genAncestors,genDescendants,genLCA
- else
- if childTagName in STATES_THAT_CAN_BE_CHILDREN #another filter
- transformedStateNode = transformStateNode child,nextAncestors,genDepth,genAncestors,genDescendants,genLCA
- if firstStateChild is null
- #this is used to set default initial state, if initial state is not specified
- firstStateChild = transformedStateNode
-
- stateChildren.push transformedStateNode
-
- #console.error state.id,firstStateChild,processedInitial
-
- #handle initial
- #FIXME: handle initial state for parallel states
- if not processedInitial and tagName isnt "parallel"
- hasInitialAttribute = attributes?.initial
-
- generateFakeInitialState = (targetId) ->
- #console.log "generating fake initial node"
- #create a fake initial state and process him
- fakeInitialState = [
- "initial",
- [
- "transition",
- {
- target : targetId
- }
- ]
- ]
-
- processInitialState fakeInitialState
-
- if hasInitialAttribute
- generateFakeInitialState attributes.initial
- else
- if firstStateChild #if this exists, he is composite
- generateFakeInitialState firstStateChild.id
-
-
- #set up these properties
- state.onexit = onExitChildren
- state.onentry = onEntryChildren
- state.transitions = (transition.documentOrder for transition in transitionChildren)
- state.children = (child.id for child in stateChildren)
-
- #lazy-initialize transitions that need it
- #if onFoundStateIdCallbacks[id] then (cb() for cb in onFoundStateIdCallbacks[id])
-
- #return the state object. depending on specified optinos, some of these properties may be undefined, which is fine
- return state
-
-
-#http://stackoverflow.com/questions/4775722/javascript-check-if-object-is-array
-isArray = (o) -> Object.prototype.toString.call(o) is '[object Array]'
-
-#TODO: make this safer
-idRoot = "$generated"
-idCounter = {}
-genId = (tagName) ->
- idCounter[tagName] ?= 0
- "#{idRoot}-#{tagName}-#{idCounter[tagName]++}"
-
-#inspect = require('util').inspect
-
-getLCA = (s1,s2) ->
- #FIXME: ancestors will be complete at this point, but will descendants?
- #yah, i think descandants may not be complete, but it will be complete enough
- #lots of string comparison... expensive
- #console.debug "getLCA"
- #console.debug "s1",s1
- #console.debug "s2",s2
- ###
- process.stdout.setEncoding 'utf-8'
- process.stdout.write "\ngetLCA\n"
- process.stdout.write "\ns1\n"
- process.stdout.write inspect(s1)
- process.stdout.write "\ns2\n"
- process.stdout.write inspect(s2)
- ###
-
- commonAncestors = []
- for a in s1.ancestors
- anc = idToStateMap[a]
- #console.debug "anc",anc
- if s2.id in anc.descendants
- commonAncestors.push a
-
- if not commonAncestors.length
- throw new Error("Could not find LCA for states.")
- return commonAncestors[0]
-
-if require.main is module
- inFile = process.argv[2]
- outFile = process.argv[3]
-
- go = (jsonStr) ->
- scxmlJson = JSON.parse jsonStr
-
- s = transformAndSerialize scxmlJson,true,true,true,true
-
- if outFile is "-"
- process.stdout.write s
- else
- fs = require 'fs'
- fs.writeFileSync outFile,s,'utf-8'
-
- if not inFile or inFile is "-"
- process.stdin.resume()
- process.stdin.setEncoding "utf-8"
-
- #read from stdin
- json = ""
- process.stdin.on "data",(data) -> json += data
- process.stdin.on "end", -> go json
-
- else
- fs = require 'fs'
- str = fs.readFileSync inFile,'utf-8'
- go str
View
542 lib/util/annotate-scxml-json.js
@@ -0,0 +1,542 @@
+
+/*
+ Copyright 2011-2012 Jacob Beard, INFICON, and other SCION contributors
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+/*
+This file transforms an SCXML document converted to JsonML so that it is easier for a JavaScript-based SCXML interpreter to parse and interpret.
+*/
+
+/*
+Example SCXML document basic1.scxml stripped of whitespace and converted to JsonML.
+
+[
+ "scxml",
+ {
+ "id": "root",
+ "profile": "ecmascript",
+ "version": "1.0"
+ },
+ [
+ "initial",
+ {
+ "id": "intitial1"
+ },
+ [
+ "transition",
+ {
+ "target": "a"
+ }
+ ]
+ ],
+ [
+ "state",
+ {
+ "id": "a"
+ },
+ [
+ "transition",
+ {
+ "event": "t",
+ "target": "b"
+ }
+ ]
+ ],
+ [
+ "state",
+ {
+ "id": "b"
+ }
+ ]
+]
+*/
+
+var _ = require('underscore');
+
+var stateKinds = require("../scxml/state-kinds-enum");
+
+var STATES_THAT_CAN_BE_CHILDREN = ["state", "parallel", "history", "final", "initial"],
+ STATE_TAGS = STATES_THAT_CAN_BE_CHILDREN.concat("scxml");
+
+var states, basicStates, uniqueEvents, transitions, idToStateMap, onFoundStateIdCallbacks, datamodel;
+
+var transformAndSerialize = exports.transformAndSerialize = transformAndSerialize = function(root, genDepth, genAncestors, genDescendants, genLCA) {
+ return JSON.stringify(transform(root, genDepth, genAncestors, genDescendants, genLCA));
+};
+
+var transform = exports.transform = function(root, genDepth, genAncestors, genDescendants, genLCA) {
+ states = [];
+ basicStates = [];
+ uniqueEvents = {};
+ transitions = [];
+ idToStateMap = {};
+ onFoundStateIdCallbacks = [];
+ datamodel = {};
+
+ var rootState = transformStateNode(root, [], genDepth, genAncestors, genDescendants, genLCA);
+
+ var tuple = deconstructNode(root),
+ tagName = tuple[0],
+ attributes = tuple[1],
+ children = tuple[2];
+
+ if (genAncestors || genLCA) {
+ _.forEach(states,function(state){
+ state.ancestors.reverse();
+ });
+ }
+ if (genDescendants || genLCA) {
+ _.forEach(states,function(state){
+ state.descendants.reverse();
+ });
+ }
+ if (genLCA) {
+ _.chain(transitions).filter(function(t){return t.targets;}).forEach(function(transition){
+ var source = idToStateMap[transition.source];
+ var targets = _(transition.targets).map(function(target){return idToStateMap[target];});
+
+ if (!source) {
+ throw new Error("source missing");
+ } else if (!targets.length) {
+ throw new Error("target missing");
+ }
+
+ transition.lca = getLCA(source, targets[0]);
+ });
+ }
+
+ return {
+ states: states,
+ transitions: transitions,
+ root: rootState.id,
+ events: genEventsEnum(uniqueEvents),
+ scripts: genRootScripts(children),
+ profile: attributes.profile,
+ version: attributes.version,
+ datamodel: datamodel
+ };
+};
+
+function genRootScripts(rootChildren) {
+ var toReturn = [];
+ _(rootChildren).forEach(function(child){
+
+ var tuple = deconstructNode(child),
+ tagName = tuple[0],
+ attributes = tuple[1],
+ grandChildren = tuple[2];
+
+ if (tagName === "script") {
+ _.chain(grandChildren).
+ filter(function(scriptNode){return typeof scriptNode === "string";}).
+ forEach(function(scriptNode){
+ toReturn.push(scriptNode);
+ });
+ }
+ });
+ return toReturn;
+}
+
+function genEventsEnum(uniqueEvents) {
+ var event, eventDocumentOrder, toReturn;
+ eventDocumentOrder = 0;
+ toReturn = {};
+ for (event in uniqueEvents) {
+ toReturn[event] = {
+ name: event,
+ documentOrder: eventDocumentOrder++
+ };
+ }
+ return toReturn;
+}
+
+function deconstructNode(node, filterText) {
+ var attributes, child, children, n1, tagName;
+ tagName = node[0];
+ n1 = node[1];
+ if (n1 && typeof n1 === "object" && !(_.isArray(n1) || typeof n1 === "string")) {
+ attributes = n1;
+ children = node.slice(2);
+ } else {
+ attributes = {};
+ children = node.slice(1);
+ }
+ if (filterText) {
+ children = _(children).filter(function(child){return typeof child !== "string";});
+ }
+ return [tagName, attributes, children];
+}
+
+var stripStarFromEventNameRe = /^((([a-zA-Z0-9]+)\.)*([a-zA-Z0-9]+))(\.\*)?$/;
+
+function transformTransitionNode (transitionNode, parentState, genDepth, genAncestors, genDescendants, genLCA) {
+
+ var tuple = deconstructNode(transitionNode, true),
+ tagName = tuple[0],
+ attributes = tuple[1],
+ children = tuple[2];
+
+ //wildcard "*" event will show up on transition.events, but will not show up in uniqueEvents
+ //default transitions (those without events) will have events set to undefined (rather than empty array)
+ if (attributes.event) {
+ var events;
+
+ if (attributes.event === "*") {
+ events = [attributes.event];
+ } else {
+ events = _(attributes.event.trim().split(/\s+/)).map(function(event){
+ var m = event.match(stripStarFromEventNameRe);
+ if (m) {
+ var normalizedEvent = m[1];
+ if (!(m && normalizedEvent)) {
+ throw new Error("Unable to parse event: " + event);
+ }else{
+ return normalizedEvent;
+ }
+ }
+ });
+ }
+
+ _.chain(events).
+ filter(function(event){return event !== "*";}).
+ forEach(function(event){uniqueEvents[event] = true;});
+
+ }
+
+ var transition = {
+ documentOrder: transitions.length,
+ id: transitions.length,
+ source: parentState.id,
+ cond: attributes.cond,
+ events: events,
+ actions: _(children).map(function(child){return transformActionNode(child);}),
+ targets: attributes && attributes.target && attributes.target.trim().split(/\s+/)
+ };
+ transitions.push(transition);
+
+ //set up LCA later
+
+ return transition;
+}
+
+function processParam(param) {
+ var tuple = deconstructNode(param),
+ tagName = tuple[0],
+ attributes = tuple[1],
+ children = tuple[2];
+ return {
+ name: attributes.name,
+ expr: attributes.expr,
+ location: attributes.location
+ };
+}
+
+function transformActionNode(node) {
+ var tuple = deconstructNode(node), tagName = tuple[0], attributes = tuple[1], children = tuple[2];
+
+ switch (tagName) {
+ case "if":
+ return {
+ "type": "if",
+ "cond": attributes.cond,
+ "actions": _(children).map(function(child){return transformActionNode(child);})
+ };
+ case "elseif":
+ return {
+ "type": "elseif",
+ "cond": attributes.cond,
+ "actions": _(children).map(function(child){return transformActionNode(child);})
+ };
+ case "else":
+ return {
+ "type": "else",
+ "actions": _(children).map(function(child){return transformActionNode(child);})
+ };
+ case "log":
+ return {
+ "type": "log",
+ "expr": attributes.expr,
+ "label": attributes.label
+ };
+ case "script":
+ return {
+ "type": "script",
+ "script": children.join("\n")
+ };
+ case "send":
+ return {
+ "type": "send",
+ "sendType": attributes.type,
+ "delay": attributes.delay,
+ "id": attributes.id,
+ "event": attributes.event,
+ "target": attributes.target,
+ "idlocation": attributes.idlocation,
+ //data
+ "namelist": attributes && attributes.namelist && attributes.namelist.trim().split(/ +/),
+ "params": _.chain(children).filter(function(child){return child[0] === 'param';}).map(function(child){return processParam(child);}).value(),
+ "content": _.chain(children).filter(function(child){return child[0] === 'content';}).map(function(child){return deconstructNode(child)[2][0];}).first().value(),
+ //exprs
+ "eventexpr": attributes.eventexpr,
+ "targetexpr": attributes.targetexpr,
+ "typeexpr": attributes.typeexpr,
+ "delayexpr": attributes.delayexpr
+ };
+ case "cancel":
+ return {
+ "type": "cancel",
+ "sendid": attributes.sendid
+ };
+ case "assign":
+ return {
+ "type": "assign",
+ "location": attributes.location,
+ "expr": attributes.expr
+ };
+ case "raise":
+ return {
+ "type": "raise",
+ "event": attributes.event
+ };
+ case "invoke":
+ throw new Error("Element " + tagName + " not yet supported");
+ return null;
+ case "finalize":
+ throw new Error("Element " + tagName + " not yet supported");
+ return null;
+ default :
+ return null;
+ }
+}
+
+function transformDatamodel(node, ancestors, genDepth, genAncestors, genDescendants, genLCA) {
+ var tuple = deconstructNode(node, true), tagName = tuple[0], attributes = tuple[1], children = tuple[2];
+
+ _.chain(children).filter(function(child){return child[0] === 'data';}).forEach(function(child){
+ var tuple = deconstructNode(child, true), childTagName = tuple[0], childAttributes = tuple[1], childChildren = tuple[2];
+ if (childAttributes.id) {
+ datamodel[childAttributes.id] = childAttributes.expr || null;
+ }
+ });
+}
+
+function transformStateNode(node, ancestors, genDepth, genAncestors, genDescendants, genLCA) {
+ var tuple = deconstructNode(node, true), tagName = tuple[0], attributes = tuple[1], children = tuple[2];
+ var id = (attributes && attributes.id) || genId(tagName);
+ var kind;
+
+ switch (tagName) {
+ case "state":
+ if( _.chain(children).filter(function(child){return _.contains(STATE_TAGS,child[0]);}).isEmpty().value()){
+ kind = stateKinds.BASIC;
+ } else {
+ kind = stateKinds.COMPOSITE;
+ }
+ break;
+ case "scxml":
+ kind = stateKinds.COMPOSITE;
+ break;
+ case "initial":
+ kind = stateKinds.INITIAL;
+ break;
+ case "parallel":
+ kind = stateKinds.PARALLEL;
+ break;
+ case "final":
+ kind = stateKinds.FINAL;
+ break;
+ case "history":
+ kind = stateKinds.HISTORY;
+ break;
+ default : break;
+ }
+ var state = {
+ id: id,
+ kind: kind,
+ descendants: []
+ };
+ idToStateMap[id] = state;
+ if (ancestors.length) state.parent = ancestors[ancestors.length - 1];
+ if (kind === stateKinds.HISTORY) {
+ state.isDeep = attributes.type === "deep" ? true : false;
+ }
+ state.documentOrder = states.length;
+ states.push(state);
+ if (kind === stateKinds.BASIC || kind === stateKinds.INITIAL || kind === stateKinds.HISTORY) {
+ state.basicDocumentOrder = basicStates.length;
+ basicStates.push(state);
+ }
+ if (genDepth) state.depth = ancestors.length;
+ if (genAncestors || genLCA) state.ancestors = ancestors.slice();
+ if (genDescendants || genLCA) {
+ //walk back up ancestors and add this state to lists of descendants
+ _(ancestors).forEach(function(anc){
+ idToStateMap[anc].descendants.push(state.id);
+ });
+ }
+
+ //need to do some work on his children
+ var onExitChildren = [];
+ var onEntryChildren = [];
+ var transitionChildren = [];
+ var stateChildren = [];
+
+ var nextAncestors = ancestors.concat(state.id);
+
+ var processedInitial = false;
+ var firstStateChild = null;
+
+ var processInitialState = function(initialState) {
+ var child = transformStateNode(initialState, nextAncestors, genDepth, genAncestors, genDescendants, genLCA);
+ state.initial = child.id;
+ stateChildren.push(child);
+ return processedInitial = true;
+ };
+
+ _(children).filter(function(child){return _.isArray(child);}).forEach(function(child){
+
+ var tuple = deconstructNode(child, true), childTagName = tuple[0], childAttributes = tuple[1], childChildren = tuple[2];
+ switch (childTagName) {
+ case "transition":
+ transitionChildren.push(transformTransitionNode(child, state));
+ break;
+ case "onentry":
+ _(childChildren).forEach(function(actionNode){
+ onEntryChildren.push(transformActionNode(actionNode));
+ });
+ break;
+ case "onexit":
+ _(childChildren).forEach(function(actionNode){
+ onExitChildren.push(transformActionNode(actionNode));
+ });
+ break;
+ case "initial":
+ if (!processedInitial) {
+ processInitialState(child);
+ } else {
+ throw new Error("Encountered duplicate initial states in state " + state.id);
+ }
+ break;
+ case "history":
+ var c = transformStateNode(child, nextAncestors, genDepth, genAncestors, genDescendants, genLCA);
+ state.history = c.id;
+ stateChildren.push(c);
+ break;
+ case "datamodel":
+ transformDatamodel(child, nextAncestors, genDepth, genAncestors, genDescendants, genLCA);
+ break;
+ default:
+ if(_.contains(STATES_THAT_CAN_BE_CHILDREN,childTagName)){
+ var transformedStateNode = transformStateNode(child, nextAncestors, genDepth, genAncestors, genDescendants, genLCA);
+ //this is used to set default initial state, if initial state is not specified
+ if (firstStateChild === null) firstStateChild = transformedStateNode;
+ stateChildren.push(transformedStateNode);
+ }
+ break;
+ }
+
+ });
+
+ if (!processedInitial && tagName !== "parallel") {
+ var hasInitialAttribute = attributes && attributes.initial;
+
+ //create a fake initial state and process him
+ function generateFakeInitialState(targetId) {
+ var fakeInitialState;
+ fakeInitialState = [
+ "initial", [
+ "transition", {
+ target: targetId
+ }
+ ]
+ ];
+ return processInitialState(fakeInitialState);
+ }
+
+ if (hasInitialAttribute) {
+ generateFakeInitialState(attributes.initial);
+ } else {
+ if (firstStateChild) generateFakeInitialState(firstStateChild.id);
+ }
+ }
+
+ state.onexit = onExitChildren;
+ state.onentry = onEntryChildren;
+ state.transitions = _(transitionChildren).map(function(transition){return transition.documentOrder;});
+ state.children = _(stateChildren).map(function(child){return child.id;});
+
+ return state;
+}
+
+var idRoot = "$generated";
+
+var idCounter = {};
+
+function genId(tagName) {
+ idCounter[tagName] = idCounter[tagName] || 0;
+ return "" + idRoot + "-" + tagName + "-" + (idCounter[tagName]++);
+}
+
+function getLCA(s1, s2) {
+ /*
+ process.stdout.setEncoding 'utf-8'
+ process.stdout.write "\ngetLCA\n"
+ process.stdout.write "\ns1\n"
+ process.stdout.write inspect(s1)
+ process.stdout.write "\ns2\n"
+ process.stdout.write inspect(s2)
+ */
+ var a, anc, commonAncestors, _i, _len, _ref, _ref2;
+ commonAncestors = [];
+ _(s1.ancestors).forEach(function(a){
+ anc = idToStateMap[a];
+ if(_.contains(anc.descendants,s2.id)){
+ commonAncestors.push(a);
+ }
+ });
+ if(_.isEmpty(commonAncestors)) throw new Error("Could not find LCA for states.");
+ return commonAncestors[0];
+}
+
+if (require.main === module) {
+ var inFile = process.argv[2];
+ var outFile = process.argv[3];
+ function go(jsonStr) {
+ var fs, s, scxmlJson;
+ scxmlJson = JSON.parse(jsonStr);
+ s = transformAndSerialize(scxmlJson, true, true, true, true);
+ if (outFile === "-") {
+ return process.stdout.write(s);
+ } else {
+ fs = require('fs');
+ return fs.writeFileSync(outFile, s, 'utf-8');
+ }
+ }
+ if (!inFile || inFile === "-") {
+ process.stdin.resume();
+ process.stdin.setEncoding("utf-8");
+ var json = "";
+ process.stdin.on("data", function(data) {
+ return json += data;
+ });
+ process.stdin.on("end", function() {
+ return go(json);
+ });
+ } else {
+ var fs = require('fs');
+ var str = fs.readFileSync(inFile, 'utf-8');
+ go(str);
+ }
+}
Please sign in to comment.
Something went wrong with that request. Please try again.