fprime-cpp-codegen 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.
- fprime_cpp_codegen/__init__.py +162 -0
- fprime_cpp_codegen/body.py +448 -0
- fprime_cpp_codegen/builder/__init__.py +75 -0
- fprime_cpp_codegen/builder/base.py +55 -0
- fprime_cpp_codegen/builder/coercion.py +97 -0
- fprime_cpp_codegen/builder/decoration.py +201 -0
- fprime_cpp_codegen/builder/definitions.py +381 -0
- fprime_cpp_codegen/builder/document.py +155 -0
- fprime_cpp_codegen/builder/scopes.py +466 -0
- fprime_cpp_codegen/comments.py +144 -0
- fprime_cpp_codegen/doc.py +371 -0
- fprime_cpp_codegen/errors.py +25 -0
- fprime_cpp_codegen/formatting.py +89 -0
- fprime_cpp_codegen/fprime.py +149 -0
- fprime_cpp_codegen/lines.py +323 -0
- fprime_cpp_codegen/output.py +121 -0
- fprime_cpp_codegen/py.typed +0 -0
- fprime_cpp_codegen/writer.py +787 -0
- fprime_cpp_codegen-0.1.0.dist-info/METADATA +347 -0
- fprime_cpp_codegen-0.1.0.dist-info/RECORD +23 -0
- fprime_cpp_codegen-0.1.0.dist-info/WHEEL +5 -0
- fprime_cpp_codegen-0.1.0.dist-info/licenses/LICENSE +201 -0
- fprime_cpp_codegen-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
"""The C++ document IR: one ``.hpp`` file plus one or more ``.cpp`` files.
|
|
2
|
+
|
|
3
|
+
The layer the writers consume: a tree of frozen dataclasses.
|
|
4
|
+
:mod:`fprime_cpp_codegen.builder` assembles it for you.
|
|
5
|
+
|
|
6
|
+
A single :class:`CppDoc` describes a header and any number of source files. Every
|
|
7
|
+
definition with a body, and every block of raw lines, can name the ``.cpp`` file it
|
|
8
|
+
belongs to via ``cpp_file``; those naming nothing land in the document's default
|
|
9
|
+
``.cpp``. The header always gets everything, so one class can be split across
|
|
10
|
+
translation units without duplicating its declaration.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from dataclasses import dataclass, field
|
|
16
|
+
from enum import Enum
|
|
17
|
+
from typing import Protocol, runtime_checkable
|
|
18
|
+
|
|
19
|
+
from .lines import Line
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
"VOID",
|
|
23
|
+
"Class",
|
|
24
|
+
"ClassMember",
|
|
25
|
+
"Constructor",
|
|
26
|
+
"CppDoc",
|
|
27
|
+
"DefaultFileBanner",
|
|
28
|
+
"Definition",
|
|
29
|
+
"Destructor",
|
|
30
|
+
"FileBanner",
|
|
31
|
+
"Function",
|
|
32
|
+
"HppFile",
|
|
33
|
+
"Lines",
|
|
34
|
+
"Member",
|
|
35
|
+
"Namespace",
|
|
36
|
+
"Output",
|
|
37
|
+
"Param",
|
|
38
|
+
"SVQualifier",
|
|
39
|
+
"Type",
|
|
40
|
+
"Variable",
|
|
41
|
+
"as_type",
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class Output(Enum):
|
|
46
|
+
"""Which of a document's files a block of raw lines is emitted into."""
|
|
47
|
+
|
|
48
|
+
HPP = "hpp"
|
|
49
|
+
"""Header only."""
|
|
50
|
+
|
|
51
|
+
CPP = "cpp"
|
|
52
|
+
"""Source only."""
|
|
53
|
+
|
|
54
|
+
BOTH = "both"
|
|
55
|
+
"""Both the header and the source."""
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class SVQualifier(Enum):
|
|
59
|
+
"""A function's static/virtual specifier, mutually exclusive by construction.
|
|
60
|
+
|
|
61
|
+
``OVERRIDE`` and ``FINAL`` render as trailing specifiers; ``STATIC``, ``VIRTUAL``
|
|
62
|
+
and ``PURE_VIRTUAL`` as leading ones. ``PURE_VIRTUAL`` also terminates the
|
|
63
|
+
declaration with ``= 0``.
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
NONE = "none"
|
|
67
|
+
STATIC = "static"
|
|
68
|
+
VIRTUAL = "virtual"
|
|
69
|
+
PURE_VIRTUAL = "pure-virtual"
|
|
70
|
+
OVERRIDE = "override"
|
|
71
|
+
FINAL = "final"
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@dataclass(frozen=True)
|
|
75
|
+
class Type:
|
|
76
|
+
"""A C++ type.
|
|
77
|
+
|
|
78
|
+
The source file may need a different spelling from the header, typically because
|
|
79
|
+
the header sits inside the namespace qualifying the name. ``cpp_type`` supplies
|
|
80
|
+
it; when absent, both files use ``hpp_type``.
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
hpp_type: str
|
|
84
|
+
cpp_type: str | None = None
|
|
85
|
+
|
|
86
|
+
@property
|
|
87
|
+
def cpp(self) -> str:
|
|
88
|
+
"""The spelling to use in a ``.cpp`` file."""
|
|
89
|
+
return self.cpp_type if self.cpp_type is not None else self.hpp_type
|
|
90
|
+
|
|
91
|
+
@property
|
|
92
|
+
def hpp(self) -> str:
|
|
93
|
+
"""The spelling to use in the ``.hpp`` file."""
|
|
94
|
+
return self.hpp_type
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
#: The ``void`` type, and the default return type of a :class:`Function`.
|
|
98
|
+
VOID = Type("void")
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def as_type(t: Type | str | tuple[str, str]) -> Type:
|
|
102
|
+
"""Coerce a type specification to a :class:`Type`.
|
|
103
|
+
|
|
104
|
+
A single string is used in both files. A ``(header, source)`` pair supplies the
|
|
105
|
+
two spellings a nested type needs: ``Status`` inside the class,
|
|
106
|
+
``MyClass::Status`` in the source file where the return type precedes
|
|
107
|
+
``MyClass::``.
|
|
108
|
+
"""
|
|
109
|
+
if isinstance(t, Type):
|
|
110
|
+
return t
|
|
111
|
+
if isinstance(t, tuple):
|
|
112
|
+
hpp, cpp = t
|
|
113
|
+
return Type(hpp, cpp)
|
|
114
|
+
return Type(t)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
@dataclass(frozen=True)
|
|
118
|
+
class Param:
|
|
119
|
+
"""A formal parameter of a function, constructor, or destructor."""
|
|
120
|
+
|
|
121
|
+
type: Type
|
|
122
|
+
name: str
|
|
123
|
+
comment: str | None = None
|
|
124
|
+
"""A doxygen post-comment, rendered after the parameter in the header."""
|
|
125
|
+
|
|
126
|
+
default: str | None = None
|
|
127
|
+
"""A default argument, rendered in the header declaration only."""
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
@dataclass(frozen=True)
|
|
131
|
+
class Lines:
|
|
132
|
+
"""A block of raw, already-rendered C++ lines.
|
|
133
|
+
|
|
134
|
+
Access tags, banner comments, ``#include`` directives, enums and structs are all
|
|
135
|
+
lines. ``output`` decides which files see them.
|
|
136
|
+
"""
|
|
137
|
+
|
|
138
|
+
content: list[Line] = field(default_factory=list)
|
|
139
|
+
output: Output = Output.HPP
|
|
140
|
+
cpp_file: str | None = None
|
|
141
|
+
"""Restrict source-file output to this ``.cpp`` base name. Header output is
|
|
142
|
+
unaffected."""
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
@dataclass(frozen=True, kw_only=True)
|
|
146
|
+
class Definition:
|
|
147
|
+
"""Fields shared by everything that has a body.
|
|
148
|
+
|
|
149
|
+
Keyword-only, so subclasses can take their own defining field as the first
|
|
150
|
+
positional argument.
|
|
151
|
+
|
|
152
|
+
``deleted`` and ``defaulted`` replace the body with ``= delete`` or
|
|
153
|
+
``= default``; ``inline_body`` and ``template`` move the definition into the
|
|
154
|
+
header, since a template's definition must be visible at every use. In all four
|
|
155
|
+
cases the source file gets nothing.
|
|
156
|
+
"""
|
|
157
|
+
|
|
158
|
+
body: list[Line] = field(default_factory=list)
|
|
159
|
+
comment: str | None = None
|
|
160
|
+
cpp_file: str | None = None
|
|
161
|
+
"""Which ``.cpp`` file the definition goes in. ``None`` means the default."""
|
|
162
|
+
|
|
163
|
+
noexcept: bool = False
|
|
164
|
+
deleted: bool = False
|
|
165
|
+
defaulted: bool = False
|
|
166
|
+
inline_body: bool = False
|
|
167
|
+
|
|
168
|
+
template: str | None = None
|
|
169
|
+
"""A template parameter list without the keyword, e.g. ``"typename T"``.
|
|
170
|
+
Implies :attr:`inline_body`."""
|
|
171
|
+
|
|
172
|
+
@property
|
|
173
|
+
def defined_in_header(self) -> bool:
|
|
174
|
+
return self.inline_body or self.template is not None
|
|
175
|
+
|
|
176
|
+
@property
|
|
177
|
+
def has_definition(self) -> bool:
|
|
178
|
+
"""Whether there is a body to write at all."""
|
|
179
|
+
return not (self.deleted or self.defaulted)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
@dataclass(frozen=True)
|
|
183
|
+
class Function(Definition):
|
|
184
|
+
"""A C++ function, either free-standing or a class member."""
|
|
185
|
+
|
|
186
|
+
name: str
|
|
187
|
+
params: list[Param] = field(default_factory=list)
|
|
188
|
+
ret_type: Type = VOID
|
|
189
|
+
"""``Type("")`` emits no return type, as a conversion operator needs."""
|
|
190
|
+
|
|
191
|
+
sv: SVQualifier = SVQualifier.NONE
|
|
192
|
+
const: bool = False
|
|
193
|
+
constexpr: bool = False
|
|
194
|
+
inline: bool = False
|
|
195
|
+
|
|
196
|
+
@property
|
|
197
|
+
def defined_in_header(self) -> bool:
|
|
198
|
+
"""``constexpr`` and ``inline`` force the definition into the header: both
|
|
199
|
+
require it visible in every translation unit that uses the function."""
|
|
200
|
+
return super().defined_in_header or self.constexpr or self.inline
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
@dataclass(frozen=True)
|
|
204
|
+
class Constructor(Definition):
|
|
205
|
+
"""A C++ constructor. Its name is taken from the enclosing class."""
|
|
206
|
+
|
|
207
|
+
params: list[Param] = field(default_factory=list)
|
|
208
|
+
initializers: list[str] = field(default_factory=list)
|
|
209
|
+
"""Member-initializer-list entries, e.g. ``"m_size(size)"``, rendered wherever the
|
|
210
|
+
definition goes."""
|
|
211
|
+
|
|
212
|
+
explicit: bool = False
|
|
213
|
+
constexpr: bool = False
|
|
214
|
+
|
|
215
|
+
@property
|
|
216
|
+
def defined_in_header(self) -> bool:
|
|
217
|
+
return super().defined_in_header or self.constexpr
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
@dataclass(frozen=True)
|
|
221
|
+
class Destructor(Definition):
|
|
222
|
+
"""A C++ destructor. Its name is taken from the enclosing class."""
|
|
223
|
+
|
|
224
|
+
virtual: bool = False
|
|
225
|
+
override: bool = False
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
@dataclass(frozen=True)
|
|
229
|
+
class Class:
|
|
230
|
+
"""A C++ class, possibly nested inside another class."""
|
|
231
|
+
|
|
232
|
+
name: str
|
|
233
|
+
superclass_decls: str | None = None
|
|
234
|
+
"""Everything after the colon, verbatim, e.g. ``"public Fw::Serializable"``.
|
|
235
|
+
Multiple bases go in one comma-separated string."""
|
|
236
|
+
|
|
237
|
+
members: list[ClassMember] = field(default_factory=list)
|
|
238
|
+
comment: str | None = None
|
|
239
|
+
final: bool = False
|
|
240
|
+
template: str | None = None
|
|
241
|
+
"""A template parameter list without the keyword, e.g. ``"typename T"``. A
|
|
242
|
+
templated class defines all its members in the header, so its source file output
|
|
243
|
+
is empty."""
|
|
244
|
+
|
|
245
|
+
struct: bool = False
|
|
246
|
+
"""Emit ``struct``, making members public by default."""
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
@dataclass(frozen=True)
|
|
250
|
+
class Variable:
|
|
251
|
+
"""A variable: a class data member, or a constant or global at namespace scope.
|
|
252
|
+
|
|
253
|
+
Where the initialiser goes depends on the kind of variable:
|
|
254
|
+
|
|
255
|
+
* A non-static data member takes its initialiser in the class body.
|
|
256
|
+
* A static data member is only declared in the class; the definition, with the
|
|
257
|
+
initialiser, goes in a source file. A ``constexpr`` static is initialised in
|
|
258
|
+
the class and needs no out-of-line definition unless something takes its
|
|
259
|
+
address -- see ``out_of_line_definition``.
|
|
260
|
+
* At namespace scope, ``extern`` splits declaration from definition the same way.
|
|
261
|
+
Without it the variable is defined where it is declared, as a ``constexpr`` or
|
|
262
|
+
``const`` constant in a header should be.
|
|
263
|
+
"""
|
|
264
|
+
|
|
265
|
+
name: str
|
|
266
|
+
type: Type
|
|
267
|
+
init: str | None = None
|
|
268
|
+
array: str | None = None
|
|
269
|
+
"""An array extent, without brackets, e.g. ``"SIZE"`` for ``m_data[SIZE]``."""
|
|
270
|
+
|
|
271
|
+
comment: str | None = None
|
|
272
|
+
static: bool = False
|
|
273
|
+
const: bool = False
|
|
274
|
+
constexpr: bool = False
|
|
275
|
+
mutable: bool = False
|
|
276
|
+
extern: bool = False
|
|
277
|
+
out_of_line_definition: bool = False
|
|
278
|
+
"""Also emit a source-file definition for a ``constexpr`` static member."""
|
|
279
|
+
|
|
280
|
+
cpp_file: str | None = None
|
|
281
|
+
|
|
282
|
+
@property
|
|
283
|
+
def declarator(self) -> str:
|
|
284
|
+
"""The name plus any array extent, e.g. ``"m_data[SIZE]"``."""
|
|
285
|
+
return f"{self.name}[{self.array}]" if self.array is not None else self.name
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
@dataclass(frozen=True)
|
|
289
|
+
class Namespace:
|
|
290
|
+
"""A C++ namespace. Nest instances to nest namespaces."""
|
|
291
|
+
|
|
292
|
+
name: str
|
|
293
|
+
members: list[Member] = field(default_factory=list)
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
#: What may appear at document or namespace scope.
|
|
297
|
+
Member = Class | Lines | Function | Namespace | Variable
|
|
298
|
+
|
|
299
|
+
#: What may appear at class scope. Namespaces may not; constructors and
|
|
300
|
+
#: destructors may.
|
|
301
|
+
ClassMember = Class | Lines | Function | Constructor | Destructor | Variable
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
@runtime_checkable
|
|
305
|
+
class FileBanner(Protocol):
|
|
306
|
+
"""Supplies the ``\\title``/``\\author``/``\\brief`` lines atop each file."""
|
|
307
|
+
|
|
308
|
+
def title(self, file_name: str) -> str:
|
|
309
|
+
"""The ``\\title`` text for ``file_name``."""
|
|
310
|
+
...
|
|
311
|
+
|
|
312
|
+
def author(self, file_name: str) -> str:
|
|
313
|
+
"""The ``\\author`` text for ``file_name``."""
|
|
314
|
+
...
|
|
315
|
+
|
|
316
|
+
def description(self, file_name: str, generic_description: str) -> str:
|
|
317
|
+
"""The ``\\brief`` text. ``generic_description`` is the writer's own phrasing,
|
|
318
|
+
e.g. ``"hpp file for my component"``."""
|
|
319
|
+
...
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
@dataclass(frozen=True)
|
|
323
|
+
class DefaultFileBanner:
|
|
324
|
+
"""The banner used when a document does not supply one."""
|
|
325
|
+
|
|
326
|
+
tool_name: str | None = None
|
|
327
|
+
|
|
328
|
+
def title(self, file_name: str) -> str:
|
|
329
|
+
return file_name
|
|
330
|
+
|
|
331
|
+
def author(self, file_name: str) -> str:
|
|
332
|
+
return f"Generated by {self.tool_name or 'fpp tools'}"
|
|
333
|
+
|
|
334
|
+
def description(self, file_name: str, generic_description: str) -> str:
|
|
335
|
+
return generic_description
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
@dataclass(frozen=True)
|
|
339
|
+
class HppFile:
|
|
340
|
+
"""The header file of a document."""
|
|
341
|
+
|
|
342
|
+
name: str
|
|
343
|
+
"""The file name including extension, e.g. ``"MyClass.hpp"``."""
|
|
344
|
+
|
|
345
|
+
include_guard: str
|
|
346
|
+
"""The include-guard macro, e.g. ``"Fw_MyClass_HPP"``."""
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
@dataclass(frozen=True)
|
|
350
|
+
class CppDoc:
|
|
351
|
+
"""A C++ document: one header, one default source file, and the members."""
|
|
352
|
+
|
|
353
|
+
description: str
|
|
354
|
+
"""Used in the file banners, as ``"hpp file for <description>"``."""
|
|
355
|
+
|
|
356
|
+
hpp_file: HppFile
|
|
357
|
+
cpp_file_name: str
|
|
358
|
+
"""The default source file name including extension, e.g. ``"MyClass.cpp"``."""
|
|
359
|
+
|
|
360
|
+
members: list[Member] = field(default_factory=list)
|
|
361
|
+
tool_name: str | None = None
|
|
362
|
+
banner: FileBanner | None = None
|
|
363
|
+
|
|
364
|
+
@property
|
|
365
|
+
def file_banner(self) -> FileBanner:
|
|
366
|
+
"""The document's banner, falling back to :class:`DefaultFileBanner`."""
|
|
367
|
+
return (
|
|
368
|
+
self.banner
|
|
369
|
+
if self.banner is not None
|
|
370
|
+
else DefaultFileBanner(self.tool_name)
|
|
371
|
+
)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""The exception hierarchy.
|
|
2
|
+
|
|
3
|
+
Everything raised here derives from :class:`CppCodegenError`, so a generator can
|
|
4
|
+
catch one type.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
__all__ = ["CppCodegenError", "ScopeError", "ValidationError"]
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class CppCodegenError(Exception):
|
|
13
|
+
"""Base class for every error raised here."""
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ScopeError(CppCodegenError):
|
|
17
|
+
"""A builder scope was used in a way its structure does not allow.
|
|
18
|
+
|
|
19
|
+
``else`` with no preceding ``if``, a ``case`` outside a ``switch``, building a body
|
|
20
|
+
with a scope still open.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ValidationError(CppCodegenError):
|
|
25
|
+
"""A document or declaration is malformed and could not produce valid C++."""
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""Passing generated files through an external formatter.
|
|
2
|
+
|
|
3
|
+
``clang-format`` is lexical: it needs no compilation database, include paths, or
|
|
4
|
+
working compiler, so generated text with missing includes and unknown types
|
|
5
|
+
formats fine. It cannot tell a type name from a variable, so ``a * b;`` becomes
|
|
6
|
+
``a *b;``.
|
|
7
|
+
|
|
8
|
+
A formatter takes the file text and its name. ``clang-format`` locates the
|
|
9
|
+
governing ``.clang-format`` by searching upward from the file's path, so the name
|
|
10
|
+
determines which project style applies.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import shutil
|
|
16
|
+
import subprocess
|
|
17
|
+
from dataclasses import dataclass
|
|
18
|
+
from typing import Protocol, runtime_checkable
|
|
19
|
+
|
|
20
|
+
from .errors import CppCodegenError
|
|
21
|
+
|
|
22
|
+
__all__ = ["ClangFormat", "Formatter"]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@runtime_checkable
|
|
26
|
+
class Formatter(Protocol):
|
|
27
|
+
"""Post-processes one generated file's text."""
|
|
28
|
+
|
|
29
|
+
def __call__(self, text: str, file_name: str) -> str:
|
|
30
|
+
"""Return ``text`` reformatted. ``file_name`` is the name it is written as."""
|
|
31
|
+
...
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass(frozen=True)
|
|
35
|
+
class ClangFormat:
|
|
36
|
+
"""Runs ``clang-format`` over generated text, through stdin and stdout::
|
|
37
|
+
|
|
38
|
+
doc.write("build-artifacts", formatter=ClangFormat())
|
|
39
|
+
|
|
40
|
+
With no ``style``, ``clang-format``'s default applies, which searches for a
|
|
41
|
+
``.clang-format`` upward from the generated file's directory. ``style`` may be
|
|
42
|
+
a named style such as ``"LLVM"`` or inline YAML like
|
|
43
|
+
``"{BasedOnStyle: LLVM, ColumnLimit: 120}"``.
|
|
44
|
+
|
|
45
|
+
This reformats everything: a ``Ret Class ::`` line and its indented signature
|
|
46
|
+
collapse onto one, and namespace bodies lose their indentation.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
executable: str = "clang-format"
|
|
50
|
+
style: str | None = None
|
|
51
|
+
timeout: float = 30.0
|
|
52
|
+
|
|
53
|
+
def available(self) -> bool:
|
|
54
|
+
"""Whether the executable can be found on ``PATH``."""
|
|
55
|
+
return shutil.which(self.executable) is not None
|
|
56
|
+
|
|
57
|
+
def version(self) -> str:
|
|
58
|
+
"""The formatter's version string."""
|
|
59
|
+
return self._run(["--version"], text=None).strip()
|
|
60
|
+
|
|
61
|
+
def __call__(self, text: str, file_name: str) -> str:
|
|
62
|
+
"""Return ``text`` as ``clang-format`` writes it for ``file_name``."""
|
|
63
|
+
args = [f"--assume-filename={file_name}"]
|
|
64
|
+
if self.style is not None:
|
|
65
|
+
args.append(f"--style={self.style}")
|
|
66
|
+
return self._run(args, text=text)
|
|
67
|
+
|
|
68
|
+
def _run(self, args: list[str], *, text: str | None) -> str:
|
|
69
|
+
try:
|
|
70
|
+
result = subprocess.run(
|
|
71
|
+
[self.executable, *args],
|
|
72
|
+
input=text,
|
|
73
|
+
capture_output=True,
|
|
74
|
+
text=True,
|
|
75
|
+
timeout=self.timeout,
|
|
76
|
+
)
|
|
77
|
+
except FileNotFoundError as exc:
|
|
78
|
+
raise CppCodegenError(
|
|
79
|
+
f"{self.executable!r} was not found on PATH; install clang-format or "
|
|
80
|
+
"pass a different executable to ClangFormat"
|
|
81
|
+
) from exc
|
|
82
|
+
except subprocess.TimeoutExpired as exc:
|
|
83
|
+
raise CppCodegenError(
|
|
84
|
+
f"{self.executable!r} did not finish within {self.timeout}s"
|
|
85
|
+
) from exc
|
|
86
|
+
if result.returncode != 0:
|
|
87
|
+
detail = result.stderr.strip() or f"exit status {result.returncode}"
|
|
88
|
+
raise CppCodegenError(f"{self.executable!r} failed: {detail}")
|
|
89
|
+
return result.stdout
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"""F Prime idioms, kept separate from the general-purpose layers.
|
|
2
|
+
|
|
3
|
+
The conventions that show up in every F Prime autocoded file, collected so a
|
|
4
|
+
generator does not have to retype them. All strings and lines; no knowledge of the
|
|
5
|
+
FPP model.
|
|
6
|
+
|
|
7
|
+
Import it explicitly; the core API stays framework-neutral.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from collections.abc import Sequence
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
from .doc import ClassMember, Lines, Member, Output
|
|
16
|
+
from .lines import Line, blank, lines, wrap_in_scope
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
"BUILD_UT",
|
|
20
|
+
"FW_ENABLE_TEXT_LOGGING",
|
|
21
|
+
"STANDARD_SYSTEM_CPP_HEADERS",
|
|
22
|
+
"STANDARD_SYSTEM_HPP_HEADERS",
|
|
23
|
+
"STANDARD_USER_CPP_HEADERS",
|
|
24
|
+
"STANDARD_USER_HPP_HEADERS",
|
|
25
|
+
"buffer_name",
|
|
26
|
+
"external_string_decl",
|
|
27
|
+
"guard_class_members_for_text_log",
|
|
28
|
+
"guard_class_members_for_unit_test",
|
|
29
|
+
"guard_members_for_text_log",
|
|
30
|
+
"guard_members_for_unit_test",
|
|
31
|
+
"write_assert",
|
|
32
|
+
"write_ostream_operator",
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
#: The preprocessor condition guarding text-log support.
|
|
36
|
+
FW_ENABLE_TEXT_LOGGING = "#if FW_ENABLE_TEXT_LOGGING"
|
|
37
|
+
|
|
38
|
+
#: The preprocessor condition guarding unit-test-only code.
|
|
39
|
+
BUILD_UT = "#ifdef BUILD_UT"
|
|
40
|
+
|
|
41
|
+
#: Project headers an autocoded F Prime header normally needs.
|
|
42
|
+
STANDARD_USER_HPP_HEADERS = [
|
|
43
|
+
f'#include "{path}"'
|
|
44
|
+
for path in (
|
|
45
|
+
"Fw/FPrimeBasicTypes.hpp",
|
|
46
|
+
"Fw/Types/ExternalString.hpp",
|
|
47
|
+
"Fw/Types/Serializable.hpp",
|
|
48
|
+
"Fw/Types/String.hpp",
|
|
49
|
+
)
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
#: System headers an autocoded F Prime header normally needs. Empty, but kept so a
|
|
53
|
+
#: generator can splice it in unconditionally.
|
|
54
|
+
STANDARD_SYSTEM_HPP_HEADERS: list[str] = []
|
|
55
|
+
|
|
56
|
+
#: Project headers an autocoded F Prime source file normally needs.
|
|
57
|
+
STANDARD_USER_CPP_HEADERS = ['#include "Fw/Types/Assert.hpp"']
|
|
58
|
+
|
|
59
|
+
#: System headers an autocoded F Prime source file normally needs.
|
|
60
|
+
STANDARD_SYSTEM_CPP_HEADERS: list[str] = []
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def write_assert(condition: str, *args: str) -> list[Line]:
|
|
64
|
+
"""Render an ``FW_ASSERT``.
|
|
65
|
+
|
|
66
|
+
Extra arguments become the assert's reported values, making a flight-side
|
|
67
|
+
assertion diagnosable after the fact. Each must already be spelled as a C++
|
|
68
|
+
expression, casts included.
|
|
69
|
+
"""
|
|
70
|
+
joined = ", ".join([condition, *args])
|
|
71
|
+
return lines(f"FW_ASSERT({joined});")
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def buffer_name(name: str) -> str:
|
|
75
|
+
"""The backing-array name for a generated ``Fw::ExternalString``."""
|
|
76
|
+
return f"__fprime_ac_{name}_buffer"
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def external_string_decl(name: str, size: str) -> list[Line]:
|
|
80
|
+
"""Declare an ``Fw::ExternalString`` over a stack buffer of ``size`` characters.
|
|
81
|
+
|
|
82
|
+
F Prime avoids dynamic memory, so a string-typed local is a fixed char array plus
|
|
83
|
+
a view onto it, not an owning string object.
|
|
84
|
+
"""
|
|
85
|
+
buf = buffer_name(name)
|
|
86
|
+
return lines(f"""|char {buf}[Fw::StringBase::BUFFER_SIZE({size})];
|
|
87
|
+
|Fw::ExternalString {name}({buf}, sizeof {buf});""")
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _guard_members(directive: str, members: Sequence[Any], output: Output) -> list[Any]:
|
|
91
|
+
"""Bracket a non-empty run of members with ``directive`` and ``#endif``."""
|
|
92
|
+
if not members:
|
|
93
|
+
return []
|
|
94
|
+
return [
|
|
95
|
+
Lines(lines(f"\n{directive}"), output),
|
|
96
|
+
*members,
|
|
97
|
+
Lines([blank(), *lines("#endif")], output),
|
|
98
|
+
]
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def guard_class_members_for_text_log(
|
|
102
|
+
members: Sequence[ClassMember], output: Output = Output.BOTH
|
|
103
|
+
) -> list[ClassMember]:
|
|
104
|
+
"""Bracket class members with ``#if FW_ENABLE_TEXT_LOGGING``."""
|
|
105
|
+
return _guard_members(FW_ENABLE_TEXT_LOGGING, members, output)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def guard_members_for_text_log(
|
|
109
|
+
members: Sequence[Member], output: Output = Output.BOTH
|
|
110
|
+
) -> list[Member]:
|
|
111
|
+
"""Bracket document members with ``#if FW_ENABLE_TEXT_LOGGING``."""
|
|
112
|
+
return _guard_members(FW_ENABLE_TEXT_LOGGING, members, output)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def guard_class_members_for_unit_test(
|
|
116
|
+
members: Sequence[ClassMember], output: Output = Output.BOTH
|
|
117
|
+
) -> list[ClassMember]:
|
|
118
|
+
"""Bracket class members with ``#ifdef BUILD_UT``."""
|
|
119
|
+
return _guard_members(BUILD_UT, members, output)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def guard_members_for_unit_test(
|
|
123
|
+
members: Sequence[Member], output: Output = Output.BOTH
|
|
124
|
+
) -> list[Member]:
|
|
125
|
+
"""Bracket document members with ``#ifdef BUILD_UT``."""
|
|
126
|
+
return _guard_members(BUILD_UT, members, output)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def write_ostream_operator(name: str, body: Sequence[Line]) -> list[ClassMember]:
|
|
130
|
+
"""Declare and define a friend ``operator<<`` for ``name``, unit-test only.
|
|
131
|
+
|
|
132
|
+
Returns the header declaration and the source-file definition, both inside a
|
|
133
|
+
``BUILD_UT`` guard, ready to splice into a class's member list.
|
|
134
|
+
"""
|
|
135
|
+
declaration = Lines(lines(f"""|
|
|
136
|
+
|//! Ostream operator
|
|
137
|
+
|friend std::ostream& operator<<(
|
|
138
|
+
| std::ostream& os, //!< The ostream
|
|
139
|
+
| const {name}& obj //!< The object
|
|
140
|
+
|);"""))
|
|
141
|
+
definition = Lines(
|
|
142
|
+
wrap_in_scope(
|
|
143
|
+
f"\nstd::ostream& operator<<(std::ostream& os, const {name}& obj) {{",
|
|
144
|
+
body,
|
|
145
|
+
"}",
|
|
146
|
+
),
|
|
147
|
+
Output.CPP,
|
|
148
|
+
)
|
|
149
|
+
return guard_class_members_for_unit_test([declaration, definition])
|