Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 97 additions & 21 deletions autocoder/fprime_backend/fppcoder.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,11 @@ def format_funcs(s: str, fullSpecifier: bool) -> str:
continue

name, arg = m.groups()
escaped_name = escape_fpp_keyword(name)
if arg and fullSpecifier:
result.append(f"{name}: {arg}")
result.append(f"{escaped_name}: {arg}")
else:
result.append(name)
result.append(escaped_name)

return ", ".join(result)

Expand Down Expand Up @@ -61,6 +62,72 @@ def getActionDataType(inputString: str):

return outputString

# -----------------------------------------------------------------------
# escape_fpp_keyword
#
# Escape FPP reserved keywords to avoid syntax errors.
# -----------------------------------------------------------------------
def escape_fpp_keyword(name: str) -> str:
"""
Escape FPP reserved keywords by appending '_state' suffix.
FPP reserved keywords are case-sensitive and include: on, state, initial,
choice, if, else, do, enter, exit, entry, signal, action, guard, etc.
"""
fpp_keywords = {
'on', 'state', 'initial', 'choice', 'if', 'else',
'do', 'enter', 'exit', 'entry', 'signal', 'action', 'guard'
}

if name in fpp_keywords:
return f"{name}_state"
return name

# -----------------------------------------------------------------------
# get_qualified_target
#
# Get the qualified target name for a transition.
# Returns just the stateName if target is at same level or up the hierarchy,
# otherwise returns qualified path (e.g., Parent.Child) when going down.
# -----------------------------------------------------------------------
def get_qualified_target(source_node: Node, target_id, xmiModel: XmiModel) -> str:
target_node = xmiModel.idMap[target_id]
target_name = target_node.stateName

# Build list of ancestors from source to root
source_ancestors = []
node = source_node
while node is not None:
source_ancestors.append(node)
node = node.parent

# Build list of ancestors from target to root
target_ancestors = []
node = target_node
while node is not None:
target_ancestors.append(node)
node = node.parent

# Find common ancestor
common_ancestor = None
for s_anc in source_ancestors:
if s_anc in target_ancestors:
common_ancestor = s_anc
break

# Build qualified path from common ancestor down to target
path = []
node = target_node
while node != common_ancestor and node is not None:
if hasattr(node, 'stateName'):
path.insert(0, escape_fpp_keyword(node.stateName))
node = node.parent

# If path has more than one element, we need qualified notation
if len(path) > 1:
return ".".join(path)
else:
return escape_fpp_keyword(target_name)

# -----------------------------------------------------------------------
# processNode
#
Expand All @@ -81,20 +148,13 @@ def processNode(node: Node,

# Process INITIAL children (typically only one, maintain order)
for child in initials:
target = xmiModel.idMap[child.target].stateName
qualified_target = get_qualified_target(node, child.target, xmiModel)
doExpr = f" do {{ {format_funcs(child.action, False)} }}" if child.action else ""
fppFile.write(f"{indent}initial{doExpr} enter {target}\n")
fppFile.write(f"{indent}initial{doExpr} enter {qualified_target}\n")

# Process TRANSITION children in sorted order by event name for consistency
for child in sorted(transitions, key=lambda t: t.event if t.event else ""):
guardExpr = f" if {format_funcs(child.guard, False)}" if child.guard else ""
enterExpr = f" enter {xmiModel.idMap[child.target].stateName}" if child.kind is None else ""
doExpr = f" do {{ {format_funcs(child.action, False)} }}" if child.action else ""
fppFile.write(f"{indent}on {child.event}{guardExpr}{doExpr}{enterExpr}\n")

# Process STATE children in sorted (alphabetical) order for consistency
# Process STATE children first (in sorted alphabetical order) to avoid forward references
for child in sorted(states, key=lambda s: s.stateName):
stateName = child.stateName
stateName = escape_fpp_keyword(child.stateName)
enterExpr = f" entry do {{ {format_funcs(child.entry, False)} }}" if child.entry else ""
exitExpr = f" exit do {{ {format_funcs(child.exit, False)} }}" if child.exit else ""
fppFile.write(f"{indent}state {stateName} {{\n")
Expand All @@ -105,14 +165,27 @@ def processNode(node: Node,
processNode(child, xmiModel, fppFile, level+1)
fppFile.write(f"{indent}}}\n\n")

# Process TRANSITION children after states (in sorted order by event name for consistency)
for child in sorted(transitions, key=lambda t: t.event if t.event else ""):
guardExpr = f" if {format_funcs(child.guard, False)}" if child.guard else ""
if child.kind is None:
qualified_target = get_qualified_target(node, child.target, xmiModel)
enterExpr = f" enter {qualified_target}"
else:
enterExpr = ""
doExpr = f" do {{ {format_funcs(child.action, False)} }}" if child.action else ""
fppFile.write(f"{indent}on {child.event}{guardExpr}{doExpr}{enterExpr}\n")

# Process JUNCTION children last (maintain order, typically referenced by transitions)
for child in junctions:
ifTarget = xmiModel.idMap[child.ifTarget].stateName
elseTarget = xmiModel.idMap[child.elseTarget].stateName
ifTarget = get_qualified_target(node, child.ifTarget, xmiModel)
elseTarget = get_qualified_target(node, child.elseTarget, xmiModel)
doIfExpr = f" do {{ {format_funcs(child.ifAction, False)} }}" if child.ifAction else ""
doElseExpr = f" do {{ {format_funcs(child.elseAction, False)} }}" if child.elseAction else ""
fppFile.write(f"{indent}choice {child.stateName} {{\n")
fppFile.write(f"{indent} if {child.guard}{doIfExpr} enter {ifTarget} else{doElseExpr} enter {elseTarget}\n")
guardExpr = format_funcs(child.guard, False) if child.guard else child.guard
junctionName = escape_fpp_keyword(child.stateName)
fppFile.write(f"{indent}choice {junctionName} {{\n")
fppFile.write(f"{indent} if {guardExpr}{doIfExpr} enter {ifTarget} else{doElseExpr} enter {elseTarget}\n")
fppFile.write(f"{indent}}}\n")

# -----------------------------------------------------------------------
Expand Down Expand Up @@ -189,7 +262,7 @@ def getStateMachineMethods(xmiModel: XmiModel):
if child.name == "JUNCTION":
actionSet.add(format_funcs(child.ifAction, True))
actionSet.add(format_funcs(child.elseAction, True))
guardSet.add(child.guard)
guardSet.add(format_funcs(child.guard, False))
if child.name == "INITIAL":
actionSet.add(format_funcs(child.action, True))

Expand Down Expand Up @@ -234,15 +307,18 @@ def generateCode(xmiModel: XmiModel):
fppFile.write(f"state machine {xmiModel.tree.stateMachine} {{\n\n")

for action in sorted(actions):
fppFile.write(f" action {action}\n")
escaped_action = escape_fpp_keyword(action.split(':')[0].strip()) + (f": {action.split(':', 1)[1].strip()}" if ':' in action else "")
fppFile.write(f" action {escaped_action}\n")
fppFile.write("\n")

for guard in sorted(guards):
fppFile.write(f" guard {guard}\n")
escaped_guard = escape_fpp_keyword(guard)
fppFile.write(f" guard {escaped_guard}\n")
fppFile.write("\n")

for signal in sorted(signals):
fppFile.write(f" signal {signal}\n")
escaped_signal = escape_fpp_keyword(signal.split(':')[0].strip()) + (f": {signal.split(':', 1)[1].strip()}" if ':' in signal else "")
fppFile.write(f" signal {escaped_signal}\n")
fppFile.write("\n")

processNode(currentNode, xmiModel, fppFile, 1)
Expand Down
30 changes: 30 additions & 0 deletions debug/cpp/Simple.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@

#ifndef SIMPLE_H_
#define SIMPLE_H_

// forward declaration of EventSignal
struct EventSignal;

class Simple {
public:

enum SimpleStates {
S1,
S2,
};

enum SimpleEvents {
EV1_SIG,
};

enum SimpleStates state;

void init();
void update(EventSignal *e);

// state machine implementation functions
void s1Entry();

};

#endif
13 changes: 13 additions & 0 deletions debug/cpp/sendEvent.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@

#ifndef _SEND_EVENT_H
#define _SEND_EVENT_H

class Simple;

typedef struct EventSignal {
unsigned int sig;
} EventSignal;

void sendEvent_send(unsigned int signal);

#endif
7 changes: 7 additions & 0 deletions debug/cpp/testDrv.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@

#ifndef _TEST_DRV_H_
#define _TEST_DRV_H_

void testDrv();

#endif
14 changes: 0 additions & 14 deletions models/TestModels/Actions/README.adoc
Original file line number Diff line number Diff line change
@@ -1,16 +1,2 @@

image::Actions.png[]

* Run the autocoder and build the executable model for C and QF
** make

* Run the unit tests for C and QF
** make ut

* Run the autocoder and build executable model for C
** make C

* Run the autocoder and build executable model for QF
** make QF


2 changes: 1 addition & 1 deletion models/TestModels/Arg_Actions/.Arg_Actions
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<item name="backups">0</item>
</group>
<group name="windows">
<item id="package1::Arg_Actions::SM">0,0,1274,925,*</item>
<item id="package1::Arg_Actions::SM">0,0,2254,1340,*</item>
</group>
<group name="search">
<item name="options">2032128</item>
Expand Down
14 changes: 0 additions & 14 deletions models/TestModels/Arg_Actions/README.adoc
Original file line number Diff line number Diff line change
@@ -1,16 +1,2 @@

image::Arg_Actions.png[]

* Run the autocoder and build the executable model for C and QF
** make

* Run the unit tests for C and QF
** make ut

* Run the autocoder and build executable model for C
** make C

* Run the autocoder and build executable model for QF
** make QF


20 changes: 20 additions & 0 deletions models/TestModels/Arg_Actions_FP/Arg_Actions_FP.plantuml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@

@startuml

[*] --> S1

state S1 {
S1:Entry: s1Entry(); s1Entry2(); foo()
S1:Exit: s1Exit(); s1Exit2(); foo()
}

state S2 {
S2:Entry: s2Entry(); s2Entry2(); foo()
S2:Exit: s2Exit(); s2Exit2()
}

S1 --> S2: EV1[g1(U16)]
S1 --> S2: EV2[g2()]
S2 --> S1: EV1/a1(U16); a2(); foo()

@enduml
14 changes: 14 additions & 0 deletions models/TestModels/Arg_Actions_FP/testDrv.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
g1 = false
EV1

g1 = true
EV1

EV1

g2 = false
EV2

g2 = true
EV2

15 changes: 0 additions & 15 deletions models/TestModels/Cameo/README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -52,18 +52,3 @@ StateS1 --> Running: Ev2/a2()
....

image::CameoUML.png[]


* Run the autocoder and build the executable model for C and QF
** make

* Run the unit tests for C and QF
** make ut

* Run the autocoder and build executable model for C
** make C

* Run the autocoder and build executable model for QF
** make QF


61 changes: 0 additions & 61 deletions models/TestModels/Cases/.Cases

This file was deleted.

Loading
Loading