robotransform 0.0.6__py3-none-any.whl → 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
robotransform/__init__.py CHANGED
@@ -1,7 +1,7 @@
1
- from robotransform.main import Store, MapleKStore, dump_messages, dump_logical
1
+ from robotransform.main import Store, MapleKStore
2
+ from robotransform.convert import dump_aadl
2
3
  __all__ = (
3
4
  "Store",
4
5
  "MapleKStore",
5
- "dump_messages",
6
- "dump_logical",
6
+ "dump_aadl",
7
7
  )
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env python3
2
+
3
+ import argparse
4
+ from pathlib import Path
5
+
6
+ import arklog
7
+
8
+ from robotransform import Store, dump_aadl
9
+
10
+
11
+ def main():
12
+ parser = argparse.ArgumentParser(description="RoboTransform CLI.")
13
+ parser.add_argument(
14
+ "path",
15
+ type=Path,
16
+ help="Path to a rct file."
17
+ )
18
+ args = parser.parse_args()
19
+
20
+ if args.path.exists():
21
+ arklog.debug(f"Path exists: ({args.path}).")
22
+ store = Store((args.path,))
23
+ arklog.info(dump_aadl(store))
24
+ else:
25
+ arklog.debug(f"Path does not exist: ({args.path.resolve()}).")
26
+
27
+
28
+ if __name__ == "__main__":
29
+ main()
robotransform/aadl.py ADDED
@@ -0,0 +1,198 @@
1
+ from __future__ import annotations
2
+ from dataclasses import dataclass, field
3
+ from typing import Dict, List, Optional, Literal
4
+
5
+ ComponentCategory = Literal[
6
+ "system", "process", "thread", "data", "processor", "bus", "device",
7
+ "memory", "virtual_processor", "virtual_bus"
8
+ ]
9
+
10
+ FeatureDirection = Literal["in", "out", "in out"]
11
+ PortCategory = Literal["event", "event_data", "data"]
12
+
13
+
14
+ @dataclass
15
+ class Mode:
16
+ name: str
17
+ initial: bool = False
18
+
19
+ def to_aadl(self, indent: str = "") -> str:
20
+ initial = "initial " if self.initial else ""
21
+ lines = [f"{indent}{self.name}: {initial}mode;"]
22
+ return "\n".join(lines)
23
+
24
+
25
+ @dataclass
26
+ class Port:
27
+ name: str
28
+ direction: FeatureDirection
29
+ port_category: PortCategory = "data"
30
+ data_type: Optional[str] = None
31
+
32
+ def to_aadl(self, indent: str = "") -> str:
33
+ port_type = "data port"
34
+ if self.port_category == "event":
35
+ port_type = "event port"
36
+ elif self.port_category == "event_data":
37
+ port_type = "event data port"
38
+ dt = f" {self.data_type}" if self.data_type else ""
39
+ return f"{indent}{self.name}: {self.direction} {port_type}{dt};"
40
+
41
+
42
+ @dataclass
43
+ class Subcomponent:
44
+ name: str
45
+ category: ComponentCategory
46
+ component_type: str
47
+ component_implementation: Optional[str] = None
48
+
49
+ def to_aadl(self, indent: str = "") -> str:
50
+ impl_part = f".{self.component_implementation}" if self.component_implementation else ""
51
+ return f"{indent}{self.name}: {self.category} {self.component_type}{impl_part};"
52
+
53
+
54
+ @dataclass
55
+ class Connection:
56
+ name: str
57
+ source: str
58
+ destination: str
59
+ bidirectional: bool = False
60
+
61
+ def to_aadl(self, indent: str = "") -> str:
62
+ arrow = "<->" if self.bidirectional else "->"
63
+ return f"{indent}{self.name}: {self.source} {arrow} {self.destination};"
64
+
65
+
66
+ @dataclass
67
+ class ComponentType:
68
+ name: str
69
+ category: ComponentCategory
70
+ features: Dict[str, Port] = field(default_factory=dict)
71
+ modes: List[Mode] = field(default_factory=list)
72
+
73
+ def to_aadl(self, indent: str = "") -> str:
74
+ lines = [f"{indent}{self.category} {self.name}"]
75
+ if self.features:
76
+ lines.append(f"{indent} features")
77
+ for f in self.features.values():
78
+ lines.append(f.to_aadl(indent + " "))
79
+ if self.modes:
80
+ lines.append(f"{indent} modes")
81
+ for m in self.modes:
82
+ lines.append(m.to_aadl(indent + " "))
83
+ lines.append(f"{indent}end {self.name};")
84
+ return "\n".join(lines)
85
+
86
+
87
+ @dataclass
88
+ class BehaviorGuard:
89
+ expression: str
90
+
91
+ def to_aadl(self) -> str:
92
+ return f"-[{self.expression}]-" if self.expression else ""
93
+
94
+
95
+ @dataclass
96
+ class BehaviorTransition:
97
+ source: str
98
+ destination: str
99
+ guard: Optional[BehaviorGuard] = None
100
+
101
+ def to_aadl(self) -> str:
102
+ if self.guard and self.guard.expression:
103
+ return f"{self.source} -[{self.guard.expression}]-> {self.destination};"
104
+ return f"{self.source} -> {self.destination};"
105
+
106
+
107
+ @dataclass
108
+ class BehaviorSpecification:
109
+ transitions: List[BehaviorTransition] = field(default_factory=list)
110
+
111
+ def add_transition(self, source: str, destination: str, guard_expression: Optional[str] = None) -> None:
112
+ self.transitions.append(
113
+ BehaviorTransition(
114
+ source=source,
115
+ destination=destination,
116
+ guard=BehaviorGuard(guard_expression) if guard_expression else None,
117
+ )
118
+ )
119
+
120
+ def to_aadl(self, indent: str = "") -> str:
121
+ lines = [f"{indent}annex behaviour_specification {{**", f"{indent} mode transitions"]
122
+ for t in self.transitions:
123
+ lines.append(f"{indent} {t.to_aadl()}")
124
+ lines.append(f"{indent}**}};")
125
+ return "\n".join(lines)
126
+
127
+
128
+ @dataclass
129
+ class ComponentImplementation:
130
+ name: str
131
+ category: str
132
+ type_name: str
133
+ subcomponents: Dict[str, Subcomponent] = field(default_factory=dict)
134
+ connections: List['Connection'] = field(default_factory=list)
135
+ modes: List['Mode'] = field(default_factory=list)
136
+ behavior: Optional['BehaviorSpecification'] = None
137
+
138
+ def to_aadl_header(self, indent: str = "") -> str:
139
+ return f"{indent}{self.category} implementation {self.name} for {self.type_name}"
140
+
141
+ def to_aadl(self, indent: str = "") -> str:
142
+ lines = [self.to_aadl_header(indent)]
143
+ if self.subcomponents:
144
+ lines.append(f"{indent} subcomponents")
145
+ for s in self.subcomponents.values():
146
+ lines.append(s.to_aadl(indent + " "))
147
+ if self.connections:
148
+ lines.append(f"{indent} connections")
149
+ for c in self.connections:
150
+ lines.append(c.to_aadl(indent + " "))
151
+ if self.modes:
152
+ lines.append(f"{indent} modes")
153
+ for m in self.modes:
154
+ lines.append(m.to_aadl(indent + " "))
155
+ if self.behavior:
156
+ lines.append(self.behavior.to_aadl(indent + " "))
157
+ lines.append(f"{indent}end {self.name};")
158
+ return "\n".join(lines)
159
+
160
+
161
+ @dataclass
162
+ class ThreadImplementation(ComponentImplementation):
163
+ category: str = field(init=False, default="thread")
164
+
165
+ @dataclass
166
+ class ProcessImplementation(ComponentImplementation):
167
+ category: str = field(init=False, default="process")
168
+
169
+ @dataclass
170
+ class SystemImplementation(ComponentImplementation):
171
+ category: str = field(init=False, default="system")
172
+
173
+
174
+ @dataclass
175
+ class AADLPackage:
176
+ name: str
177
+ with_packages: List[str] = field(default_factory=list)
178
+ public_types: Dict[str, ComponentType] = field(default_factory=dict)
179
+ public_implementations: Dict[str, ComponentImplementation] = field(default_factory=dict)
180
+ private_types: Dict[str, ComponentType] = field(default_factory=dict)
181
+ private_implementations: Dict[str, ComponentImplementation] = field(default_factory=dict)
182
+
183
+ def to_aadl(self) -> str:
184
+ lines = [f"package {self.name}", "public"]
185
+ if self.with_packages:
186
+ lines.append(f" with {', '.join(self.with_packages)};")
187
+ for t in self.public_types.values():
188
+ lines.append(t.to_aadl(" "))
189
+ for i in self.public_implementations.values():
190
+ lines.append(i.to_aadl(" "))
191
+ if self.private_types or self.private_implementations:
192
+ lines.append("private")
193
+ for t in self.private_types.values():
194
+ lines.append(t.to_aadl(" "))
195
+ for i in self.private_implementations.values():
196
+ lines.append(i.to_aadl(" "))
197
+ lines.append(f"end {self.name};")
198
+ return "\n".join(lines)
@@ -0,0 +1,464 @@
1
+ from __future__ import annotations
2
+
3
+ import io
4
+ import re
5
+ from pathlib import Path
6
+ from typing import Optional, Union
7
+
8
+ from robotransform.aadl import (
9
+ AADLPackage,
10
+ ComponentType,
11
+ Connection as AADLConnection,
12
+ Port,
13
+ Mode,
14
+ BehaviorSpecification,
15
+ BehaviorTransition,
16
+ BehaviorGuard,
17
+ Subcomponent,
18
+ ProcessImplementation,
19
+ ThreadImplementation,
20
+ SystemImplementation,
21
+ )
22
+ from robotransform.filters import type_to_aadl_type
23
+ from robotransform.main import get_template, write_output
24
+ from robotransform.concepts import Package, StateMachineDef, ControllerDef, RoboticPlatformDef, Variable, Event, Module
25
+
26
+ _IDENTIFIER_RE = re.compile(r"[^0-9A-Za-z_]")
27
+
28
+
29
+ def _aadl_id(value: object) -> str:
30
+ if value is None:
31
+ return "unnamed"
32
+ text = str(value)
33
+ text = text.replace("::", "_")
34
+ text = _IDENTIFIER_RE.sub("_", text)
35
+ if text and text[0].isdigit():
36
+ text = f"n_{text}"
37
+ return text or "unnamed"
38
+
39
+
40
+ def _local_name(value: object) -> str:
41
+ if value is None:
42
+ return "unnamed"
43
+ if hasattr(value, "parts"):
44
+ parts = getattr(value, "parts")
45
+ if parts:
46
+ return str(parts[-1])
47
+ text = str(value)
48
+ return text.split("::")[-1]
49
+
50
+
51
+ def _aadl_name(value: object) -> str:
52
+ return _aadl_id(_local_name(value))
53
+
54
+
55
+ def _aadl_qualified_name(value: object) -> str:
56
+ if hasattr(value, "parts"):
57
+ parts = getattr(value, "parts")
58
+ return "::".join(_aadl_id(part) for part in parts)
59
+ return str(value)
60
+
61
+
62
+ def _unique_name(base: str, existing: set[str]) -> str:
63
+ name = base
64
+ index = 1
65
+ while name in existing:
66
+ name = f"{base}_{index}"
67
+ index += 1
68
+ return name
69
+
70
+
71
+ def _message_type_name(type_ref: Optional[str]) -> Optional[str]:
72
+ if not type_ref or "::" not in type_ref:
73
+ return None
74
+ package, name = type_ref.split("::", 1)
75
+ if package != "messages":
76
+ return None
77
+ return name
78
+
79
+
80
+ def _collect_message_types(packages: list[AADLPackage]) -> set[str]:
81
+ names: set[str] = set()
82
+ for pkg in packages:
83
+ for comp in pkg.public_types.values():
84
+ for feature in comp.features.values():
85
+ if isinstance(feature, Port):
86
+ msg_name = _message_type_name(feature.data_type)
87
+ if msg_name:
88
+ names.add(msg_name)
89
+ for impl in pkg.public_implementations.values():
90
+ for sub in impl.subcomponents.values():
91
+ if sub.category != "data":
92
+ continue
93
+ msg_name = _message_type_name(sub.component_type)
94
+ if msg_name:
95
+ names.add(msg_name)
96
+ return names
97
+
98
+
99
+ def _collect_defined_message_types(robo_packages: list[Package]) -> set[str]:
100
+ names: set[str] = set()
101
+ for pkg in robo_packages:
102
+ for record in getattr(pkg, "types", []):
103
+ names.add(str(getattr(record, "name", "")))
104
+ return names
105
+
106
+
107
+ def _render_messages(robo_packages: list[Package], aadl_packages: list[AADLPackage],
108
+ output: Optional[Union[io.TextIOBase, Path, str]]) -> str:
109
+ used_types = _collect_message_types(aadl_packages)
110
+ defined_types = _collect_defined_message_types(robo_packages)
111
+ extra_types = sorted(used_types - defined_types)
112
+ template = get_template("messages")
113
+ data = template.render(packages=robo_packages, extra_types=extra_types)
114
+ return write_output(data, output)
115
+
116
+
117
+ def robocart_package_to_aadl_full(rc_pkg: "Package") -> AADLPackage:
118
+ aadl_pkg = AADLPackage(
119
+ name=_aadl_name(rc_pkg.name),
120
+ with_packages=["Base_Types", "messages"],
121
+ )
122
+
123
+ # ---- registries ----
124
+ def register_type(comp_type: ComponentType) -> ComponentType:
125
+ return aadl_pkg.public_types.setdefault(comp_type.name, comp_type)
126
+
127
+ def register_impl(comp_impl):
128
+ return aadl_pkg.public_implementations.setdefault(comp_impl.name, comp_impl)
129
+
130
+ local_type_names = {str(getattr(t, "name", "")) for t in getattr(rc_pkg, "types", [])}
131
+
132
+ # ---- helpers ----
133
+ def resolve_type(robo_type: object) -> str:
134
+ typename = str(robo_type)
135
+ short_name = typename.split("::")[-1]
136
+ if short_name in local_type_names:
137
+ return _aadl_id(short_name)
138
+ return type_to_aadl_type(robo_type)
139
+
140
+ def convert_event(ev: "Event") -> Port:
141
+ data_type = resolve_type(ev.type) if getattr(ev, "type", None) else None
142
+ port_category = "event_data" if data_type else "event"
143
+ return Port(
144
+ name=_aadl_id(ev.name),
145
+ direction="in out",
146
+ port_category=port_category,
147
+ data_type=data_type,
148
+ )
149
+
150
+ def convert_variable(var: "Variable") -> Subcomponent:
151
+ return Subcomponent(
152
+ name=_aadl_id(var.name),
153
+ category="data",
154
+ component_type=resolve_type(var.type),
155
+ )
156
+
157
+ def transition_guard(transition) -> Optional[str]:
158
+ trigger = getattr(transition, "trigger", None)
159
+ if not trigger:
160
+ return None
161
+ communication = getattr(trigger, "trigger", None)
162
+ if communication:
163
+ return _aadl_name(getattr(communication, "event", ""))
164
+ return None
165
+
166
+ def ensure_process(sm: "StateMachineDef") -> ProcessImplementation:
167
+ type_name = _aadl_name(sm.name)
168
+ impl_name = f"{type_name}.impl"
169
+ if impl_name in aadl_pkg.public_implementations:
170
+ return aadl_pkg.public_implementations[impl_name]
171
+
172
+ proc_type = ComponentType(name=type_name, category="process")
173
+ for ev in getattr(sm, "events", []):
174
+ port = convert_event(ev)
175
+ proc_type.features.setdefault(port.name, port)
176
+ register_type(proc_type)
177
+
178
+ proc_impl = ProcessImplementation(name=impl_name, type_name=type_name)
179
+
180
+ nodes = list(getattr(sm, "nodes", []) or [])
181
+ state_names: list[str] = []
182
+ initial_names: set[str] = set()
183
+ for idx, node in enumerate(nodes):
184
+ node_type = node.__class__.__name__
185
+ if node_type == "State":
186
+ raw_name = getattr(node, "name", f"state_{idx}")
187
+ state_names.append(_aadl_name(raw_name))
188
+ elif node_type == "Initial":
189
+ initial_names.add(_aadl_name(getattr(node, "name", "")))
190
+
191
+ state_name_set = set(state_names)
192
+ initial_targets: set[str] = set()
193
+ transitions: list[BehaviorTransition] = []
194
+ for t in getattr(sm, "transitions", []):
195
+ src = _aadl_name(getattr(t, "source", ""))
196
+ dst = _aadl_name(getattr(t, "target", ""))
197
+ if src in initial_names and dst in state_name_set:
198
+ initial_targets.add(dst)
199
+ if src in state_name_set and dst in state_name_set:
200
+ guard_expr = transition_guard(t)
201
+ guard = BehaviorGuard(guard_expr) if guard_expr else None
202
+ transitions.append(BehaviorTransition(source=src, destination=dst, guard=guard))
203
+
204
+ for state_name in state_names:
205
+ proc_impl.modes.append(Mode(name=state_name, initial=state_name in initial_targets))
206
+
207
+ if proc_impl.modes and not any(m.initial for m in proc_impl.modes):
208
+ proc_impl.modes[0].initial = True
209
+
210
+ subcomponent_names = set()
211
+ for state_name in state_names:
212
+ thread_type_name = _aadl_id(f"{type_name}_{state_name}_Thread")
213
+ thread_impl_name = f"{thread_type_name}.impl"
214
+ register_type(ComponentType(name=thread_type_name, category="thread"))
215
+ register_impl(ThreadImplementation(name=thread_impl_name, type_name=thread_type_name))
216
+ sub_name = _unique_name(f"{state_name}_thread", subcomponent_names)
217
+ subcomponent_names.add(sub_name)
218
+ proc_impl.subcomponents[sub_name] = Subcomponent(
219
+ name=sub_name,
220
+ category="thread",
221
+ component_type=thread_type_name,
222
+ component_implementation="impl",
223
+ )
224
+
225
+ for var in getattr(sm, "variables", []):
226
+ data_sub = convert_variable(var)
227
+ data_sub.name = _unique_name(data_sub.name, subcomponent_names)
228
+ subcomponent_names.add(data_sub.name)
229
+ proc_impl.subcomponents[data_sub.name] = data_sub
230
+
231
+ if transitions:
232
+ behavior = BehaviorSpecification(transitions=transitions)
233
+ proc_impl.behavior = behavior
234
+
235
+ register_impl(proc_impl)
236
+ return proc_impl
237
+
238
+ def connection_endpoint(component, event, subcomponents: set[str]) -> str:
239
+ component_name = _aadl_name(component)
240
+ event_name = _aadl_name(event)
241
+ if component_name in subcomponents:
242
+ return f"{component_name}.{event_name}"
243
+ return event_name
244
+
245
+ def convert_connection(conn, subcomponents: set[str]) -> AADLConnection:
246
+ src = _aadl_name(getattr(conn, "source", ""))
247
+ dst = _aadl_name(getattr(conn, "target", ""))
248
+ src_evt = _aadl_name(getattr(conn, "event_source", ""))
249
+ dst_evt = _aadl_name(getattr(conn, "event_target", ""))
250
+ name = _aadl_id(f"{src}_{src_evt}_to_{dst}_{dst_evt}")
251
+ return AADLConnection(
252
+ name=name,
253
+ source=connection_endpoint(conn.source, conn.event_source, subcomponents),
254
+ destination=connection_endpoint(conn.target, conn.event_target, subcomponents),
255
+ bidirectional=getattr(conn, "bidirectional", False),
256
+ )
257
+
258
+ def ensure_controller(controller: "ControllerDef") -> SystemImplementation:
259
+ type_name = _aadl_name(controller.name)
260
+ impl_name = f"{type_name}.impl"
261
+ if impl_name in aadl_pkg.public_implementations:
262
+ return aadl_pkg.public_implementations[impl_name]
263
+
264
+ sys_type = ComponentType(name=type_name, category="system")
265
+ for ev in getattr(controller, "events", []):
266
+ port = convert_event(ev)
267
+ sys_type.features.setdefault(port.name, port)
268
+ register_type(sys_type)
269
+
270
+ sys_impl = SystemImplementation(name=impl_name, type_name=type_name)
271
+ subcomponent_names = set()
272
+ port_subcomponents = set()
273
+
274
+ for sm in getattr(controller, "machines", []):
275
+ if isinstance(sm, StateMachineDef) or sm.__class__.__name__ == "StateMachineDef":
276
+ proc_impl = ensure_process(sm)
277
+ sub_name = _aadl_name(getattr(sm, "name", "process"))
278
+ sub_name = _unique_name(sub_name, subcomponent_names)
279
+ subcomponent_names.add(sub_name)
280
+ port_subcomponents.add(sub_name)
281
+ sys_impl.subcomponents[sub_name] = Subcomponent(
282
+ name=sub_name,
283
+ category="process",
284
+ component_type=proc_impl.type_name,
285
+ component_implementation="impl",
286
+ )
287
+ elif sm.__class__.__name__ == "StateMachineRef":
288
+ sub_name = _aadl_name(getattr(sm, "name", "process_ref"))
289
+ sub_name = _unique_name(sub_name, subcomponent_names)
290
+ subcomponent_names.add(sub_name)
291
+ port_subcomponents.add(sub_name)
292
+ sys_impl.subcomponents[sub_name] = Subcomponent(
293
+ name=sub_name,
294
+ category="process",
295
+ component_type=_aadl_qualified_name(getattr(sm, "ref", "")),
296
+ )
297
+
298
+ for var in getattr(controller, "variables", []):
299
+ data_sub = convert_variable(var)
300
+ data_sub.name = _unique_name(data_sub.name, subcomponent_names)
301
+ subcomponent_names.add(data_sub.name)
302
+ sys_impl.subcomponents[data_sub.name] = data_sub
303
+
304
+ for conn in getattr(controller, "connections", []):
305
+ sys_impl.connections.append(convert_connection(conn, port_subcomponents))
306
+
307
+ register_impl(sys_impl)
308
+ return sys_impl
309
+
310
+ def ensure_robotic_platform(platform: "RoboticPlatformDef") -> SystemImplementation:
311
+ type_name = _aadl_name(platform.name)
312
+ impl_name = f"{type_name}.impl"
313
+ if impl_name in aadl_pkg.public_implementations:
314
+ return aadl_pkg.public_implementations[impl_name]
315
+
316
+ sys_type = ComponentType(name=type_name, category="system")
317
+ for ev in getattr(platform, "events", []):
318
+ port = convert_event(ev)
319
+ sys_type.features.setdefault(port.name, port)
320
+ register_type(sys_type)
321
+
322
+ sys_impl = SystemImplementation(name=impl_name, type_name=type_name)
323
+ subcomponent_names = set()
324
+
325
+ for var in getattr(platform, "variables", []):
326
+ data_sub = convert_variable(var)
327
+ data_sub.name = _unique_name(data_sub.name, subcomponent_names)
328
+ subcomponent_names.add(data_sub.name)
329
+ sys_impl.subcomponents[data_sub.name] = data_sub
330
+
331
+ register_impl(sys_impl)
332
+ return sys_impl
333
+
334
+ def ensure_module(module: "Module") -> SystemImplementation:
335
+ type_name = _aadl_name(module.name)
336
+ impl_name = f"{type_name}.impl"
337
+ if impl_name in aadl_pkg.public_implementations:
338
+ return aadl_pkg.public_implementations[impl_name]
339
+
340
+ module_type = ComponentType(name=type_name, category="system")
341
+ register_type(module_type)
342
+
343
+ module_impl = SystemImplementation(name=impl_name, type_name=type_name)
344
+ subcomponent_names = set()
345
+ port_subcomponents = set()
346
+
347
+ for node in getattr(module, "nodes", []):
348
+ node_type = node.__class__.__name__
349
+ if node_type == "ControllerDef":
350
+ controller_impl = ensure_controller(node)
351
+ sub_name = _aadl_name(getattr(node, "name", "controller"))
352
+ sub_name = _unique_name(sub_name, subcomponent_names)
353
+ subcomponent_names.add(sub_name)
354
+ port_subcomponents.add(sub_name)
355
+ module_impl.subcomponents[sub_name] = Subcomponent(
356
+ name=sub_name,
357
+ category="system",
358
+ component_type=controller_impl.type_name,
359
+ component_implementation="impl",
360
+ )
361
+ elif node_type == "ControllerRef":
362
+ sub_name = _aadl_name(getattr(node, "name", "controller_ref"))
363
+ sub_name = _unique_name(sub_name, subcomponent_names)
364
+ subcomponent_names.add(sub_name)
365
+ port_subcomponents.add(sub_name)
366
+ module_impl.subcomponents[sub_name] = Subcomponent(
367
+ name=sub_name,
368
+ category="system",
369
+ component_type=_aadl_qualified_name(getattr(node, "ref", "")),
370
+ )
371
+ elif node_type == "StateMachineDef":
372
+ proc_impl = ensure_process(node)
373
+ sub_name = _aadl_name(getattr(node, "name", "process"))
374
+ sub_name = _unique_name(sub_name, subcomponent_names)
375
+ subcomponent_names.add(sub_name)
376
+ port_subcomponents.add(sub_name)
377
+ module_impl.subcomponents[sub_name] = Subcomponent(
378
+ name=sub_name,
379
+ category="process",
380
+ component_type=proc_impl.type_name,
381
+ component_implementation="impl",
382
+ )
383
+ elif node_type == "StateMachineRef":
384
+ sub_name = _aadl_name(getattr(node, "name", "process_ref"))
385
+ sub_name = _unique_name(sub_name, subcomponent_names)
386
+ subcomponent_names.add(sub_name)
387
+ port_subcomponents.add(sub_name)
388
+ module_impl.subcomponents[sub_name] = Subcomponent(
389
+ name=sub_name,
390
+ category="process",
391
+ component_type=_aadl_qualified_name(getattr(node, "ref", "")),
392
+ )
393
+ elif node_type == "RoboticPlatformDef":
394
+ platform_impl = ensure_robotic_platform(node)
395
+ sub_name = _aadl_name(getattr(node, "name", "platform"))
396
+ sub_name = _unique_name(sub_name, subcomponent_names)
397
+ subcomponent_names.add(sub_name)
398
+ port_subcomponents.add(sub_name)
399
+ module_impl.subcomponents[sub_name] = Subcomponent(
400
+ name=sub_name,
401
+ category="system",
402
+ component_type=platform_impl.type_name,
403
+ component_implementation="impl",
404
+ )
405
+ elif node_type == "RoboticPlatformRef":
406
+ sub_name = _aadl_name(getattr(node, "name", "platform_ref"))
407
+ sub_name = _unique_name(sub_name, subcomponent_names)
408
+ subcomponent_names.add(sub_name)
409
+ port_subcomponents.add(sub_name)
410
+ module_impl.subcomponents[sub_name] = Subcomponent(
411
+ name=sub_name,
412
+ category="system",
413
+ component_type=_aadl_qualified_name(getattr(node, "ref", "")),
414
+ )
415
+
416
+ for conn in getattr(module, "connections", []):
417
+ module_impl.connections.append(convert_connection(conn, port_subcomponents))
418
+
419
+ register_impl(module_impl)
420
+ return module_impl
421
+
422
+ # ---- top-level package conversion ----
423
+ for record in getattr(rc_pkg, "types", []):
424
+ data_name = _aadl_name(getattr(record, "name", "data"))
425
+ register_type(ComponentType(name=data_name, category="data"))
426
+
427
+ for ctl in getattr(rc_pkg, "controllers", []):
428
+ ensure_controller(ctl)
429
+
430
+ for sm in getattr(rc_pkg, "machines", []):
431
+ if isinstance(sm, StateMachineDef) or sm.__class__.__name__ == "StateMachineDef":
432
+ ensure_process(sm)
433
+
434
+ for robot in getattr(rc_pkg, "robots", []):
435
+ ensure_robotic_platform(robot)
436
+
437
+ for mod in getattr(rc_pkg, "modules", []):
438
+ ensure_module(mod)
439
+
440
+ return aadl_pkg
441
+
442
+
443
+ def dump_aadl(rc_input: Union["Package", "Store"], output: Optional[Union[io.TextIOBase, Path, str]] = None,
444
+ template_name: str = "aadl",
445
+ messages_output: Optional[Union[io.TextIOBase, Path, str]] = None) -> str:
446
+ # If a Store, render all packages inside it
447
+ if hasattr(rc_input, "__iter__") and not isinstance(rc_input, Package):
448
+ robo_packages = list(rc_input.load().values())
449
+ packages = [robocart_package_to_aadl_full(pkg) for pkg in robo_packages]
450
+ else:
451
+ robo_packages = [rc_input]
452
+ packages = [robocart_package_to_aadl_full(rc_input)]
453
+
454
+ template = get_template(template_name)
455
+ data = template.render(packages=packages)
456
+ write_output(data, output)
457
+
458
+ if messages_output is None and isinstance(output, (str, Path)):
459
+ messages_output = Path(output).parent / "messages.aadl"
460
+
461
+ if messages_output is not None:
462
+ _render_messages(robo_packages, packages, messages_output)
463
+
464
+ return data
robotransform/filters.py CHANGED
@@ -1,9 +1,4 @@
1
- def type_to_port(_type) -> str:
2
- # TODO this is wrong
3
- if str(_type) in {"event", }:
4
- return "event"
5
- return "data"
6
-
1
+ from __future__ import annotations
7
2
  def type_to_aadl_type(_type) -> str:
8
3
  typename = str(_type)
9
4
  # Boolean,
@@ -27,6 +22,3 @@ def type_to_aadl_type(_type) -> str:
27
22
  elif "*" in typename:
28
23
  typename = typename.split("*")[0]
29
24
  return mapping.get(typename, f"messages::{typename}")
30
-
31
- def aadl_case_fix(indetifier: str) -> str:
32
- return identifier
robotransform/main.py CHANGED
@@ -1,3 +1,4 @@
1
+ import logging
1
2
  from dataclasses import dataclass, fields
2
3
  from functools import lru_cache
3
4
  from pathlib import Path
@@ -6,10 +7,10 @@ import arklog
6
7
  from textx import metamodel_from_str
7
8
  from jinja2 import Environment, FileSystemLoader, Template
8
9
  import io
9
- from typing import Union, Optional, Iterable, Iterator
10
+ from typing import Union, Optional, Iterable, Iterator, Callable
10
11
 
11
12
  from robotransform.concepts import all_concepts, Package
12
- from robotransform.filters import type_to_port, type_to_aadl_type
13
+ from robotransform.filters import type_to_aadl_type
13
14
  from robotransform.processors import processors
14
15
 
15
16
 
@@ -29,30 +30,39 @@ class Store:
29
30
  yield item
30
31
  continue
31
32
 
32
- def parse_imports(self, package: Package, parent_path: Path, visited):
33
+ def parse_imports(self, package: Package, parent_path: Path, visited: dict[Path, Package]):
33
34
  stack = [package]
35
+ unresolved = []
34
36
  while stack:
35
- package = stack.pop()
36
- for imp in package.imports:
37
+ current_package = stack.pop()
38
+ for imp in current_package.imports:
37
39
  name = imp.name.name.parts[-1]
38
- if name in visited:
39
- continue
40
40
  path = parent_path / f"{name}.rct"
41
+
42
+ if path in visited or path in unresolved: # check path, not name
43
+ continue
41
44
  if not path.exists():
42
- arklog.debug(f"Can't import {path}.")
45
+ arklog.debug(f"Can't import ({path}).")
46
+ unresolved.append(path)
43
47
  continue
48
+
44
49
  sub_package = parse_robochart(path)
45
- visited[path] = sub_package
50
+ visited[path] = sub_package # mark visited immediately
46
51
  stack.append(sub_package)
52
+
47
53
  return visited
48
54
 
49
55
  def load(self, imports: bool = True) -> dict[Path, Package]:
50
- visited = {}
56
+ visited: dict[Path, Package] = {}
57
+
51
58
  for path in self:
52
- package = parse_robochart(path)
53
- visited[path] = package
54
- if imports:
55
- visited |= self.parse_imports(package, path.parent, visited)
59
+ if path not in visited:
60
+ package = parse_robochart(path)
61
+ visited[path] = package # mark visited immediately
62
+ if imports:
63
+ # parse imports recursively
64
+ visited |= self.parse_imports(package, path.parent, visited)
65
+
56
66
  return visited
57
67
 
58
68
 
@@ -98,15 +108,13 @@ def parse_robochart(source: Path | str) -> Package:
98
108
  return metamodel.model_from_str(data)
99
109
 
100
110
 
101
- @lru_cache(maxsize=2)
111
+ @lru_cache(maxsize=3)
102
112
  def get_template(name: str) -> Template:
103
113
  environment = Environment(loader=FileSystemLoader(Path(__file__).resolve().parent / "templates"))
104
- environment.filters["type_to_port"] = type_to_port # For use with pipes
105
- environment.filters["type_to_aadl_type"] = type_to_aadl_type # For use with pipes
106
- # environment.globals["type_to_port"] = type_to_port # For use as a function
114
+ environment.filters["type_to_aadl_type"] = type_to_aadl_type # For use with pipes
107
115
  templates = {
108
- "messages": "messages.aadl",
109
- "logical": "logical.aadl",
116
+ "messages": "messages.aadl.jinja",
117
+ "aadl": "aadl.jinja",
110
118
  }
111
119
  if found := templates.get(name):
112
120
  return environment.get_template(found)
@@ -129,18 +137,6 @@ def write_output(data: str, output: Optional[Union[io.TextIOBase, Path, str]] =
129
137
  return data
130
138
 
131
139
 
132
- def dump_messages(store: Store, output: Optional[Union[io.TextIOBase, Path, str]] = None) -> None:
133
- template = get_template("messages")
134
- output = output if output else Path("output/generated/messages/messages.aadl")
135
- write_output(template.render(packages=store.load().values()), output)
136
-
137
-
138
- def dump_logical(store: Store, output: Optional[Union[io.TextIOBase, Path, str]] = None) -> None:
139
- template = get_template("logical")
140
- output = output if output else Path("output/generated/LogicalArchitecture.aadl")
141
- write_output(template.render(packages=store.load().values()), output)
142
-
143
-
144
140
  def main():
145
141
  pass
146
142
 
@@ -302,7 +302,7 @@ Transition:
302
302
  'transition' name=ID '{'
303
303
  'from' source=QualifiedName
304
304
  'to' target=QualifiedName
305
- trigger?=Trigger
305
+ (trigger=Trigger)?
306
306
  (reset*=ClockReset '<{' deadline=Expression '}')?
307
307
  ('condition' condition=ConditionExpr)?
308
308
  ('action' action=Statement)?
@@ -0,0 +1,18 @@
1
+ {#- Render all AADL packages -#}
2
+ {% for pkg in packages -%}
3
+ package {{ pkg.name }}
4
+ public
5
+ {% if pkg.with_packages -%}
6
+ with {{ pkg.with_packages | join(', ') }};
7
+ {% endif -%}
8
+ {% for comp in pkg.public_types.values() -%}
9
+ {{ comp.to_aadl(" ") }}
10
+ {% endfor -%}
11
+ {% for impl in pkg.public_implementations.values() -%}
12
+ {{ impl.to_aadl(" ") }}
13
+ {% endfor -%}
14
+ end {{ pkg.name }};
15
+ {% if not loop.last %}
16
+
17
+ {% endif -%}
18
+ {% endfor %}
@@ -11,4 +11,10 @@ public
11
11
  end {{type.name}};
12
12
  {%- endfor %}
13
13
  {%- endfor %}
14
+ {%- if extra_types %}
15
+ {%- for type_name in extra_types %}
16
+ data {{ type_name }}
17
+ end {{ type_name }};
18
+ {%- endfor %}
19
+ {%- endif %}
14
20
  end messages;
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: robotransform
3
- Version: 0.0.6
3
+ Version: 0.1.0
4
4
  Summary: Perform model transformations for the RoboSapiens project.
5
5
  Keywords: transformation
6
6
  Author: Arkadiusz Michał Ryś
@@ -688,25 +688,14 @@ Requires-Dist: arkfast>=0.1.0,<1
688
688
  Requires-Dist: textx>=4.2.3,<5
689
689
  Requires-Dist: jinja2>=3.1.6,<4
690
690
  Requires-Dist: pyecore>=0.15.2,<1
691
- Requires-Dist: sphinx ; extra == 'doc'
692
- Requires-Dist: sphinx-rtd-theme ; extra == 'doc'
693
- Requires-Dist: sphinx-tabs ; extra == 'doc'
694
- Requires-Dist: sphinx-toolbox ; extra == 'doc'
695
- Requires-Dist: sphinxext-opengraph ; extra == 'doc'
696
- Requires-Dist: sphinx-notfound-page ; extra == 'doc'
697
- Requires-Dist: sphinxcontrib-drawio ; extra == 'doc'
698
- Requires-Dist: readthedocs-sphinx-search ; extra == 'doc'
699
- Requires-Dist: myst-parser ; extra == 'doc'
700
- Requires-Dist: pytest ; extra == 'test'
701
- Requires-Python: >=3.12
691
+ Requires-Python: >=3.14
702
692
  Project-URL: source, https://git.rys.one/phd/robosapiens/robotransform
703
- Provides-Extra: doc
704
- Provides-Extra: test
693
+ Provides-Extra: gui
705
694
  Description-Content-Type: text/markdown
706
695
 
707
696
  # RoboTransform
708
697
 
709
- Library to perform model to model transformations in the context of RoboSAPIENS.
698
+ Library to perform model-to-model transformations in the context of RoboSAPIENS.
710
699
 
711
700
  For installation steps follow [the documentation](https://robotransform-0f96de.pages.rys.one/).
712
701
  We also provide [the reasoning behind certain decisions](-/jobs/artifacts/main/download?job=typst).
@@ -0,0 +1,15 @@
1
+ robotransform/__init__.py,sha256=xvdDZSVP3mLi7gN7a2Qdtyvh-ZQ1YwXlIaGFvcb1Ulg,157
2
+ robotransform/__main__.py,sha256=PcaDvzku32_BZ3c9ODZMUJKveARGID3FGOq0XA60c7o,617
3
+ robotransform/aadl.py,sha256=tPlmiSltLSbC8kqU-FlxyDPdcdTjUT4rKHwEweFgBow,6664
4
+ robotransform/concepts.py,sha256=MXPff6XwQyabWJcSC5armw3eI4xGDmnd6yZpmHdO6m8,9933
5
+ robotransform/convert.py,sha256=KStKLUDAXD15j4-jzqIG-rga8c5WGB3J4URjAGuJvt0,18819
6
+ robotransform/filters.py,sha256=uEG2xs-gvtxpmyItbXtjLeOXgCJlACbPgAL1Hi2n5iE,838
7
+ robotransform/main.py,sha256=xrbu1Dal027VPAYSzRjHFqh7OuFHQZ_tkL_xz7lYFro,4620
8
+ robotransform/processors.py,sha256=8NaNFTAm3iaNAzbp5D8-Uv_qIWyxVklIbmGtXi5--nM,1578
9
+ robotransform/robochart.tx,sha256=lM4ooS8jvb949q20X4t6jZzh1M3VCtlxUORlWS3GA4w,10229
10
+ robotransform/templates/aadl.jinja,sha256=7BHQvBPsJby5XSmiqEXd8mrQwEdSG3WPtwzcDu8WSlY,424
11
+ robotransform/templates/messages.aadl.jinja,sha256=1EOMiBE-Lh5FWnfjSsVoICDU_-ZcgFEY1sMasXP5k00,530
12
+ robotransform-0.1.0.dist-info/WHEEL,sha256=XV0cjMrO7zXhVAIyyc8aFf1VjZ33Fen4IiJk5zFlC3g,80
13
+ robotransform-0.1.0.dist-info/entry_points.txt,sha256=NvmK-01oEE7ji06NbIxr7pZGUEJwbwMLjjfHssOVmB0,52
14
+ robotransform-0.1.0.dist-info/METADATA,sha256=MwlUaGCSB2st_rvml5t5CzYM2oSmJ8U8LMNDYoYZ-h0,42259
15
+ robotransform-0.1.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: uv 0.9.16
2
+ Generator: uv 0.9.26
3
3
  Root-Is-Purelib: true
4
- Tag: py3-none-any
4
+ Tag: py3-none-any
@@ -1,74 +0,0 @@
1
- {%- macro render_port(name, direction, type) -%}
2
- {{ name }}: {{ direction }} {{ type | type_to_port }} port {{ type | type_to_aadl_type}};
3
- {%- endmacro -%}
4
-
5
- {%- macro render_node_threads(machine) -%}
6
- {%- for node in machine.nodes %}
7
- thread {{ node.name }}
8
- --features {# TODO: Replace these with real resolved fields #}
9
- --input: in data port Base_Types::Float;
10
- --output: out data port Base_Types::Float;
11
- end {{ node.name }};
12
- thread implementation {{ node.name }}.impl
13
- end {{ node.name }}.impl;
14
- {%- endfor %}
15
- {%- endmacro -%}
16
-
17
- package LogicalArchitecture
18
- public
19
- with messages, Base_Types;
20
- {%- for package in packages %}
21
- process {{package.name}}
22
- {%- set interface_vars = package.interfaces | map(attribute="variables") | sum(start=[]) -%}
23
- {%- set machine_vars = package.machines | map(attribute="variables") | sum(start=[]) -%}
24
- {%- if interface_vars or machine_vars %}
25
- features
26
- {%- for interface in package.interfaces -%}
27
- {%- for variable in interface.variables %}
28
- {{ render_port(variable.name, "in", variable.type) }}
29
- {%- endfor %}
30
- {%- endfor %}
31
- {%- for machine in package.machines -%}
32
- {%- for variable in machine.variables %}
33
- {{ render_port(variable.name, "in", variable.type) }}
34
- {%- endfor %}
35
- {%- endfor -%}
36
- {%- endif %}
37
- end {{package.name}};
38
- process implementation {{package.name}}.impl
39
- {%- set machines_with_nodes = package.machines | selectattr('nodes') | selectattr('nodes', '!=', []) | list %}
40
- {%- if machines_with_nodes %}
41
- modes
42
- {%- for machine in machines_with_nodes -%}
43
- {% for node in machine.nodes %}
44
- {{node.name}}: {% if loop.first %}initial {% endif %}mode;
45
- {%- endfor %}
46
- {%- endfor %}
47
- {%- endif %}
48
- {%- if machines_with_nodes %}
49
- --subcomponents
50
- {%- for machine in machines_with_nodes -%}
51
- {% for node in machine.nodes %}
52
- --{{node.name}}: thread {{node.name}}; {# TODO: Filter so not all nodes are transformed into threads #}
53
- {%- endfor %}
54
- {%- endfor %}
55
- {%- endif %}
56
- {%- set machines_with_transitions = package.machines | selectattr("transitions") | selectattr('transitions', '!=', []) | list %}
57
- {%- if machines_with_transitions %}
58
- annex behaviour_specification {**
59
- mode transitions
60
- {%- for machine in machines_with_transitions -%}
61
- {% for transition in machine.transitions %}
62
- {{ transition.source }} -> {{ transition.target }}; {# TODO: Check if these are correct #}
63
- {%- endfor %}
64
- {%- endfor %}
65
- **};
66
- {%- endif %}
67
- end {{package.name}}.impl;
68
- {%- if machines_with_nodes %}
69
- {%- for machine in machines_with_nodes -%}
70
- {{ render_node_threads(machine) }}
71
- {%- endfor %}
72
- {%- endif %}
73
- {%- endfor %}
74
- end LogicalArchitecture;
@@ -1,12 +0,0 @@
1
- robotransform/__init__.py,sha256=8IkQ3Astb5vWt6yKgB0e3FqniYWPy9wufasZR9OwXmI,166
2
- robotransform/concepts.py,sha256=MXPff6XwQyabWJcSC5armw3eI4xGDmnd6yZpmHdO6m8,9933
3
- robotransform/filters.py,sha256=35FKiQlVpKxjCtFaUydwMu8QPt-GcaPn5hwDnV7MCzw,1002
4
- robotransform/main.py,sha256=afE30HpVo5weXeBO60LOpkCDS4cZ0EKo-nuD0xUG5cI,5025
5
- robotransform/processors.py,sha256=8NaNFTAm3iaNAzbp5D8-Uv_qIWyxVklIbmGtXi5--nM,1578
6
- robotransform/robochart.tx,sha256=dz_4hr-reu9ubuXdZR4WNKkp-gs_txgzyRvR-p7nphA,10227
7
- robotransform/templates/logical.aadl,sha256=AkqbpICONS4iOkQozAjKB7JIe1BgU-duoOTa7pPt2b4,2819
8
- robotransform/templates/messages.aadl,sha256=gSEULreDkIM-ukODyD1WnaV1qFTw_j5EAM2Z9u-Xkmo,383
9
- robotransform-0.0.6.dist-info/WHEEL,sha256=93kfTGt3a0Dykt_T-gsjtyS5_p8F_d6CE1NwmBOirzo,79
10
- robotransform-0.0.6.dist-info/entry_points.txt,sha256=NvmK-01oEE7ji06NbIxr7pZGUEJwbwMLjjfHssOVmB0,52
11
- robotransform-0.0.6.dist-info/METADATA,sha256=dJ5c206E1zIJ7KqLXzBjF57k2oYcz7EWLNpcC2Txf0A,42759
12
- robotransform-0.0.6.dist-info/RECORD,,