/******************************************************************************* * Copyright (c) 2014 protos software gmbh (http://www.protos.de). * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 * which accompanies this distribution, and is available at * https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * CONTRIBUTORS: * Henrik Rentz-Reichert (initial contribution) * *******************************************************************************/ package org.eclipse.etrice.generator.fsm.generic import com.google.inject.Inject import org.eclipse.etrice.core.fsm.fSM.ComponentCommunicationType import org.eclipse.etrice.core.fsm.fSM.GuardedTransition import org.eclipse.etrice.core.fsm.fSM.MessageFromIf import org.eclipse.etrice.core.fsm.fSM.ModelComponent import org.eclipse.etrice.core.fsm.fSM.State import org.eclipse.etrice.core.fsm.fSM.Transition import org.eclipse.etrice.core.fsm.fSM.TransitionPoint import org.eclipse.etrice.core.fsm.naming.FSMNameProvider import org.eclipse.etrice.core.fsm.util.FSMHelpers import org.eclipse.etrice.core.genmodel.fsm.TriggerExtensions import org.eclipse.etrice.core.genmodel.fsm.fsmgen.CommonTrigger import org.eclipse.etrice.core.genmodel.fsm.fsmgen.GraphContainer import org.eclipse.etrice.core.genmodel.fsm.fsmgen.Link import org.eclipse.etrice.core.genmodel.fsm.fsmgen.Node import org.eclipse.etrice.generator.fsm.base.CodegenHelpers import org.eclipse.xtext.util.Pair import static org.eclipse.xtext.util.Tuples.* import static extension org.eclipse.etrice.core.genmodel.fsm.FsmGenExtensions.* import java.util.ArrayList /** * @author Henrik Rentz-Reichert * */ abstract class AbstractStateMachineGenerator { protected static val NUM_PREDEF_STATE_CONSTANTS = 2 @Inject public extension FSMHelpers @Inject public extension CodegenHelpers @Inject public extension FSMExtensions @Inject public ILanguageExtensionBase langExt @Inject public IMessageIdGenerator msgIdGen @Inject public IIfItemIdGenerator itemIdGen @Inject public TransitionChainGenerator transitionChainGenerator @Inject public IDetailCodeTranslator translator @Inject public FSMNameProvider fsmNameProvider /** * generates trigger IDs. * Inheritance (if available) is used for base class IDs. * * @param gc the {@link GraphContainer} * @return the generated code */ def public String genTriggerConstants(GraphContainer gc) { gc.genTriggerConstants(langExt.usesInheritance) } /** * generates trigger IDs. * Inheritance (if available) is used for base class IDs. * * @param gc the {@link GraphContainer} * @param omitBase use true if no base class trigger constants are needed * * @return the generated code */ def public String genTriggerConstants(GraphContainer gc, boolean omitBase) { val triggers = if (omitBase) gc.component.ownMessagesFromInterfaces else gc.component.allMessagesFromInterfaces val list = >newArrayList list.add(pair("POLLING", "0")); for (mif : triggers) { if (mif.from.eventDriven) { list.add(pair(mif.triggerCodeName, itemIdGen.getIfItemId(mif.from)+" + EVT_SHIFT*"+msgIdGen.getMessageID(mif))) } } return langExt.genEnumeration("triggers", list) } def String getTriggerCodeName(MessageFromIf mif) { return "TRIG_"+mif.from.name+"__"+fsmNameProvider.getMessageName(mif.message); } /** * generates state ID constants. * Inheritance (if available) is used for base class IDs. * * @param gc the {@link GraphContainer} * @return the generated code */ def public genStateIdConstants(GraphContainer gc) { gc.genStateIdConstants(langExt.usesInheritance) } /** * generates state ID constants. * Inheritance (if available) is used for base class IDs. * * @param gc the {@link GraphContainer} * @param omitBase use true if no base class state constants are needed * * @return the generated code */ def public genStateIdConstants(GraphContainer gc, boolean omitBase) { // with inheritance we exclude inherited states val allStateNodes = gc.graph.allStateNodes.toList // TODO: without toList this didn't work - why? // omitBase: base class constants are inherited, so we start at an offset of // NUM_PREDEF_STATE_CONSTANTS + #(inherited states) // !omitBase: nothing is inherited, offset is just NUM_PREDEF_STATE_CONSTANTS var offset = NUM_PREDEF_STATE_CONSTANTS + if (omitBase) allStateNodes.filter[inherited].size else 0 // considered are all states (case of !omitBase) or just the ones which are not inherited (case of omitBase) var consideredStates = (if (omitBase) allStateNodes.filter[!inherited] else allStateNodes).map[stateGraphNode].filter(typeof(State)).toList // we sort them with the non leaf states last to have the history as short as possible consideredStates = consideredStates.leafStatesLast.toList var list = >newArrayList if (!omitBase) { list.add(pair("NO_STATE","0")) list.add(pair("STATE_TOP","1")) } for (state : consideredStates) { list.add(pair(state.getGenStateId, offset.toString)) offset = offset+1; } list.add(pair("STATE_MAX", offset.toString)) return langExt.genEnumeration("state_ids", list) } /** * generates transition chain ID constants. * Inheritance can't be used used for base class IDs because of corner cases * where base class and derived class chain IDs deviate (see bug 501354). * * @param gc the {@link GraphContainer} * * @return the generated code */ def public genTransitionChainConstants(GraphContainer gc) { gc.genTransitionChainConstants(false/*langExt.usesInheritance*/) } /** * generates transition chain ID constants.

* * Note: Inheritance can't be used used for base class IDs because of corner cases * where base class and derived class chain IDs deviate. * * @param gc the {@link GraphContainer} * @param omitBase use true if no base class transition chain constants are needed * * @return the generated code */ def public genTransitionChainConstants(GraphContainer gc, boolean omitBase) { val chains = (if (omitBase) gc.graph.allLinks.filter[!inherited] else gc.graph.allLinks).map[transition].filter[isChainHead].filter(typeof(Transition)).toList var offset = if (omitBase) gc.graph.allLinks.filter[inherited].size else 0 var list = >newArrayList for (chain : chains) { offset = offset+1; list.add(pair(chain.genChainId, offset.toString)) } return langExt.genEnumeration("ChainIDs", list) } /** * generates entry and exit code for states * * @param gc the {@link GraphContainer} * @param generateImplementation if true the implementation is generated, else the declaration * * @return the generated code */ def public String genEntryAndExitCodes(GraphContainer gc, boolean generateImplementation) { gc.genEntryAndExitCodes(generateImplementation, langExt.usesInheritance) } /** * generates entry and exit code for states * * @param gc the {@link GraphContainer} * @param generateImplementation if true the implementation is generated, else the declaration * @param omitBase use true if no base class entry and exit codes are needed * * @return the generated code */ def public String genEntryAndExitCodes(GraphContainer gc, boolean generateImplementation, boolean omitBase) { val states = gc.graph.allStateNodes.filter[!omitBase || !inherited].toList ''' «FOR state : states» «gc.genActionCodeMethods(state, generateImplementation)» «ENDFOR» ''' } /** * generates transition action codes * * @param gc the {@link GraphContainer} * @param generateImplementation if true the implementation is generated, else the declaration * * @return the generated code */ def public String genActionCodes(GraphContainer gc, boolean generateImplementation) { gc.genActionCodes(generateImplementation, langExt.usesInheritance) } /** * generates transition action codes * * @param gc the {@link GraphContainer} * @param generateImplementation if true the implementation is generated, else the declaration * @param omitBase use true if no base class action codes are needed * * @return the generated code */ def public String genActionCodes(GraphContainer gc, boolean generateImplementation, boolean omitBase) { val transitions = gc.graph.allLinks.filter[!omitBase || !inherited].filter[transition.action.hasDetailCode].toList ''' «FOR tr : transitions» «gc.genActionCodeMethod(tr, generateImplementation)» «ENDFOR» ''' } def public String genStateSwitchMethods(GraphContainer gc, boolean generateImplementation) { val mc = gc.component val async = mc.commType==ComponentCommunicationType::ASYNCHRONOUS val eventDriven = mc.commType==ComponentCommunicationType::EVENT_DRIVEN val ifItemPtr = interfaceItemType + langExt.pointerLiteral val handleEvents = async || eventDriven val chainIDScope = if (langExt.usesInheritance) mc.className+langExt.scopeSeparator else "" val opScope = langExt.operationScope(mc.className, !generateImplementation) val opScopePriv = if (langExt.usesInheritance) opScope else "" val publicIf = if (langExt.usesInheritance) langExt.accessLevelPublic else langExt.accessLevelPrivate val privAccess = langExt.accessLevelPrivate val selfPtr = langExt.selfPointer(mc.className, true) val selfOnly = langExt.selfPointer(mc.className, false) val getLocalId = if (langExt.usesInheritance) { if (langExt.usesPointers) "->getLocalId()" else ".getLocalId()" } else "->localId" val constIfItemPtr = if (langExt.usesPointers) "const "+ifItemPtr else ifItemPtr val usesHdlr = usesHandlerTrPoints(gc) val nodes = gc.graph.allStateNodes.toList.sortBy[(stateGraphNode as State).genStateId] val state2node = newHashMap nodes.forEach[state2node.put(stateGraphNode as State, it)] val states = nodes.map[stateGraphNode].filter(typeof(State)).toList val transitionChains = gc.graph.allLinks.filter[isChainHead].toList.sortBy[transition.genChainId] ''' /** * calls exit codes while exiting from the current state to one of its * parent states while remembering the history * @param current__et - the current state * @param to - the final parent state «IF usesHdlr» * @param handler__et - entry and exit codes are called only if not handler (for handler TransitionPoints) «ENDIF» */ «IF generateImplementation» «privAccess»void «opScopePriv»exitTo(«selfPtr»«stateType» current__et, «stateType» to«IF usesHdlr», «boolType» handler__et«ENDIF») { while (current__et!=to) { switch (current__et) { «FOR state : states» case «state.getGenStateId»: «IF state.hasExitCode(true)»«IF usesHdlr»if (!handler__et) «ENDIF»«state.exitCodeOperationName»(«langExt.selfPointer(false)»);«ENDIF» «setHistory(state.parentStateId, state.genStateId)»; current__et = «state.parentStateId»; break; «ENDFOR» default: /* should not occur */ break; } } } «ELSE» void exitTo(«selfPtr»«stateType» current__et, «stateType» to«IF usesHdlr», «boolType» handler__et«ENDIF»); «ENDIF» /** * calls action, entry and exit codes along a transition chain. The generic data are cast to typed data * matching the trigger of this chain. The ID of the final state is returned * @param chain__et - the chain ID * @param generic_data__et - the generic data pointer * @return the +/- ID of the final state either with a positive sign, that indicates to execute the state's entry code, or a negative sign vice versa */ «IF generateImplementation» «privAccess»«stateType» «opScopePriv»executeTransitionChain(«selfPtr»int chain__et«IF handleEvents», «constIfItemPtr» ifitem, «langExt.voidPointer» generic_data__et«ENDIF») { switch (chain__et) { «FOR tc : transitionChains» case «chainIDScope»«tc.transition.genChainId»: { «transitionChainGenerator.generateExecuteChain(gc, tc)» } «ENDFOR» default: /* should not occur */ break; } return NO_STATE; } «ELSE» «stateType» executeTransitionChain(«selfPtr»int chain__et«IF handleEvents», «constIfItemPtr» ifitem, «langExt.voidPointer» generic_data__et«ENDIF»); «ENDIF» /** * calls entry codes while entering a state's history. The ID of the final leaf state is returned * @param state__et - the state which is entered «IF usesHdlr» * @param handler__et - entry code is executed if not handler «ENDIF» * @return - the ID of the final leaf state */ «IF generateImplementation» «privAccess»«stateType» «opScopePriv»enterHistory(«selfPtr»«stateType» state__et«IF usesHdlr», «boolType» handler__et«ENDIF») { «val needsSkipVar = !states.filter(s|s.hasEntryCode(true)).empty» «IF needsSkipVar» «boolType» skip_entry__et = «langExt.booleanConstant(false)»; «ENDIF» if (state__et >= STATE_MAX) { state__et = «IF !langExt.usesInheritance»(«stateType»)«ENDIF» (state__et - STATE_MAX); «IF needsSkipVar» skip_entry__et = «langExt.booleanConstant(true)»; «ENDIF» } while («langExt.booleanConstant(true)») { switch (state__et) { «FOR state : states» case «state.genStateId»: «IF state.hasEntryCode(true)»if (!(skip_entry__et«IF usesHdlr» || handler__et«ENDIF»)) «state.entryCodeOperationName»(«langExt.selfPointer(false)»);«ENDIF» «IF state.isLeaf» /* in leaf state: return state id */ return «state.getGenStateId»; «ELSE» /* state has a sub graph */ «var sub_initt = state2node.get(state).subgraph.initialTransition» «IF sub_initt!==null» /* with init transition */ if («getHistory(state.genStateId)»==NO_STATE) { state__et = executeTransitionChain(«langExt.selfPointer(true)»«chainIDScope»«sub_initt.genChainId»«IF handleEvents», «langExt.nullPointer», «langExt.nullPointer»«ENDIF»); } else { state__et = «state.genStateId.history»; } «ELSE» /* without init transition */ state__et = «state.genStateId.history»; «ENDIF» break; «ENDIF» «ENDFOR» case STATE_TOP: state__et = «getHistory("STATE_TOP")»; break; default: /* should not occur */ break; } «IF needsSkipVar» skip_entry__et = «langExt.booleanConstant(false)»; «ENDIF» } «unreachableReturn» } «ELSE» «stateType» enterHistory(«selfPtr»«stateType» state__et«IF usesHdlr», «boolType» handler__et«ENDIF»); «ENDIF» «IF generateImplementation» «publicIf»void «opScope»executeInitTransition(«selfOnly») { «var initt = gc.graph.initialTransition» int chain__et = «chainIDScope»«initt.genChainId»; «stateType» next__et = «opScopePriv»executeTransitionChain(«langExt.selfPointer(true)»chain__et«IF handleEvents», «langExt.nullPointer», «langExt.nullPointer»«ENDIF»); next__et = «opScopePriv»enterHistory(«langExt.selfPointer(true)»next__et«IF usesHdlr», «langExt.booleanConstant(false)»«ENDIF»); setState(«langExt.selfPointer(true)»next__et); } «ELSE» void «opScope»executeInitTransition(«selfOnly»); «ENDIF» /* receiveEvent contains the main implementation of the FSM */ «IF generateImplementation» «publicIf»void «opScope»receiveEventInternal(«langExt.selfPointer(mc.className, handleEvents)»«IF handleEvents»«ifItemPtr» ifitem, int localId, int evt, «langExt.voidPointer» generic_data__et«ENDIF») { «IF async» int trigger__et = (ifitem==«langExt.nullPointer»)? POLLING : localId + EVT_SHIFT*evt; «ELSEIF eventDriven» int trigger__et = localId + EVT_SHIFT*evt; «ENDIF» int chain__et = NOT_CAUGHT; «stateType» catching_state__et = NO_STATE; «IF usesHdlr» «boolType» is_handler__et = «langExt.booleanConstant(false)»; «ENDIF» «IF async || eventDriven» «markVariableUsed("trigger__et")» «ENDIF» «IF handleEvents» if (!handleSystemEvent(ifitem, evt, generic_data__et)) { «genStateSwitch(gc, usesHdlr)» } «ELSE» «genStateSwitch(gc, usesHdlr)» «ENDIF» if (chain__et != NOT_CAUGHT) { «opScopePriv»exitTo(«langExt.selfPointer(true)»getState(«langExt.selfPointer(false)»), catching_state__et«IF usesHdlr», is_handler__et«ENDIF»); { «stateType» next__et = «opScopePriv»executeTransitionChain(«langExt.selfPointer(true)»chain__et«IF handleEvents», ifitem, generic_data__et«ENDIF»); next__et = «opScopePriv»enterHistory(«langExt.selfPointer(true)»next__et«IF usesHdlr», is_handler__et«ENDIF»); setState(«langExt.selfPointer(true)»next__et); «finalAction» } } } «ELSE» void «opScope»receiveEventInternal(«langExt.selfPointer(mc.className, handleEvents)»«IF handleEvents»«ifItemPtr» ifitem, int localId, int evt, «langExt.voidPointer» generic_data__et«ENDIF»); «ENDIF» «IF handleEvents» «IF generateImplementation» «publicIf»void «opScope»receiveEvent(«langExt.selfPointer(mc.className, true)»«ifItemPtr» ifitem, int evt, «langExt.voidPointer» generic_data__et) { int localId = (ifitem==«langExt.nullPointer»)? 0 : ifitem«getLocalId»; «opScope»receiveEventInternal(«langExt.selfPointer(true)»ifitem, localId, evt, generic_data__et); } «ELSE» void «opScope»receiveEvent(«langExt.selfPointer(true)»«ifItemPtr» ifitem, int evt, «langExt.voidPointer» generic_data__et); «ENDIF» «ENDIF» ''' } /** * generate the do code calls for a given state * * @param state the {@link State} * @return the generated code */ def public String genDoCodes(State state) {''' «IF state.hasDoCode(true)» «state.doCodeOperationName»(«langExt.selfPointer(false)»); «ENDIF» «IF state.eContainer.eContainer instanceof State» «genDoCodes(state.eContainer.eContainer as State)» «ENDIF» '''} /** * helper method which generates the state switch. * Asynchronous, data driven and event driven state machines are distinguished * * @param gc the {@link GraphContainer} * @param usesHdlr if the state machine uses no handler {@link TransitionPoint}s * at all then unused variables can be avoided by passing true * @return the generated code */ def public genStateSwitch(GraphContainer gc, boolean usesHdlr) { var async = gc.component.commType==ComponentCommunicationType::ASYNCHRONOUS var eventDriven = gc.component.commType==ComponentCommunicationType::EVENT_DRIVEN var dataDriven = gc.component.commType==ComponentCommunicationType::DATA_DRIVEN val allLeafStateNodes = gc.graph.allStateNodes.filter[isLeaf].toList.sortBy[(stateGraphNode as State).genStateId] ''' switch (getState(«langExt.selfPointer(false)»)) { «FOR stateNode : allLeafStateNodes» «val state = stateNode.stateGraphNode as State» case «state.genStateId»: «val caughtTriggers = stateNode.caughtTriggers» «IF async» «IF !caughtTriggers.isEmpty» switch(trigger__et) { case POLLING: «genDataDrivenTriggers(gc, stateNode, usesHdlr)» break; «genEventDrivenTriggers(gc, stateNode, usesHdlr)» } «ELSE» «genDataDrivenTriggers(gc, stateNode, usesHdlr)» «ENDIF» «ELSEIF dataDriven» «genDataDrivenTriggers(gc, stateNode, usesHdlr)» «ELSEIF eventDriven» «IF !caughtTriggers.isEmpty» switch(trigger__et) { «genEventDrivenTriggers(gc, stateNode, usesHdlr)» } «ENDIF» «ENDIF» break; «ENDFOR» default: /* should not occur */ break; } ''' } /** * helper method which generates the data driven triggers * * @param gc the {@link GraphContainer} * @param state the {@link State} for which the trigger if-else switch should be generated * @param usesHdlr if the state machine uses no handler {@link TransitionPoints} * at all then unused variables can be avoided by passing true * @return the generated code */ def public genDataDrivenTriggers(GraphContainer gc, Node stateNode, boolean usesHdlr) { val chainIDScope = if (langExt.usesInheritance) gc.className+langExt.scopeSeparator else "" val state = stateNode.stateGraphNode as State ''' «genDoCodes(state)» «var links = stateNode.getOutgoingLinksHierarchically.filter[transition instanceof GuardedTransition]» «FOR l : links» if («genGuardedTransitionGuard(l, "", gc)») { chain__et = «chainIDScope»«l.transition.genChainId»; catching_state__et = «l.transition.superState.genStateId»; «IF l.isHandler && usesHdlr» is_handler__et = TRUE; «ENDIF» } «IF l!=links.last» else «ENDIF» «ENDFOR» ''' } /** * helper method which generates the event driven triggers * * @param gc the {@link GraphContainer} * @param state the {@link State} for which the trigger switch should be generated * @param usesHdlr if the state machine uses no handler {@link TransitionPoints} * at all then unused variables can be avoided by passing true * @return the generated code */ def public genEventDrivenTriggers(GraphContainer gc, Node stateNode, boolean usesHdlr) { val caughtTriggers = new ArrayList(stateNode.caughtTriggers).sortBy[triggerCodeName] val chainIDScope = if (langExt.usesInheritance) gc.className+langExt.scopeSeparator else "" ''' «FOR ct : caughtTriggers» case «ct.triggerCodeName»: «var needData = ct.hasGuard» «IF needData»{ «langExt.getTypedDataDefinition(ct.msg)»«ENDIF» «FOR link : ct.links SEPARATOR " else "» «genTriggeredTransitionGuard(link, ct.trigger, gc)» { chain__et = «chainIDScope»«link.transition.genChainId»; catching_state__et = «link.transition.superState.genStateId»; «IF link.isHandler && usesHdlr» is_handler__et = «langExt.booleanConstant(true)»; «ENDIF» } «ENDFOR» «IF needData»}«ENDIF» break; «ENDFOR» default: /* should not occur */ break; ''' } def public getClassName(GraphContainer gc) { gc.component.className } def public getClassName(ModelComponent mc) { mc.componentName } def getTriggerCodeName(CommonTrigger tr) { val parts = tr.trigger.split(TriggerExtensions.TRIGGER_SEP) return "TRIG_"+parts.get(0)+"__"+parts.get(1) } /** * getter for history array * * @param state the ID of the history state * @return the generated code */ def public getHistory(String state) { langExt.memberAccess+"history["+state+"]" } /** * setter for history array * * @param state the ID of the state whose history should be set * @param historyState the ID of the state that should be assigned * @return the generated code */ def public setHistory(String state, String historyState) { langExt.memberAccess+"history["+state+"] = "+historyState } /** * @return the type of (temporary) state variables (defaults to "int") * and has to be signed */ def public stateType() { "int" } /** * allow target language dependent generation of unreachable return in generated enterHistory method. * The default is just a comment. * @return the generated code */ def public unreachableReturn() { "/* return NO_STATE; // required by CDT but detected as unreachable by JDT because of while (true) */" } /** * type of (temporary) boolean variables (defaults to "boolean") * @return the generated code */ def public boolType() { return "boolean" } /** * empty, but may be overridden */ def public finalAction() { '''''' } /** * the type of the interface item passed into the receiveEvent() method */ def public interfaceItemType() { "InterfaceItemBase" } /** * empty, but may be overridden */ def markVariableUsed(String varname) { '''''' } /** * helper method to determine whether this state machine uses handler transitions * points at all * * @param xpax the {@link GraphContainer} * @return true if the state machine uses handler transition points */ def public usesHandlerTrPoints(GraphContainer gc) { !gc.graph.allTransitionPointNodes.filter(t|((t.stateGraphNode as TransitionPoint).handler)).empty } def public String genTriggeredTransitionGuard(Link link, String trigger, GraphContainer mc) def public String genGuardedTransitionGuard(Link link, String trigger, GraphContainer mc) def public String genActionCodeMethod(GraphContainer gc, Link link, boolean generateImplementation) def public String genActionCodeMethods(GraphContainer gc, Node node, boolean generateImplementation) }