diff --git a/autocoder/Stars.py b/autocoder/Stars.py index b55e5d6..329707a 100755 --- a/autocoder/Stars.py +++ b/autocoder/Stars.py @@ -187,7 +187,7 @@ def getQmRoot(modelFileName: str) -> Tuple[ElementTreeType, XmiModel] : cppcoder.generateCode(xmiModel, args.noImpl) if args.backend == "c": - ccoder.generateCode(qmRoot, args.noImpl) + ccoder.generateCode(xmiModel, args.noImpl) if args.backend == "qf": qfcoder.generateCode(xmiModel, args.noImpl, args.noSignals) diff --git a/autocoder/c_backend/ccoder.py b/autocoder/c_backend/ccoder.py index 0607041..4faa88c 100644 --- a/autocoder/c_backend/ccoder.py +++ b/autocoder/c_backend/ccoder.py @@ -17,9 +17,12 @@ from c_backend.ctemplates import CTemplate from c_backend.cUnitTestTemplates import CUnitTestTemplate from c_backend.cImplTemplates import CImplTemplate -from typing import List, Dict, Tuple, Any, Optional, IO +from typing import List, Dict, Tuple, Any, Optional, IO, TextIO from qmlib import ElementTreeType from lxml import etree +import re +from xmiModelApi import XmiModel +from anytree import Node, PreOrderIter # Initialize global variables @@ -32,23 +35,90 @@ # # Print the state-machine header file # ----------------------------------------------------------------------- -def printSmHeader(smname: str, root: ElementTreeType): +def printSmHeader(xmiModel: XmiModel): + stateMachine = xmiModel.tree.stateMachine - hFile = open(smname + ".h", "w") + hFile = open(f"{stateMachine}.h", "w") - eventList = [] - trans = root.iter('tran') - for tran in trans: - event = tran.get('trig').upper() + "_SIG" - if event not in eventList: - eventList.append(event) + states = list() + (actions, guards, signals) = getStateMachineMethods(xmiModel) + + #trans = root.iter('tran') + + #print(f"signals {signals}") + + isSuperstate = False + + for child in PreOrderIter(xmiModel.tree): + if child.name == "STATE": + for grandchild in PreOrderIter(child): + # if the node is a superstate, do not add it to the state list + if (grandchild.name == "STATE"): + if (child.stateName != grandchild.stateName): + isSuperstate = True + break + if (not isSuperstate): + states.append(child.stateName) + + isSuperstate = False + + signals = {signal.upper() + "_SIG" for signal in signals} - stateList = [] - states = root.iter('state') - for state in states: - stateList.append(state.get('name')) + actions = sorted(actions) + guards = sorted(guards) + signals = sorted(signals) + + #eventList = [] + #trans = root.iter('tran') + #for tran in trans: + #event = tran.get('trig').upper() + "_SIG" + #if event not in eventList: + #eventList.append(event) + + #stateList = [] + #states = root.iter('state') + #for state in states: + #stateList.append(state.get('name')) - hFile.write(codeTemplate.fileHeader(smname, stateList, eventList)) + hFile.write(codeTemplate.fileHeader(stateMachine, states, signals)) + +def getStateMachineMethods(xmiModel: XmiModel): + + actionSet = set() + guardSet = set() + signalSet = set() + + for child in PreOrderIter(xmiModel.tree): + #print(child.name) + if child.name == "STATE": + #actionSet.add(getActionNames(child.entry, True)) + actionSet.add(child.entry) + actionSet.add(child.exit) + if child.name == "TRANSITION": + actionSet.add(child.action) + guardSet.add(getActionNames(child.guard, False)) + if (child.event != None): + #print(f"{child.name} - {child} - {child.event}") + signalSet.add((child.event)) + if child.name == "JUNCTION": + actionSet.add(child.ifAction) + actionSet.add(child.elseAction) + guardSet.add(child.guard) + if child.name == "INITIAL": + actionSet.add(child.action) + + # Remove empty strings + actionSet = {item for item in actionSet if item} + guardSet = {item for item in guardSet if item} + signalSet = {item for item in signalSet if item} + + #for item in guardSet: + #print("item: " + str(item)) + + flatActions = {a.strip() for action in actionSet for a in action.split(',')} + + + return (flatActions, guardSet, signalSet) # --------------------------------------------------------------------------- @@ -58,6 +128,38 @@ def printSmHeader(smname: str, root: ElementTreeType): # --------------------------------------------------------------------------- def formatTarget(targ: str) -> str: return codeTemplate.target(targ) + +def getActionNames(input_string: str, fullSpecifier: bool): + if input_string is None: + return None + + # Use regex to find all procedural names before the '(' and ignore everything after + procedural_names = re.findall(r'\b\w+(?=\()', input_string) + # Join the names with commas + output_string = ', '.join(procedural_names) + + if fullSpecifier: + output_string = output_string + getActionDataType(input_string) + + return output_string + +def getActionDataType(inputString: str): + if inputString is None: + return "" + + outputString = None + + # Get this index of the opening and closing parenthesis for function parameter list + start = inputString.index('(') + 1 + end = inputString.index(')') + + # If there is any character between the parenthesis, treat it as a FPP datatype + if (start != end): + outputString = (": " + inputString[slice(start,end)]) + else: + outputString = "" + + return outputString # --------------------------------------------------------------------------- # printTransition @@ -117,7 +219,33 @@ def printTransition(smname: str, tran: ElementTreeType) -> List[str]: return rstr - +def getStates(xmiModel: XmiModel): + states = list() + + isSuperstate = False + + for child in PreOrderIter(xmiModel.tree): + if child.name == "STATE": + for grandchild in PreOrderIter(child): + # if the node is a superstate, do not add it to the state list + if (grandchild.name == "STATE"): + if (child.stateName != grandchild.stateName): + isSuperstate = True + break + if (not isSuperstate): + states.append(child.stateName) + + isSuperstate = False + + return states + +def resolveTransition(xmiModel: XmiModel, node: Node, states: List): + if xmiModel.idMap[node.target].stateName in states: + return xmiModel.idMap[node.target].stateName + else: + for child in xmiModel.idMap[node.target].children: + if child.name == "INITIAL": + return xmiModel.idMap[child.target].stateName # --------------------------------------------------------------------------- # printStateTransition @@ -135,20 +263,41 @@ def printStateTransition(smname: str, tran: ElementTreeType, cFile: IO): # # Print the state-machine C file # ----------------------------------------------------------------------- -def printSmCode(smname: str, root: ElementTreeType): +def printSmCode(smname: str, xmiModel: XmiModel, cFile: TextIO, level = 1): + stateMachine = xmiModel.tree.stateMachine + + states = getStates(xmiModel) + + defaultIndent = " " + + indent = defaultIndent * level - cFile = open(smname + ".c", "w") + #cFile = open(smname + ".c", "w") - initialTran = root.find('initial') - initialCode = qmlib.format_C(printTransition(smname, initialTran), 4) - cFile.write(codeTemplate.stateMachineInit(smname, initialCode)) + #initialTran = root.find('initial') + #initialCode = qmlib.format_C(printTransition(smname, initialTran), 4) + #cFile.write(codeTemplate.stateMachineInit(smname, initialCode)) - states = root.iter("state") + #states = root.iter("state") for state in states: - cFile.write(codeTemplate.stateMachineState(state.get('name'))) - trans = state.findall('tran') - for tran in trans: - printStateTransition(smname, tran, cFile) + #cFile.write(codeTemplate.stateMachineState(state.get('name'))) + cFile.write(codeTemplate.stateMachineState(state)) + + #trans = state.findall('tran') + #for tran in trans: + #printStateTransition(smname, tran, cFile) + + for child in PreOrderIter(xmiModel.tree): + if child.name == "TRANSITION": + #print(xmiModel.idMap[child.source]) + if (xmiModel.idMap[child.source].stateName == state): + #print(f"State match with {state}") + guardExpr = f" if {getActionNames(child.guard, False)}" if child.guard else "" + transition = f"\n{indent}self->sm.state = {resolveTransition(xmiModel, child, states)};" if child.kind is None else "" + action = f"\n{indent}{stateMachine}Impl_{getActionNames(child.action, False)}(self);" if child.action else "" + + if (action != "" or transition != ""): + cFile.write(f"{defaultIndent}case {child.event.upper() + "_SIG: "}{guardExpr}{action}{transition}\n{indent}break;\n") cFile.write(codeTemplate.stateMachineBreak()) @@ -275,34 +424,77 @@ def printImplCode(smname: str, root: ElementTreeType): # everything else is only important for the tool developer. # ----------------------------------------------------------------------- -def generateCode(qmRoot: ElementTreeType, noImpl: bool): +def generateCode(xmiModel: XmiModel, noImpl: bool): global codeTemplate global unitTestTemplate global codeImplTemplate - qmRoot, smname = qmlib.get_state_machine(qmRoot) + stateMachine = xmiModel.tree.stateMachine + + currentNode = xmiModel.tree + + #qmRoot, smname = qmlib.get_state_machine(qmRoot) + + print ("Generating flat C code for {0}".format(stateMachine)) - print ("Generating flat C code for {0}".format(smname)) + #flatchart : ElementTreeType = flatt.flatten_state_machine(qmRoot) - flatchart : ElementTreeType = flatt.flatten_state_machine(qmRoot) + xmiModel.getInitTransitions() + + xmiModel.getJunctions() + + (actions, guards, signals) = getStateMachineMethods(xmiModel) + + xmiModel.moveTransitions() + + #xmiModel.print() + + cFile = open(f"{stateMachine}.c", "w") if noImpl == False: # Generate the Impl files - print ("Generating " + smname + "Impl.c") - print ("Generating " + smname + "Impl.h") - printImplCode(smname, flatchart) + print ("Generating " + stateMachine + "Impl.c") + print ("Generating " + stateMachine + "Impl.h") + #printImplCode(StateMachine, flatchart) # Generate the unit test files print ("Generating main.c") print ("Generating sendEvent.h") print ("Generating sendEvent.c") - printUnitCode(smname, flatchart) + #printUnitCode(smname, flatchart) # Generate the header file - print ("Generating " + smname + ".c") - printSmHeader(smname, flatchart) + print ("Generating " + stateMachine + ".c") + printSmHeader(xmiModel) + + initialCode = str() + target = str() + + for child in currentNode.children: + #print(f"{child.name}") + + if child.name == "INITIAL": + #print(child.target) + target = xmiModel.idMap[child.target].stateName + + for child in currentNode.children: + if (child.name == "STATE"): + #print(child.stateName) + + if (child.stateName == target): + #print("node found") + if child.entry: + initialCode = " " + stateMachine + "Impl_" + child.entry + ";" + + initialCode = initialCode + "\n self->sm.state = " + target + ";" + + break + + xmiModel.flattenModel() + + cFile.write(codeTemplate.stateMachineInit(stateMachine, initialCode)) # Generate the C file - print ("Generating " + smname + ".h") - printSmCode(smname, flatchart) + print ("Generating " + stateMachine + ".h") + printSmCode(stateMachine, xmiModel, cFile) diff --git a/autocoder/cpp_backend/cppcoder.py b/autocoder/cpp_backend/cppcoder.py index 21f3c5a..72b01a8 100644 --- a/autocoder/cpp_backend/cppcoder.py +++ b/autocoder/cpp_backend/cppcoder.py @@ -94,22 +94,6 @@ def printSmHeader(xmiModel: XmiModel): hFile.write(codeTemplate.fileHeader(stateMachine, states, signals, actions)) -# ----------------------------------------------------------------------- -# getInitTranstions -# -# Update the xmi model to add Initial Transitions from Transitions -# ----------------------------------------------------------------------- -def getInitTransitions(xmiModel: XmiModel): - - psuedoStateList = xmiModel.psuedoStateList - transTargetSet = xmiModel.transTargets - - for trans in PreOrderIter(xmiModel.tree): - if trans.name == "TRANSITION": - # If the transition source is a psuedostate and no other transition goes into that psuedostate - if (trans.source in psuedoStateList) and (trans.source not in transTargetSet): - xmiModel.addInitial(trans) - def getActionNames(input_string: str, fullSpecifier: bool): if input_string is None: return None @@ -250,46 +234,6 @@ def printTransition(smname: str, tran: ElementTreeType): #print(f"transition {rstr}") return rstr - -# ----------------------------------------------------------------------- -# getJunctions -# -# Update the xmi model to add Junctions -# ----------------------------------------------------------------------- -def getJunctions(xmiModel: XmiModel): - - for ps in PreOrderIter(xmiModel.tree): - if ps.name == "PSUEDOSTATE": - psId = ps.id - transList = [] - for child in PreOrderIter(xmiModel.tree): - # Get the transitions that exit this psuedo state - if child.name == "TRANSITION": - if psId == child.source: - transList.append(child) - if len(transList) == 2: - xmiModel.addJunction(transList, ps) - -# ----------------------------------------------------------------------- -# moveTransitions -# -# Transitions that start from a state are to be moved under that state -# ----------------------------------------------------------------------- -def moveTransitions(xmiModel: XmiModel): - for child in PreOrderIter(xmiModel.tree): - if child.name == "TRANSITION": - # Look up where this transition is supposed to go - state = xmiModel.idMap[child.source] - # Move the transition under the source state - xmiModel.moveTransition(child, state) - - if child.name == "JUNCTION": - for sourceTransition in PreOrderIter(xmiModel.tree): - if (sourceTransition.name == "TRANSITION") and (sourceTransition.target == child.id): - #state = xmiModel.idMap[parentState.source] - child.parent = sourceTransition.parent.parent - # Move the transition under the source state - # --------------------------------------------------------------------------- # printStateTransition @@ -355,13 +299,13 @@ def printSmCode(node: Node, xmiModel: XmiModel, cFile: TextIO, level: int = 4): if child.name == "TRANSITION": #print(xmiModel.idMap[child.source]) if (xmiModel.idMap[child.source].stateName == state): - print(f"State match with {state}") + #print(f"State match with {state}") guardExpr = f" if {getActionNames(child.guard, False)}" if child.guard else "" transition = f"\n{indent}this->state = {resolveTransition(xmiModel, child, states)};" if child.kind is None else "" action = f"\n{indent}{getActionNames(child.action, False)}();" if child.action else "" if (action != "" or transition != ""): - cFile.write(f"{defaultIndent}case {child.event.upper() + "_SIG:"}{guardExpr}{action}{transition}\n{indent}break;\n") + cFile.write(f"{defaultIndent}case {child.event.upper() + "_SIG: "}{guardExpr}{action}{transition}\n{indent}break;\n") cFile.write(codeTemplate.stateMachineBreak()) @@ -484,15 +428,15 @@ def generateCode(xmiModel: XmiModel, noImpl: bool): cFile = open(f"{stateMachine}.cpp", "w") - getInitTransitions(xmiModel) + xmiModel.getInitTransitions() - getJunctions(xmiModel) + xmiModel.getJunctions() (actions, guards, signals) = getStateMachineMethods(xmiModel) - moveTransitions(xmiModel) + xmiModel.moveTransitions() - xmiModel.print() + #xmiModel.print() #initialCode = qmlib.format_C(printTransition(stateMachine, currentNode), 4) #cFile.write(codeTemplate.stateMachineInit(stateMachine, initialCode)) @@ -543,7 +487,7 @@ def generateCode(xmiModel: XmiModel, noImpl: bool): xmiModel.flattenModel() - xmiModel.print() + #xmiModel.print() #initialCode = qmlib.format_C(printTransition(stateMachine, Node), 4) cFile.write(codeTemplate.stateMachineInit(stateMachine, initialCode, "")) diff --git a/autocoder/fprime_backend/fppcoder.py b/autocoder/fprime_backend/fppcoder.py index ec72109..3b733e1 100644 --- a/autocoder/fprime_backend/fppcoder.py +++ b/autocoder/fprime_backend/fppcoder.py @@ -87,62 +87,6 @@ def processNode(node: Node, fppFile.write(f"{indent}{exitExpr}\n") processNode(child, xmiModel, fppFile, level+1) fppFile.write(f"{indent}}}\n\n") - -# ----------------------------------------------------------------------- -# getInitTranstions -# -# Update the xmi model to add Initial Transitions from Transitions -# ----------------------------------------------------------------------- -def getInitTransitions(xmiModel: XmiModel): - - psuedoStateList = xmiModel.psuedoStateList - transTargetSet = xmiModel.transTargets - - for trans in PreOrderIter(xmiModel.tree): - if trans.name == "TRANSITION": - # If the transition source is a psuedostate and no other transition goes into that psuedostate - if (trans.source in psuedoStateList) and (trans.source not in transTargetSet): - xmiModel.addInitial(trans) - -# ----------------------------------------------------------------------- -# getJunctions -# -# Update the xmi model to add Junctions -# ----------------------------------------------------------------------- -def getJunctions(xmiModel: XmiModel): - - for ps in PreOrderIter(xmiModel.tree): - if ps.name == "PSUEDOSTATE": - psId = ps.id - transList = [] - for child in PreOrderIter(xmiModel.tree): - # Get the transitions that exit this psuedo state - if child.name == "TRANSITION": - if psId == child.source: - transList.append(child) - if len(transList) == 2: - xmiModel.addJunction(transList, ps) - -# ----------------------------------------------------------------------- -# moveTransitions -# -# Transitions that start from a state are to be moved under that state -# ----------------------------------------------------------------------- -def moveTransitions(xmiModel: XmiModel): - for child in PreOrderIter(xmiModel.tree): - if child.name == "TRANSITION": - # Look up where this transition is supposed to go - state = xmiModel.idMap[child.source] - # Move the transition under the source state - xmiModel.moveTransition(child, state) - - if child.name == "JUNCTION": - for sourceTransition in PreOrderIter(xmiModel.tree): - if (sourceTransition.name == "TRANSITION") and (sourceTransition.target == child.id): - #state = xmiModel.idMap[parentState.source] - child.parent = sourceTransition.parent.parent - # Move the transition under the source state - def getStateMachineMethods(xmiModel: XmiModel): @@ -196,15 +140,15 @@ def generateCode(xmiModel: XmiModel): currentNode = xmiModel.tree - getInitTransitions(xmiModel) + xmiModel.getInitTransitions() - getJunctions(xmiModel) + xmiModel.getJunctions() (actions, guards, signals) = getStateMachineMethods(xmiModel) - moveTransitions(xmiModel) + xmiModel.moveTransitions() - xmiModel.print() + #xmiModel.print() fppFile.write(f"state machine {xmiModel.tree.stateMachine} {{\n\n") diff --git a/autocoder/xmiModelApi.py b/autocoder/xmiModelApi.py index dd0f11b..3d6e4de 100755 --- a/autocoder/xmiModelApi.py +++ b/autocoder/xmiModelApi.py @@ -151,6 +151,56 @@ def __init__(self, source, target, guard, action): thisList = thisList + self.getTransitionsList(node) return thisList + + # ----------------------------------------------------------------------- + # getInitTranstions + # + # Update the xmi model to add Initial Transitions from Transitions + # ----------------------------------------------------------------------- + def getInitTransitions(self): + for trans in PreOrderIter(self.tree): + if trans.name == "TRANSITION": + # If the transition source is a psuedostate and no other transition goes into that psuedostate + if (trans.source in self.psuedoStateList) and (trans.source not in self.transTargets): + self.addInitial(trans) + + # ----------------------------------------------------------------------- + # getJunctions + # + # Update the xmi model to add Junctions + # ----------------------------------------------------------------------- + def getJunctions(self): + for ps in PreOrderIter(self.tree): + if ps.name == "PSUEDOSTATE": + psId = ps.id + transList = [] + for child in PreOrderIter(self.tree): + # Get the transitions that exit this psuedo state + if child.name == "TRANSITION": + if psId == child.source: + transList.append(child) + if len(transList) == 2: + self.addJunction(transList, ps) + + # ----------------------------------------------------------------------- + # moveTransitions + # + # Transitions that start from a state are to be moved under that state + # ----------------------------------------------------------------------- + def moveTransitions(self): + for child in PreOrderIter(self.tree): + if child.name == "TRANSITION": + # Look up where this transition is supposed to go + state = self.idMap[child.source] + # Move the transition under the source state + self.moveTransition(child, state) + + if child.name == "JUNCTION": + for sourceTransition in PreOrderIter(self.tree): + if (sourceTransition.name == "TRANSITION") and (sourceTransition.target == child.id): + #state = xmiModel.idMap[parentState.source] + child.parent = sourceTransition.parent.parent + # Move the transition under the source state # -------------------------------------------------------- # getPsuedoStateList @@ -204,7 +254,7 @@ def flattenModel(self): if (self.isSuperstate(child)): self.flattenSuperstate(child, flattenedTransitions) - print("break loop") + #print("break loop") break @@ -219,11 +269,11 @@ def flattenSuperstate(self, superstate: Node, flattenedTransitions: set): flattenedTransitions.add(child) if child.name == "STATE": if (self.isSuperstate(child)): - print(f"{superstate.stateName} is a superstate. Moving transitions.") + #print(f"{superstate.stateName} is a superstate. Moving transitions.") self.flattenSuperstate(child, flattenedTransitions) else: - print(f"{child.stateName} is innermost state of {superstate.stateName}") + #print(f"{child.stateName} is innermost state of {superstate.stateName}") for transition in superstate.children: if transition.name == "TRANSITION": @@ -237,7 +287,7 @@ def flattenSuperstate(self, superstate: Node, flattenedTransitions: set): #self.resolveSuperstateTransitions(initial) - print(f"Removing superstate: {superstate.stateName}") + #print(f"Removing superstate: {superstate.stateName}") superstate.parent = None @@ -280,7 +330,7 @@ def isSuperstate(self, node: Node): if node.name == "STATE": for child in node.children: if child.name == "STATE": - print(f"Substate of {node.stateName} is {child.stateName}") + #print(f"Substate of {node.stateName} is {child.stateName}") return True else: