robotransform 0.0.2__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 +0 -0
- robotransform/concepts.py +286 -0
- robotransform/filters.py +12 -0
- robotransform/main.py +146 -0
- robotransform/robochart.tx +507 -0
- robotransform/templates/logical.aadl +61 -0
- robotransform/templates/messages.aadl +27 -0
- robotransform-0.0.2.dist-info/METADATA +710 -0
- robotransform-0.0.2.dist-info/RECORD +11 -0
- robotransform-0.0.2.dist-info/WHEEL +4 -0
- robotransform-0.0.2.dist-info/entry_points.txt +3 -0
|
File without changes
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from typing import Type as ClassType, Any, List, Optional, Union
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
# TODO Flatten this a little bit
|
|
6
|
+
|
|
7
|
+
class Node:
|
|
8
|
+
def __init__(self, parent: Optional[Any] = None):
|
|
9
|
+
self.parent = parent
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ControllerDef(Node):
|
|
13
|
+
def __init__(self, name, uses, provides, requires, connections, machines, events, operations, variables,
|
|
14
|
+
parent: Any):
|
|
15
|
+
super().__init__(parent)
|
|
16
|
+
self.name = name
|
|
17
|
+
self.uses = uses
|
|
18
|
+
self.provides = provides
|
|
19
|
+
self.requires = requires
|
|
20
|
+
self.connections = connections
|
|
21
|
+
self.machines = machines
|
|
22
|
+
self.events = events
|
|
23
|
+
self.operations = operations
|
|
24
|
+
self.variables = variables
|
|
25
|
+
|
|
26
|
+
def __repr__(self):
|
|
27
|
+
use_count = len(self.uses)
|
|
28
|
+
provide_count = len(self.provides)
|
|
29
|
+
require_count = len(self.requires)
|
|
30
|
+
connection_count = len(self.connections)
|
|
31
|
+
machine_count = len(self.machines)
|
|
32
|
+
event_count = len(self.events)
|
|
33
|
+
operation_count = len(self.operations)
|
|
34
|
+
variable_count = len(self.variables)
|
|
35
|
+
return f"{self.name}: ({use_count} uses), ({provide_count} provides), ({require_count} requires), ({connection_count} connections), ({machine_count} machines, ({event_count} events), ({operation_count} operations), ({variable_count} variables)"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class Variable(Node):
|
|
39
|
+
def __init__(self, name: str, kind, initial, parent: Any):
|
|
40
|
+
super().__init__(parent)
|
|
41
|
+
self.name = name
|
|
42
|
+
self.type = kind
|
|
43
|
+
self.initial = initial
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class Type(Node):
|
|
47
|
+
def __init__(self, source: FunctionType, target: Optional[Type], parent: Any):
|
|
48
|
+
super().__init__(parent)
|
|
49
|
+
self.source = source
|
|
50
|
+
self.target = target
|
|
51
|
+
|
|
52
|
+
def __repr__(self):
|
|
53
|
+
if self.target:
|
|
54
|
+
return f"{self.source} <-> {self.target}"
|
|
55
|
+
return f"{self.source}"
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class Field(Node):
|
|
59
|
+
def __init__(self, name: str, kind: Type, parent: Any):
|
|
60
|
+
super().__init__(parent)
|
|
61
|
+
self.name = name
|
|
62
|
+
self.type = kind
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
def typename(self) -> str:
|
|
66
|
+
return str(self.type)
|
|
67
|
+
|
|
68
|
+
def __repr__(self):
|
|
69
|
+
return f"{self.name}[{self.type}]"
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class RecordType(Node):
|
|
73
|
+
def __init__(self, name: str, fields: List[Field], parent: Any):
|
|
74
|
+
# Can be datatype or record
|
|
75
|
+
super().__init__(parent)
|
|
76
|
+
self.name = name
|
|
77
|
+
self.fields = fields
|
|
78
|
+
|
|
79
|
+
def __repr__(self):
|
|
80
|
+
return f"{self.name}({self.fields})"
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class Function(Node):
|
|
84
|
+
def __init__(self, name: str, parameters, kind, body, parent: Any):
|
|
85
|
+
super().__init__(parent)
|
|
86
|
+
self.name = name
|
|
87
|
+
self.parameters = parameters
|
|
88
|
+
self.kind = kind
|
|
89
|
+
self.body = body
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class RCModule(Node):
|
|
93
|
+
def __init__(self, name: str, connections, nodes, parent: Any):
|
|
94
|
+
super().__init__(parent)
|
|
95
|
+
self.name = name
|
|
96
|
+
self.connections = connections
|
|
97
|
+
self.nodes = nodes
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class VariableList(Node):
|
|
101
|
+
def __init__(self, modifier, variables, parent: Any):
|
|
102
|
+
super().__init__(parent)
|
|
103
|
+
self.modifier = modifier
|
|
104
|
+
self.variables = variables
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class Interface(Node):
|
|
108
|
+
def __init__(self, name: str, operations, events, variables: List[VariableList], clocks, parent: Any):
|
|
109
|
+
super().__init__(parent)
|
|
110
|
+
self.name = name
|
|
111
|
+
self.operations = operations
|
|
112
|
+
self.events = events
|
|
113
|
+
self._variables = variables
|
|
114
|
+
self.clocks = clocks
|
|
115
|
+
|
|
116
|
+
@property
|
|
117
|
+
def variables(self) -> List[Variable]:
|
|
118
|
+
return [var for v in self._variables for var in v.variables]
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class RoboticPlatformDef(Node):
|
|
122
|
+
def __init__(self, name: str, uses, provides, requires, variables, operations, events, parent: Any):
|
|
123
|
+
super().__init__(parent)
|
|
124
|
+
self.name = name
|
|
125
|
+
self.uses = uses
|
|
126
|
+
self.provides = provides
|
|
127
|
+
self.requires = requires
|
|
128
|
+
self._variables = variables
|
|
129
|
+
self.operations = operations
|
|
130
|
+
self.events = events
|
|
131
|
+
|
|
132
|
+
@property
|
|
133
|
+
def variables(self) -> List[Variable]:
|
|
134
|
+
return [var for v in self._variables for var in v.variables]
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
class OperationDef(Node):
|
|
138
|
+
def __init__(self, name: str, parent: Any):
|
|
139
|
+
super().__init__(parent)
|
|
140
|
+
self.name = name
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
class RCPackage(Node):
|
|
144
|
+
def __init__(self, name: QualifiedName, imports: List[Import], controllers: List[ControllerDef],
|
|
145
|
+
modules: List[RCModule], functions: List[Function], types: List[RecordType],
|
|
146
|
+
machines: List[StateMachineDef], interfaces: List[Interface], robots: List[RoboticPlatformDef],
|
|
147
|
+
operations: List[OperationDef]):
|
|
148
|
+
super().__init__()
|
|
149
|
+
self.name = name
|
|
150
|
+
self.imports = imports
|
|
151
|
+
self.controllers = controllers
|
|
152
|
+
self.modules = modules
|
|
153
|
+
self.functions = functions
|
|
154
|
+
self.types = types
|
|
155
|
+
self.machines = machines
|
|
156
|
+
self.interfaces = interfaces
|
|
157
|
+
self.robots = robots
|
|
158
|
+
self.operations = operations
|
|
159
|
+
|
|
160
|
+
def __repr__(self):
|
|
161
|
+
return f"{self.name}"
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
class QualifiedName(Node):
|
|
165
|
+
def __init__(self, parts: List[str], parent: Any):
|
|
166
|
+
super().__init__(parent)
|
|
167
|
+
self.parts = parts
|
|
168
|
+
|
|
169
|
+
def __repr__(self):
|
|
170
|
+
return "".join(self.parts)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
class StateMachineDef(Node):
|
|
174
|
+
def __init__(self, name, interfaces, provides, requires, variables, events, nodes, transitions, clocks,
|
|
175
|
+
parent: Any):
|
|
176
|
+
super().__init__(parent)
|
|
177
|
+
self.name = name
|
|
178
|
+
self.interfaces = interfaces
|
|
179
|
+
self.provides = provides
|
|
180
|
+
self.requires = requires
|
|
181
|
+
self._variables = variables
|
|
182
|
+
self.events = events
|
|
183
|
+
self.nodes = nodes
|
|
184
|
+
self.clocks = clocks
|
|
185
|
+
self.transitions = transitions
|
|
186
|
+
|
|
187
|
+
@property
|
|
188
|
+
def variables(self) -> List[Variable]:
|
|
189
|
+
return [var for v in self._variables for var in v.variables]
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
class Transition(Node):
|
|
193
|
+
def __init__(self, name, source, target, trigger, reset, deadline, condition, action, parent: Any):
|
|
194
|
+
super().__init__(parent)
|
|
195
|
+
self.name = name
|
|
196
|
+
self.source = source
|
|
197
|
+
self.target = target
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
class QualifiedNameWithWildcard(Node):
|
|
201
|
+
def __init__(self, name, parent: Any):
|
|
202
|
+
super().__init__(parent)
|
|
203
|
+
self.name = name
|
|
204
|
+
|
|
205
|
+
def __repr__(self):
|
|
206
|
+
return str(self.name) + "::*"
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
class Import(Node):
|
|
210
|
+
def __init__(self, name, parent: Any):
|
|
211
|
+
super().__init__(parent)
|
|
212
|
+
self.name = name
|
|
213
|
+
|
|
214
|
+
def __repr__(self):
|
|
215
|
+
return str(self.name)
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
class FunctionType(Node):
|
|
219
|
+
def __init__(self, source: ProductType, target: Optional[FunctionType], parent: Any):
|
|
220
|
+
super().__init__(parent)
|
|
221
|
+
self.source = source
|
|
222
|
+
self.target = target
|
|
223
|
+
|
|
224
|
+
def __repr__(self):
|
|
225
|
+
if self.target:
|
|
226
|
+
return f"{self.source} -> {self.target}"
|
|
227
|
+
return f"{self.source}"
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
class TypeRef(Node):
|
|
231
|
+
def __init__(self, kind, parent: Any):
|
|
232
|
+
super().__init__(parent)
|
|
233
|
+
self.type = kind
|
|
234
|
+
|
|
235
|
+
def __repr__(self):
|
|
236
|
+
return f"{self.type}"
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
class VectorType(Node):
|
|
240
|
+
# def __init__(self, source: Union[VectorDef, MatrixDef, TypeRef], parent: Any):
|
|
241
|
+
def __init__(self, source: Union[TypeRef], parent: Any):
|
|
242
|
+
super().__init__(parent)
|
|
243
|
+
self.source = source
|
|
244
|
+
|
|
245
|
+
def __repr__(self):
|
|
246
|
+
return f"{self.source}"
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
class ProductType(Node):
|
|
250
|
+
def __init__(self, types: List[Union[VectorType, TypeRef]], parent: Any):
|
|
251
|
+
super().__init__(parent)
|
|
252
|
+
self.types = types
|
|
253
|
+
|
|
254
|
+
def __repr__(self):
|
|
255
|
+
if len(self.types) > 1:
|
|
256
|
+
joined = "*".join(map(str, self.types))
|
|
257
|
+
return f"{joined}]"
|
|
258
|
+
return f"{self.types[0]}"
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
class SeqType(Node):
|
|
262
|
+
def __init__(self, domain, parent: Any):
|
|
263
|
+
super().__init__(parent)
|
|
264
|
+
self.domain = domain
|
|
265
|
+
|
|
266
|
+
def __repr__(self):
|
|
267
|
+
return f"Seq({self.domain})"
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
class Event(Node):
|
|
271
|
+
def __init__(self, name: str, broadcast: bool, kind: Type, parent: Any):
|
|
272
|
+
super().__init__(parent)
|
|
273
|
+
self.name = name
|
|
274
|
+
self.broadcast = broadcast
|
|
275
|
+
self.type = kind
|
|
276
|
+
|
|
277
|
+
def __repr__(self):
|
|
278
|
+
return f"Event: {self.name} {self.broadcast, self.type}"
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def all_concepts() -> List[ClassType]:
|
|
282
|
+
import inspect
|
|
283
|
+
return [
|
|
284
|
+
obj for _, obj in globals().items()
|
|
285
|
+
if inspect.isclass(obj) and obj.__module__ == __name__
|
|
286
|
+
]
|
robotransform/filters.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
def typename_to_port(type_name: str) -> str:
|
|
2
|
+
event_types = {"event"}
|
|
3
|
+
data_types = {"int", "integer", "real", "float", "double", "string"}
|
|
4
|
+
mixed_types = {"event_data", "event data"}
|
|
5
|
+
|
|
6
|
+
if type_name in event_types:
|
|
7
|
+
return "event"
|
|
8
|
+
if type_name in data_types:
|
|
9
|
+
return "data"
|
|
10
|
+
if type_name in mixed_types:
|
|
11
|
+
return "event data"
|
|
12
|
+
return "data"
|
robotransform/main.py
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
from dataclasses import dataclass, fields
|
|
2
|
+
from functools import lru_cache
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import arklog
|
|
6
|
+
from textx import metamodel_from_str
|
|
7
|
+
from jinja2 import Environment, FileSystemLoader, Template
|
|
8
|
+
import io
|
|
9
|
+
from typing import Union, Optional, Iterable, Iterator
|
|
10
|
+
|
|
11
|
+
from robotransform.concepts import all_concepts, RCPackage
|
|
12
|
+
from robotransform.filters import typename_to_port
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class Store:
|
|
17
|
+
packages: Iterable[Path]
|
|
18
|
+
|
|
19
|
+
def __iter__(self) -> Iterator[Path]:
|
|
20
|
+
for f in fields(self):
|
|
21
|
+
value = getattr(self, f.name)
|
|
22
|
+
if isinstance(value, Path):
|
|
23
|
+
yield value
|
|
24
|
+
continue
|
|
25
|
+
if isinstance(value, Iterable):
|
|
26
|
+
for item in value:
|
|
27
|
+
if isinstance(item, Path):
|
|
28
|
+
yield item
|
|
29
|
+
continue
|
|
30
|
+
|
|
31
|
+
def parse_imports(self, package: RCPackage, parent_path: Path, visited):
|
|
32
|
+
stack = [package]
|
|
33
|
+
while stack:
|
|
34
|
+
package = stack.pop()
|
|
35
|
+
for imp in package.imports:
|
|
36
|
+
name = imp.name.name.parts[-1]
|
|
37
|
+
if name in visited:
|
|
38
|
+
continue
|
|
39
|
+
path = parent_path / f"{name}.rct"
|
|
40
|
+
if not path.exists():
|
|
41
|
+
arklog.debug(f"Can't import {path}.")
|
|
42
|
+
continue
|
|
43
|
+
subpkg = parse_robochart(path)
|
|
44
|
+
visited[path] = subpkg
|
|
45
|
+
stack.append(subpkg)
|
|
46
|
+
return visited
|
|
47
|
+
|
|
48
|
+
def load(self, imports: bool = True) -> dict[Path, RCPackage]:
|
|
49
|
+
visited = {}
|
|
50
|
+
for path in self:
|
|
51
|
+
package = parse_robochart(path)
|
|
52
|
+
visited[path] = package
|
|
53
|
+
if imports:
|
|
54
|
+
visited |= self.parse_imports(package, path.parent, visited)
|
|
55
|
+
return visited
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@dataclass
|
|
59
|
+
class MapleKStore(Store):
|
|
60
|
+
monitor: Path
|
|
61
|
+
analysis: Path
|
|
62
|
+
plan: Path
|
|
63
|
+
legitimate: Path
|
|
64
|
+
execute: Path
|
|
65
|
+
knowledge: Path
|
|
66
|
+
|
|
67
|
+
def __init__(self, monitor: Path, analysis: Path, plan: Path, legitimate: Path, execute: Path, knowledge: Path,
|
|
68
|
+
additional: Optional[Iterable[Path]] = None):
|
|
69
|
+
self.monitor = monitor
|
|
70
|
+
self.analysis = analysis
|
|
71
|
+
self.plan = plan
|
|
72
|
+
self.legitimate = legitimate
|
|
73
|
+
self.execute = execute
|
|
74
|
+
self.knowledge = knowledge
|
|
75
|
+
self.packages = [monitor, analysis, plan, legitimate, execute, knowledge]
|
|
76
|
+
if additional is not None:
|
|
77
|
+
self.packages += additional
|
|
78
|
+
|
|
79
|
+
def verify(self):
|
|
80
|
+
pass
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@lru_cache(maxsize=1)
|
|
84
|
+
def get_robochart_metamodel(name: str = "robochart.tx"):
|
|
85
|
+
arklog.debug(f"Loading ({name}) metamodel.")
|
|
86
|
+
metamodel_path = Path(__file__).resolve().parent / name
|
|
87
|
+
metamodel = metamodel_from_str(metamodel_path.read_text(), classes=all_concepts(), memoization=True)
|
|
88
|
+
return metamodel
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@lru_cache(maxsize=1)
|
|
92
|
+
def parse_robochart(source: Path | str) -> RCPackage:
|
|
93
|
+
arklog.debug(f"Parsing ({source}).")
|
|
94
|
+
metamodel = get_robochart_metamodel()
|
|
95
|
+
data = source.read_text() if isinstance(source, Path) else source
|
|
96
|
+
return metamodel.model_from_str(data)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@lru_cache(maxsize=2)
|
|
100
|
+
def get_template(name: str) -> Template:
|
|
101
|
+
environment = Environment(loader=FileSystemLoader(Path(__file__).resolve().parent / "templates"))
|
|
102
|
+
environment.filters["typename_to_port"] = typename_to_port # For use with pipes
|
|
103
|
+
# environment.globals["typename_to_port"] = typename_to_port # For use as a function
|
|
104
|
+
templates = {
|
|
105
|
+
"messages": "messages.aadl",
|
|
106
|
+
"logical": "logical.aadl",
|
|
107
|
+
}
|
|
108
|
+
if found := templates.get(name):
|
|
109
|
+
return environment.get_template(found)
|
|
110
|
+
else:
|
|
111
|
+
raise ValueError(f"No template found for name '{name}'")
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def write_output(data: str, output: Optional[Union[io.TextIOBase, Path, str]] = None) -> str:
|
|
115
|
+
if output is None:
|
|
116
|
+
return data
|
|
117
|
+
if isinstance(output, (str, Path)):
|
|
118
|
+
output = Path(output)
|
|
119
|
+
output.parent.mkdir(parents=True, exist_ok=True)
|
|
120
|
+
output.write_text(data)
|
|
121
|
+
elif isinstance(output, io.TextIOBase):
|
|
122
|
+
output.write(data)
|
|
123
|
+
output.flush()
|
|
124
|
+
else:
|
|
125
|
+
raise TypeError(f"Unsupported output type: {type(output)}.")
|
|
126
|
+
return data
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def dump_messages(store: Store, output: Optional[Union[io.TextIOBase, Path, str]] = None) -> None:
|
|
130
|
+
template = get_template("messages")
|
|
131
|
+
output = output if output else Path("output/generated/messages/messages.aadl")
|
|
132
|
+
write_output(template.render(packages=store.load().values()), output)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def dump_logical(store: Store, output: Optional[Union[io.TextIOBase, Path, str]] = None) -> None:
|
|
136
|
+
template = get_template("logical")
|
|
137
|
+
output = output if output else Path("output/generated/LogicalArchitecture.aadl")
|
|
138
|
+
write_output(template.render(packages=store.load().values()), output)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def main():
|
|
142
|
+
pass
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
if __name__ == "__main__":
|
|
146
|
+
main()
|