Created
June 29, 2014 03:59
-
-
Save Phrogz/9e7180b6c37ee0643260 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-------+------------------------------------------------------------------------------- | |
-- ct | code ------------------------------------------------------------------------- | |
-------+------------------------------------------------------------------------------- | |
120 local LXSC = require 'lib/lxsc'; | |
*****0 (function(S) | |
120 S.MAX_ITERATIONS = 1000 | |
120 local OrderedSet,Queue,List = LXSC.OrderedSet, LXSC.Queue, LXSC.List | |
-- **************************************************************************** | |
410 local function entryOrder(a,b) return a._order < b._order end | |
324 local function exitOrder(a,b) return b._order < a._order end | |
1469 local function isDescendant(a,b) return a:descendantOf(b) end | |
157 local function isCancelEvent(e) return e.name=='quit.lxsc' end | |
755 local function isFinalState(s) return s._kind=='final' end | |
905 local function isScxmlState(s) return s._kind=='scxml' end | |
1641 local function isHistoryState(s) return s._kind=='history' end | |
582 local function isParallelState(s) return s._kind=='parallel' end | |
1330 local function isCompoundState(s) return s.isCompound end | |
901 local function isAtomicState(s) return s.isAtomic end | |
150 local function getChildStates(s) return s.reals end | |
local function findLCCA(first,rest) -- least common compound ancestor | |
743 for _,anc in ipairs(first.ancestors) do | |
743 if isCompoundState(anc) or isScxmlState(anc) then | |
1414 if rest:every(function(s) return isDescendant(s,anc) end) then | |
644 return anc | |
end | |
end | |
end | |
end | |
120 local emptyList = List() | |
120 local depth=0 | |
local function logloglog(s) | |
-- print(string.rep(' ',depth)..tostring(s)) | |
end | |
5386 local function startfunc(s) logloglog(s) depth=depth+1 end | |
5386 local function closefunc(s) if s then logloglog(s) end depth=depth-1 end | |
-- **************************************************************************** | |
120 function S:interpret(options) | |
120 self._delayedSend = { extraTime=0 } | |
-- if not self:validate() then self:failWithError() end | |
120 if not rawget(self,'_stateById') then self:expandScxmlSource() end | |
120 self._configuration:clear() | |
120 self._statesToInvoke = OrderedSet() -- TODO: implement <invoke> | |
120 self._internalQueue = Queue() | |
120 self._externalQueue = Queue() | |
120 self._historyValue = {} | |
120 self._data = LXSC.Datamodel(self,options and options.data) | |
120 self._data:_setSystem('_sessionid',LXSC.uuid4()) | |
120 self._data:_setSystem('_name',self.name or LXSC.uuid4()) | |
120 self._data:_setSystem('_ioprocessors',{}) | |
120 if self.binding == "early" then self._data:initAll() end | |
120 self.running = true | |
120 self:executeGlobalScriptElement() | |
120 self:enterStates(self.initial.transitions) | |
120 self:mainEventLoop() | |
end | |
-- ****************************************************************************************************** | |
-- ****************************************************************************************************** | |
-- ****************************************************************************************************** | |
120 function S:mainEventLoop() | |
local anyTransition, enabledTransitions, macrostepDone, iterations | |
190 while self.running do | |
145 anyTransition = false -- (LXSC specific) | |
145 iterations = 0 -- (LXSC specific) | |
145 macrostepDone = false | |
-- Here we handle eventless transitions and transitions | |
-- triggered by internal events until macrostep is complete | |
416 while self.running and not macrostepDone and iterations<S.MAX_ITERATIONS do | |
271 enabledTransitions = self:selectEventlessTransitions() | |
271 if enabledTransitions:isEmpty() then | |
176 if self._internalQueue:isEmpty() then | |
48 macrostepDone = true | |
else | |
128 logloglog("-- Internal Queue: "..self._internalQueue:inspect()) | |
128 local internalEvent = self._internalQueue:dequeue() | |
128 self._data:_setSystem('_event',internalEvent) | |
128 enabledTransitions = self:selectTransitions(internalEvent) | |
end | |
end | |
271 if not enabledTransitions:isEmpty() then | |
218 anyTransition = true | |
218 self:microstep(enabledTransitions) | |
end | |
271 iterations = iterations + 1 | |
end | |
145 if iterations>=S.MAX_ITERATIONS then print(string.format("Warning: stopped unstable system after %d internal iterations",S.MAX_ITERATIONS)) end | |
-- Either we're in a final state, and we break out of the loop… | |
145 if not self.running then break end | |
-- …or we've completed a macrostep, so we start a new macrostep by waiting for an external event | |
-- Here we invoke whatever needs to be invoked. The implementation of 'invoke' is platform-specific | |
86 for _,state in ipairs(self._statesToInvoke) do for _,inv in ipairs(state._invokes) do self:invoke(inv) end end | |
48 self._statesToInvoke:clear() | |
-- Invoking may have raised internal error events; if so, we skip and iterate to handle them | |
48 if self._internalQueue:isEmpty() then | |
48 logloglog("-- External Queue: "..self._externalQueue:inspect()) | |
48 local externalEvent = self._externalQueue:dequeue() | |
48 if externalEvent then -- (LXSC specific) The queue might be empty. | |
37 if isCancelEvent(externalEvent) then | |
*****0 self.running = false | |
else | |
37 self._data:_setSystem('_event',externalEvent) | |
76 for _,state in ipairs(self._configuration) do | |
39 for _,inv in ipairs(state._invokes) do | |
*****0 if inv.invokeid == externalEvent.invokeid then self:applyFinalize(inv, externalEvent) end | |
*****0 if inv.autoforward then self:send(inv.id, externalEvent) end | |
end | |
end | |
37 enabledTransitions = self:selectTransitions(externalEvent) | |
37 if not enabledTransitions:isEmpty() then | |
36 anyTransition = true | |
36 self:microstep(enabledTransitions) | |
end | |
end | |
end | |
-- (LXSC specific) we stop iterating as soon as no transitions occur | |
48 if not anyTransition then break end | |
end | |
end | |
-- We re-check if we're running here because we use step-based processing; | |
-- we may have exited the 'running' loop if there were no more events to process. | |
151 if not self.running then self:exitInterpreter() end | |
end | |
-- ****************************************************************************************************** | |
-- ****************************************************************************************************** | |
-- ****************************************************************************************************** | |
120 function S:executeGlobalScriptElement() | |
120 if rawget(self,'_script') then self:executeSingle(self._script) end | |
end | |
120 function S:exitInterpreter() | |
142 local statesToExit = self._configuration:toList():sort(exitOrder) | |
284 for _,s in ipairs(statesToExit) do | |
142 for _,content in ipairs(s._onexits) do self:executeContent(content) end | |
142 for _,inv in ipairs(s._invokes) do self:cancelInvoke(inv) end | |
-- (LXSC specific) We do not delete the configuration on exit so that it may be examined later. | |
-- self._configuration:delete(s) | |
142 if isFinalState(s) and isScxmlState(s.parent) then | |
142 self:returnDoneEvent(self:donedata(s)) | |
end | |
end | |
end | |
120 function S:selectEventlessTransitions() | |
271 startfunc('selectEventlessTransitions()') | |
271 local enabledTransitions = OrderedSet() | |
271 local atomicStates = self._configuration:toList():filter(isAtomicState):sort(entryOrder) | |
574 for _,state in ipairs(atomicStates) do | |
303 self:addEventlessTransition(state,enabledTransitions) | |
end | |
271 enabledTransitions = self:removeConflictingTransitions(enabledTransitions) | |
271 closefunc('-- selectEventlessTransitions result: '..enabledTransitions:inspect()) | |
271 return enabledTransitions | |
end | |
-- (LXSC specific) we use this function since Lua cannot break out of a nested loop | |
120 function S:addEventlessTransition(state,enabledTransitions) | |
889 for _,s in ipairs(state.selfAndAncestors) do | |
690 for _,t in ipairs(s._eventlessTransitions) do | |
104 if t:conditionMatched(self._data) then | |
99 enabledTransitions:add(t) | |
99 return | |
end | |
end | |
end | |
end | |
120 function S:selectTransitions(event) | |
165 startfunc('selectTransitions( '..event:inspect()..' )') | |
165 local enabledTransitions = OrderedSet() | |
165 local atomicStates = self._configuration:toList():filter(isAtomicState):sort(entryOrder) | |
356 for _,state in ipairs(atomicStates) do | |
191 self:addTransitionForEvent(state,event,enabledTransitions) | |
end | |
165 enabledTransitions = self:removeConflictingTransitions(enabledTransitions) | |
165 closefunc('-- selectTransitions result: '..enabledTransitions:inspect()) | |
165 return enabledTransitions | |
end | |
-- (LXSC specific) we use this function since Lua cannot break out of a nested loop | |
120 function S:addTransitionForEvent(state,event,enabledTransitions) | |
320 for _,s in ipairs(state.selfAndAncestors) do | |
370 for _,t in ipairs(s._eventedTransitions) do | |
241 if t:matchesEvent(event) and t:conditionMatched(self._data) then | |
175 enabledTransitions:add(t) | |
175 return | |
end | |
end | |
end | |
end | |
120 function S:removeConflictingTransitions(enabledTransitions) | |
436 startfunc('removeConflictingTransitions( enabledTransitions:'..enabledTransitions:inspect()..' )') | |
436 local filteredTransitions = OrderedSet() | |
699 for _,t1 in ipairs(enabledTransitions) do | |
263 local t1Preempted = false | |
263 local transitionsToRemove = OrderedSet() | |
274 for _,t2 in ipairs(filteredTransitions) do | |
13 if self:computeExitSet(List(t1)):hasIntersection(self:computeExitSet(List(t2))) then | |
3 if isDescendant(t1.source,t2.source) then | |
1 transitionsToRemove:add(t2) | |
else | |
2 t1Preempted = true | |
2 break | |
end | |
end | |
end | |
263 if not t1Preempted then | |
262 for _,t3 in ipairs(transitionsToRemove) do | |
1 filteredTransitions:delete(t3) | |
end | |
261 filteredTransitions:add(t1) | |
end | |
end | |
436 closefunc('-- removeConflictingTransitions result: '..filteredTransitions:inspect()) | |
436 return filteredTransitions | |
end | |
120 function S:microstep(enabledTransitions) | |
254 startfunc('microstep( enabledTransitions:'..enabledTransitions:inspect()..' )') | |
254 self:exitStates(enabledTransitions) | |
254 self:executeTransitionContent(enabledTransitions) | |
254 self:enterStates(enabledTransitions) | |
254 if rawget(self,'onEnteredAll') then self.onEnteredAll() end | |
254 closefunc() | |
end | |
120 function S:exitStates(enabledTransitions) | |
254 startfunc('exitStates( enabledTransitions:'..enabledTransitions:inspect()..' )') | |
254 local statesToExit = self:computeExitSet(enabledTransitions) | |
611 for _,s in ipairs(statesToExit) do self._statesToInvoke:delete(s) end | |
254 statesToExit = statesToExit:toList():sort(exitOrder) | |
-- Record history for states being exited | |
611 for _,s in ipairs(statesToExit) do | |
621 for _,h in ipairs(s.states) do | |
264 if h._kind=='history' then | |
22 self._historyValue[h.id] = self._configuration:toList():filter(function(s0) | |
32 if h.type=='deep' then | |
15 return isAtomicState(s0) and isDescendant(s0,s) | |
else | |
17 return s0.parent==s | |
end | |
22 end) | |
end | |
end | |
end | |
-- Exit the states | |
611 for _,s in ipairs(statesToExit) do | |
357 if self.onBeforeExit then self.onBeforeExit(s.id,s._kind,s.isAtomic) end | |
391 for _,content in ipairs(s._onexits) do | |
34 self:executeContent(content) | |
end | |
357 for _,inv in ipairs(s._invokes) do self:cancelInvoke(inv) end | |
357 self._configuration:delete(s) | |
357 logloglog(string.format("-- removed %s from the configuration; config is now {%s}",s:inspect(),table.concat(self:activeStateIds(),', '))) | |
end | |
254 closefunc() | |
end | |
120 function S:computeExitSet(transitions) | |
280 startfunc('computeExitSet( transitions:'..transitions:inspect()..' )') | |
280 local statesToExit = OrderedSet() | |
566 for _,t in ipairs(transitions) do | |
286 if t.targets then | |
266 local domain = self:getTransitionDomain(t) | |
786 for _,s in ipairs(self._configuration) do | |
520 if isDescendant(s,domain) then | |
430 statesToExit:add(s) | |
end | |
end | |
end | |
end | |
280 closefunc('-- computeExitSet result '..statesToExit:inspect()) | |
280 return statesToExit | |
end | |
120 function S:executeTransitionContent(enabledTransitions) | |
254 startfunc('executeTransitionContent( enabledTransitions:'..enabledTransitions:inspect()..' )') | |
514 for _,t in ipairs(enabledTransitions) do | |
260 if self.onTransition then self.onTransition(t) end | |
291 for _,executable in ipairs(t._exec) do | |
31 if not self:executeSingle(executable) then break end | |
end | |
end | |
254 closefunc() | |
end | |
120 function S:enterStates(enabledTransitions) | |
374 startfunc('enterStates( enabledTransitions:'..enabledTransitions:inspect()..' )') | |
374 local statesToEnter = OrderedSet() | |
374 local statesForDefaultEntry = OrderedSet() | |
374 local defaultHistoryContent = {} -- temporary table for default content in history states | |
374 self:computeEntrySet(enabledTransitions,statesToEnter,statesForDefaultEntry,defaultHistoryContent) | |
851 for _,s in ipairs(statesToEnter:toList():sort(entryOrder)) do | |
477 self._configuration:add(s) | |
477 logloglog(string.format("-- added %s '%s' to the configuration; config is now <%s>",s._kind,s.id,table.concat(self:activeStateIds(),', '))) | |
477 if isScxmlState(s) then error("Added SCXML to configuration.") end | |
477 self._statesToInvoke:add(s) | |
477 if self.binding=="late" then | |
-- The LXSC datamodel ensures this happens only once per state | |
3 self._data:initState(s) | |
end | |
620 for _,content in ipairs(s._onentrys) do | |
143 self:executeContent(content) | |
end | |
477 if self.onAfterEnter then self.onAfterEnter(s.id,s._kind,s.isAtomic) end | |
477 if statesForDefaultEntry:isMember(s) then | |
90 for _,t in ipairs(s.initial.transitions) do | |
47 for _,executable in ipairs(t._exec) do | |
2 if not self:executeSingle(executable) then break end | |
end | |
end | |
end | |
477 if defaultHistoryContent[s.id] then | |
2 logloglog("-- executing defaultHistoryContent for "..s.id) | |
2 for _,executable in ipairs(defaultHistoryContent[s.id]) do | |
*****0 if not self:executeSingle(executable) then break end | |
end | |
end | |
477 if isFinalState(s) then | |
134 local parent = s.parent | |
134 if isScxmlState(parent) then | |
120 self.running = false | |
else | |
14 local grandparent = parent.parent | |
14 self:fireEvent( "done.state."..parent.id, self:donedata(s), {type='internal'} ) | |
14 if isParallelState(grandparent) then | |
4 local allAreInFinal = true | |
10 for _,child in ipairs(grandparent.reals) do | |
8 if not self:isInFinalState(child) then | |
2 allAreInFinal = false | |
2 break | |
end | |
end | |
4 if allAreInFinal then | |
2 self:fireEvent( "done.state."..grandparent.id, nil, {type='internal'} ) | |
end | |
end | |
end | |
end | |
end | |
374 closefunc() | |
end | |
120 function S:computeEntrySet(transitions,statesToEnter,statesForDefaultEntry,defaultHistoryContent) | |
374 startfunc('computeEntrySet( transitions:'..transitions:inspect()..', ... )') | |
754 for _,t in ipairs(transitions) do | |
380 if t.targets then | |
742 for _,s in ipairs(t.targets) do | |
372 self:addDescendantStatesToEnter(s,statesToEnter,statesForDefaultEntry,defaultHistoryContent) | |
end | |
end | |
-- logloglog('-- after adding descendants statesToEnter is: '..statesToEnter:inspect()) | |
380 local ancestor = self:getTransitionDomain(t) | |
752 for _,s in ipairs(self:getEffectiveTargetStates(t)) do | |
372 self:addAncestorStatesToEnter(s,ancestor,statesToEnter,statesForDefaultEntry,defaultHistoryContent) | |
end | |
end | |
374 logloglog('-- computeEntrySet result statesToEnter: '..statesToEnter:inspect()) | |
374 logloglog('-- computeEntrySet result statesForDefaultEntry: '..statesForDefaultEntry:inspect()) | |
374 closefunc() | |
end | |
120 function S:addDescendantStatesToEnter(state,statesToEnter,statesForDefaultEntry,defaultHistoryContent) | |
457 startfunc("addDescendantStatesToEnter( state:"..state:inspect()..", ... )") | |
457 if isHistoryState(state) then | |
4 if self._historyValue[state.id] then | |
4 for _,s in ipairs(self._historyValue[state.id]) do | |
2 self:addDescendantStatesToEnter(s,statesToEnter,statesForDefaultEntry,defaultHistoryContent) | |
2 self:addAncestorStatesToEnter(s,state.parent,statesToEnter,statesForDefaultEntry,defaultHistoryContent) | |
end | |
else | |
2 defaultHistoryContent[state.parent.id] = state.transitions[1]._exec | |
2 logloglog("-- defaultHistoryContent['"..state.parent.id.."'] = "..(#state.transitions[1]._exec).." executables") | |
4 for _,t in ipairs(state.transitions) do | |
2 if t.targets then | |
4 for _,s in ipairs(t.targets) do | |
2 self:addDescendantStatesToEnter(s,statesToEnter,statesForDefaultEntry,defaultHistoryContent) | |
2 self:addAncestorStatesToEnter(s,state.parent,statesToEnter,statesForDefaultEntry,defaultHistoryContent) | |
end | |
end | |
end | |
end | |
else | |
453 statesToEnter:add(state) | |
453 logloglog("statesToEnter:add( "..state:inspect().." )") | |
453 if isCompoundState(state) then | |
45 statesForDefaultEntry:add(state) | |
90 for _,t in ipairs(state.initial.transitions) do | |
92 for _,s in ipairs(self:getEffectiveTargetStates(t)) do | |
47 self:addDescendantStatesToEnter(s,statesToEnter,statesForDefaultEntry,defaultHistoryContent) | |
47 self:addAncestorStatesToEnter(s,state,statesToEnter,statesForDefaultEntry,defaultHistoryContent) | |
end | |
end | |
408 elseif isParallelState(state) then | |
35 for _,child in ipairs(getChildStates(state)) do | |
79 if not statesToEnter:some(function(s) return isDescendant(s,child) end) then | |
24 self:addDescendantStatesToEnter(child,statesToEnter,statesForDefaultEntry,defaultHistoryContent) | |
end | |
end | |
end | |
end | |
457 closefunc() | |
end | |
120 function S:addAncestorStatesToEnter(state,ancestor,statesToEnter,statesForDefaultEntry,defaultHistoryContent) | |
423 startfunc("addAncestorStatesToEnter( state:"..state:inspect()..", ancestor:"..ancestor:inspect()..", ... )") | |
463 for anc in state:ancestorsUntil(ancestor) do | |
40 statesToEnter:add(anc) | |
40 logloglog("statesToEnter:add( "..anc:inspect().." )") | |
40 if isParallelState(anc) then | |
35 for _,child in ipairs(getChildStates(anc)) do | |
83 if not statesToEnter:some(function(s) return isDescendant(s,child) end) then | |
10 self:addDescendantStatesToEnter(child,statesToEnter,statesForDefaultEntry,defaultHistoryContent) | |
end | |
end | |
end | |
end | |
423 closefunc() | |
end | |
120 function S:isInFinalState(s) | |
8 if isCompoundState(s) then | |
24 return getChildStates(s):some(function(s) return isFinalState(s) and self._configuration:isMember(s) end) | |
*****0 elseif isParallelState(s) then | |
*****0 return getChildStates(s):every(function(s) self:isInFinalState(s) end) | |
else | |
*****0 return false | |
end | |
end | |
120 function S:getTransitionDomain(t) | |
646 startfunc('getTransitionDomain( t:'..t:inspect()..' )' ) | |
local result | |
646 local tstates = self:getEffectiveTargetStates(t) | |
646 if not tstates then | |
*****0 result = nil | |
650 elseif t.type=='internal' and isCompoundState(t.source) and tstates:every(function(s) return isDescendant(s,t.source) end) then | |
2 result = t.source | |
else | |
644 result = findLCCA(t.source,t.targets or emptyList) | |
end | |
646 closefunc('-- getTransitionDomain result: '..tostring(result and result.id)) | |
646 return result | |
end | |
120 function S:getEffectiveTargetStates(transition) | |
1078 startfunc('getEffectiveTargetStates( transition:'..transition:inspect()..' )') | |
1078 local targets = OrderedSet() | |
1078 if transition.targets then | |
2122 for _,s in ipairs(transition.targets) do | |
1064 if isHistoryState(s) then | |
13 if self._historyValue[s.id] then | |
6 targets:union(self._historyValue[s.id]) | |
else | |
-- History states can only have one transition, so we hard-code that here. | |
7 targets:union(self:getEffectiveTargetStates(s.transitions[1])) | |
end | |
else | |
1051 targets:add(s) | |
end | |
end | |
end | |
1078 closefunc('-- getEffectiveTargetStates result: '..targets:inspect()) | |
1078 return targets | |
end | |
120 function S:expandScxmlSource() | |
120 self:convertInitials() | |
120 self._stateById = {} | |
691 for _,s in ipairs(self.states) do s:cacheReference(self._stateById) end | |
120 self:resolveReferences(self._stateById) | |
end | |
120 function S:returnDoneEvent(donedata) | |
-- TODO: implement | |
end | |
120 function S:donedata(state) | |
156 local c = state._donedatas[1] | |
156 if c then | |
8 if c._kind=='content' then | |
4 local wrapper = {} | |
4 self:executeSingle(c,wrapper) | |
4 return wrapper.content | |
else | |
4 local map = {} | |
8 for _,p in ipairs(state._donedatas) do | |
4 local val = p.location and self._data:get(p.location) or p.expr and self._data:eval(p.expr) | |
4 if val == LXSC.Datamodel.INVALIDLOCATION then | |
2 self:fireEvent("error.execution.invalid-param-value","There was an error determining the value for a <param> inside a <donedata>") | |
2 elseif val ~= LXSC.Datamodel.EVALERROR then | |
1 if p.name==nil or p.name=="" then | |
*****0 self:fireEvent("error.execution.invalid-param-name","Unsupported <param> name '"..tostring(p.name).."'") | |
else | |
1 map[p.name] = val | |
end | |
end | |
end | |
4 return next(map) and map | |
end | |
end | |
end | |
120 function S:fireEvent(name,data,eventValues) | |
221 eventValues = eventValues or {} | |
221 eventValues.type = eventValues.type or 'platform' | |
221 local event = LXSC.Event(name,data,eventValues) | |
221 logloglog(string.format("-- queued %s event '%s'",event.type,event.name)) | |
221 if rawget(self,'onEventFired') then self.onEventFired(event) end | |
221 self[eventValues.type=='external' and "_externalQueue" or "_internalQueue"]:enqueue(event) | |
221 return event | |
end | |
-- Sensible aliases | |
120 S.start = S.interpret | |
120 S.restart = S.interpret | |
120 function S:step() | |
31 self:processDelayedSends() | |
31 self:mainEventLoop() | |
end | |
240 end)(LXSC.SCXML) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment