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.
@@ -0,0 +1,162 @@
1
+ """Generate C++ header and source files from Python.
2
+
3
+ The package is layered, and you can enter at whichever level suits the job:
4
+
5
+ * :mod:`~fprime_cpp_codegen.builder` -- the builder API. Start with
6
+ :class:`CppDocBuilder`.
7
+ * :mod:`~fprime_cpp_codegen.body` -- :class:`Body`, for function bodies.
8
+ * :mod:`~fprime_cpp_codegen.doc` -- the document IR, to build the tree directly.
9
+ * :mod:`~fprime_cpp_codegen.writer` -- the visitors rendering the IR to lines.
10
+ * :mod:`~fprime_cpp_codegen.comments` -- comment and banner formatting.
11
+ * :mod:`~fprime_cpp_codegen.lines` -- the line model everything is built on.
12
+ * :mod:`~fprime_cpp_codegen.output` -- rendering to text and to disk.
13
+ * :mod:`~fprime_cpp_codegen.formatting` -- optional post-processing through
14
+ ``clang-format``.
15
+ * :mod:`~fprime_cpp_codegen.fprime` -- F Prime conventions. Import it explicitly;
16
+ nothing else in the package depends on it.
17
+
18
+ Nothing here knows about the FPP model.
19
+
20
+ A minimal example::
21
+
22
+ from fprime_cpp_codegen import CppDocBuilder
23
+
24
+ doc = CppDocBuilder("Greeter", description="a greeter", namespaces=["Demo"])
25
+ doc.include("Fw/FPrimeBasicTypes.hpp")
26
+
27
+ with doc.namespace("Demo") as ns:
28
+ with ns.class_("Greeter") as cls:
29
+ with cls.public():
30
+ fn = cls.function("greet", params=[("const char*", "name")])
31
+ fn.body.line('printf("hello %s\\n", name);')
32
+
33
+ print(doc.render_hpp())
34
+ print(doc.render_cpp())
35
+ """
36
+
37
+ from __future__ import annotations
38
+
39
+ from .body import Body, Code, Switch, stmts
40
+ from .builder import (
41
+ AccessSection,
42
+ ClassBuilder,
43
+ ConstructorBuilder,
44
+ CppDocBuilder,
45
+ DestructorBuilder,
46
+ EnumBuilder,
47
+ FunctionBuilder,
48
+ NamespaceBuilder,
49
+ Radix,
50
+ )
51
+ from .doc import (
52
+ VOID,
53
+ Class,
54
+ ClassMember,
55
+ Constructor,
56
+ CppDoc,
57
+ DefaultFileBanner,
58
+ Definition,
59
+ Destructor,
60
+ FileBanner,
61
+ Function,
62
+ HppFile,
63
+ Lines,
64
+ Member,
65
+ Namespace,
66
+ Output,
67
+ Param,
68
+ SVQualifier,
69
+ Type,
70
+ Variable,
71
+ as_type,
72
+ )
73
+ from .errors import CppCodegenError, ScopeError, ValidationError
74
+ from .formatting import ClangFormat, Formatter
75
+ from .lines import (
76
+ INDENT_INCREMENT,
77
+ IndentMode,
78
+ Line,
79
+ blank,
80
+ line,
81
+ lines,
82
+ render,
83
+ wrap_in_scope,
84
+ )
85
+ from .output import WriteResult, collect_cpp_files, doc_files, write_doc
86
+ from .writer import (
87
+ Context,
88
+ CppWriter,
89
+ DocWriter,
90
+ HppWriter,
91
+ cpp_lines,
92
+ hpp_lines,
93
+ render_cpp,
94
+ render_hpp,
95
+ )
96
+
97
+ __all__ = [
98
+ # Line model
99
+ "INDENT_INCREMENT",
100
+ "IndentMode",
101
+ "Line",
102
+ "blank",
103
+ "line",
104
+ "lines",
105
+ "render",
106
+ "wrap_in_scope",
107
+ # Document IR
108
+ "VOID",
109
+ "Class",
110
+ "ClassMember",
111
+ "Constructor",
112
+ "CppDoc",
113
+ "DefaultFileBanner",
114
+ "Definition",
115
+ "Destructor",
116
+ "FileBanner",
117
+ "Function",
118
+ "HppFile",
119
+ "Lines",
120
+ "Member",
121
+ "Namespace",
122
+ "Output",
123
+ "Param",
124
+ "Radix",
125
+ "SVQualifier",
126
+ "Type",
127
+ "Variable",
128
+ "as_type",
129
+ # Builders
130
+ "AccessSection",
131
+ "Body",
132
+ "ClassBuilder",
133
+ "Code",
134
+ "ConstructorBuilder",
135
+ "CppDocBuilder",
136
+ "DestructorBuilder",
137
+ "EnumBuilder",
138
+ "FunctionBuilder",
139
+ "NamespaceBuilder",
140
+ "Switch",
141
+ "stmts",
142
+ # Writers
143
+ "Context",
144
+ "CppWriter",
145
+ "DocWriter",
146
+ "HppWriter",
147
+ "cpp_lines",
148
+ "hpp_lines",
149
+ "render_cpp",
150
+ "render_hpp",
151
+ # Output
152
+ "ClangFormat",
153
+ "Formatter",
154
+ "WriteResult",
155
+ "collect_cpp_files",
156
+ "doc_files",
157
+ "write_doc",
158
+ # Errors
159
+ "CppCodegenError",
160
+ "ScopeError",
161
+ "ValidationError",
162
+ ]
@@ -0,0 +1,448 @@
1
+ """Building function bodies out of C++ statements.
2
+
3
+ A :class:`Body` accumulates lines. Statements append to it; control-flow scopes
4
+ are context managers that indent everything written inside them::
5
+
6
+ body = Body()
7
+ body.line("U32 total = 0;")
8
+ with body.for_("U32 i = 0", "i < n", "i++"):
9
+ body.line("total += m_data[i];")
10
+ body.line("return total;")
11
+
12
+ A ``Body`` is a value: build one anywhere, return it from a helper, and splice it
13
+ in with :meth:`Body.extend`.
14
+
15
+ This class covers structure -- scopes, nesting, control flow. Individual
16
+ statements go through :meth:`Body.line`. :meth:`Body.raw` takes lines from
17
+ elsewhere, such as :func:`fprime_cpp_codegen.fprime.write_assert`.
18
+
19
+ Scopes emit even when their body turns out empty, since a vanishing ``if`` would
20
+ re-point the ``else`` that follows it. Pass ``omit_if_empty=True`` to let the scope
21
+ disappear.
22
+ """
23
+
24
+ from __future__ import annotations
25
+
26
+ from collections.abc import Generator, Iterable, Iterator, Sequence
27
+ from contextlib import AbstractContextManager, contextmanager
28
+ from dataclasses import dataclass, field
29
+ from typing import TypeAlias
30
+
31
+ from .comments import (
32
+ write_banner_comment,
33
+ write_comment,
34
+ write_comment_body,
35
+ write_doxygen_comment,
36
+ )
37
+ from .errors import ScopeError, ValidationError
38
+ from .lines import Line, blank, indent_lines
39
+ from .lines import line as _line
40
+ from .lines import lines as _lines
41
+ from .lines import render as _render
42
+
43
+ __all__ = ["Body", "Code", "Switch", "stmts"]
44
+
45
+ #: Anything usable as a run of C++ statements. ``None`` contributes nothing, so
46
+ #: ``b.add(frag if condition else None)`` needs no branch. A ``str`` is
47
+ #: margin-stripped and taken verbatim, with no punctuation added.
48
+ Code: TypeAlias = "None | str | Line | Body | Sequence[Code]"
49
+
50
+
51
+ def stmts(*code: Code) -> list[Line]:
52
+ """Coerce statement-shaped values to lines, flattening nested sequences."""
53
+ out: list[Line] = []
54
+ for item in code:
55
+ if item is None:
56
+ continue
57
+ if isinstance(item, Body):
58
+ out.extend(item.build())
59
+ elif isinstance(item, Line):
60
+ out.append(item)
61
+ elif isinstance(item, str):
62
+ out.extend(_lines(item))
63
+ elif isinstance(item, Sequence):
64
+ out.extend(stmts(*item))
65
+ else:
66
+ raise ValidationError(f"not usable as C++ statements: {item!r}")
67
+ return out
68
+
69
+
70
+ #: Statements after which control does not fall through.
71
+ _TERMINATING_KEYWORDS = ("return", "throw", "goto")
72
+
73
+
74
+ def _terminates(ll: Sequence[Line]) -> bool:
75
+ """Whether ``ll`` ends in a statement that unconditionally transfers control.
76
+
77
+ Conservative: a miss costs an unreachable ``break;``, while a false positive
78
+ would drop a ``break`` and turn a switch arm into a fallthrough. Only shapes
79
+ that cannot be anything else match -- ``return`` and ``throw`` are keywords, so
80
+ a following space or semicolon is unambiguous.
81
+ """
82
+ if not ll:
83
+ return False
84
+ last = ll[-1].string.strip()
85
+ if last in ("break;", "continue;"):
86
+ return True
87
+ return any(
88
+ last == f"{kw};" or last.startswith(f"{kw} ") for kw in _TERMINATING_KEYWORDS
89
+ )
90
+
91
+
92
+ @dataclass
93
+ class _Frame:
94
+ """One level of the body under construction."""
95
+
96
+ lines: list[Line] = field(default_factory=list)
97
+ kind: str = "body"
98
+ open_chain: bool = False
99
+ """Whether the last thing written was an ``if``/``else if``, so an ``else``
100
+ may still attach to it."""
101
+
102
+
103
+ class Body:
104
+ """A function body under construction."""
105
+
106
+ def __init__(self, initial: Iterable[Line] | None = None) -> None:
107
+ self._frames: list[_Frame] = [_Frame(list(initial or []))]
108
+
109
+ # ------------------------------------------------------------------
110
+ # Result
111
+ # ------------------------------------------------------------------
112
+
113
+ @property
114
+ def terminated(self) -> bool:
115
+ """Whether the last statement written unconditionally transfers control.
116
+
117
+ A switch arm reads this to skip an unreachable ``break;``. A ``return``
118
+ nested inside an ``if`` does not count: the last line at this level is then
119
+ the ``if``'s closing brace.
120
+ """
121
+ return _terminates(self._current.lines)
122
+
123
+ @property
124
+ def depth(self) -> int:
125
+ """How many scopes are currently open. Zero at the top level."""
126
+ return len(self._frames) - 1
127
+
128
+ def build(self) -> list[Line]:
129
+ """Return the accumulated lines.
130
+
131
+ Raises if a scope is still open, which means a ``with`` block was skipped
132
+ or exited by something other than falling off the end.
133
+ """
134
+ if len(self._frames) > 1:
135
+ raise ScopeError(
136
+ f"{len(self._frames) - 1} body scope(s) are still open; finish every "
137
+ "'with' block before building"
138
+ )
139
+ return list(self._frames[0].lines)
140
+
141
+ def __bool__(self) -> bool:
142
+ return any(f.lines for f in self._frames)
143
+
144
+ def __iter__(self) -> Iterator[Line]:
145
+ return iter(self.build())
146
+
147
+ def __str__(self) -> str:
148
+ return _render(self.build())
149
+
150
+ def __enter__(self) -> Body:
151
+ """Support ``with fn.body as b:`` as a way to shorten the name."""
152
+ return self
153
+
154
+ def __exit__(self, *exc: object) -> None:
155
+ return None
156
+
157
+ # ------------------------------------------------------------------
158
+ # Internals
159
+ # ------------------------------------------------------------------
160
+
161
+ @property
162
+ def _current(self) -> _Frame:
163
+ return self._frames[-1]
164
+
165
+ def _emit(self, ll: Sequence[Line], *, chain: bool = False) -> Body:
166
+ """Append lines and record whether an ``else`` may follow."""
167
+ frame = self._current
168
+ frame.lines.extend(ll)
169
+ frame.open_chain = chain
170
+ return self
171
+
172
+ @contextmanager
173
+ def _scope(
174
+ self,
175
+ opening: str,
176
+ closing: str,
177
+ *,
178
+ kind: str = "body",
179
+ omit_if_empty: bool = False,
180
+ chain: bool = False,
181
+ indent: bool = True,
182
+ ) -> Generator[Body]:
183
+ """Open a nested scope, indenting whatever is written inside it.
184
+
185
+ If the block raises, the scope is discarded and the body is left as it was
186
+ before the ``with``.
187
+ """
188
+ frame = _Frame(kind=kind)
189
+ self._frames.append(frame)
190
+ try:
191
+ yield self
192
+ except BaseException:
193
+ self._frames.pop()
194
+ raise
195
+ self._frames.pop()
196
+ if not frame.lines and omit_if_empty:
197
+ return
198
+ body = indent_lines(frame.lines) if indent else list(frame.lines)
199
+ self._emit(
200
+ [*_lines(opening), *body, *_lines(closing)],
201
+ chain=chain,
202
+ )
203
+
204
+ def _require_chain(self, keyword: str) -> None:
205
+ if not self._current.open_chain:
206
+ raise ScopeError(
207
+ f"{keyword!r} has no 'if' to attach to; it must directly follow an "
208
+ "if_() or elif_() scope in the same body"
209
+ )
210
+
211
+ # ------------------------------------------------------------------
212
+ # Raw output
213
+ # ------------------------------------------------------------------
214
+
215
+ def line(self, text: str) -> Body:
216
+ """Append one line verbatim."""
217
+ return self._emit([_line(text)])
218
+
219
+ def lines(self, text: str) -> Body:
220
+ """Append a margin-stripped, possibly multi-line block of C++."""
221
+ return self._emit(_lines(text))
222
+
223
+ def raw(self, ll: Iterable[Line]) -> Body:
224
+ """Append already-rendered lines."""
225
+ return self._emit(list(ll))
226
+
227
+ def add(self, *code: Code) -> Body:
228
+ """Append anything statement-shaped: text, lines, another body, or nested
229
+ sequences of those. ``None`` contributes nothing."""
230
+ return self._emit(stmts(*code))
231
+
232
+ def extend(self, other: Code) -> Body:
233
+ """Splice in another body or block of lines. Alias of :meth:`add`."""
234
+ return self.add(other)
235
+
236
+ def blank(self) -> Body:
237
+ """Append a blank line."""
238
+ return self._emit([blank()])
239
+
240
+ # ------------------------------------------------------------------
241
+ # Comments
242
+ # ------------------------------------------------------------------
243
+
244
+ def comment(self, text: str) -> Body:
245
+ """Append a ``//`` comment with no leading blank line."""
246
+ return self._emit(write_comment_body(text))
247
+
248
+ def spaced_comment(self, text: str) -> Body:
249
+ """Append a ``//`` comment preceded by a blank line."""
250
+ return self._emit(write_comment(text))
251
+
252
+ def doc_comment(self, text: str) -> Body:
253
+ """Append a ``//!`` doxygen comment."""
254
+ return self._emit(write_doxygen_comment(text))
255
+
256
+ def banner(self, text: str) -> Body:
257
+ """Append a ruled banner comment."""
258
+ return self._emit(write_banner_comment(text))
259
+
260
+ # ------------------------------------------------------------------
261
+ # Control flow
262
+ # ------------------------------------------------------------------
263
+
264
+ def block(self, *, omit_if_empty: bool = False) -> AbstractContextManager[Body]:
265
+ """A bare braced block, for scoping a local."""
266
+ return self._scope("{", "}", omit_if_empty=omit_if_empty)
267
+
268
+ def if_(
269
+ self, condition: str, *, omit_if_empty: bool = False
270
+ ) -> AbstractContextManager[Body]:
271
+ """``if (condition) { ... }``, which an :meth:`elif_` or :meth:`else_` may follow."""
272
+ return self._scope(
273
+ f"if ({condition}) {{", "}", omit_if_empty=omit_if_empty, chain=True
274
+ )
275
+
276
+ def elif_(self, condition: str) -> AbstractContextManager[Body]:
277
+ """``else if (condition) { ... }``. Must follow an ``if`` or another ``else if``."""
278
+ self._require_chain("elif_")
279
+ return self._scope(f"else if ({condition}) {{", "}", chain=True)
280
+
281
+ def else_(self) -> AbstractContextManager[Body]:
282
+ """``else { ... }``. Must follow an ``if`` or an ``else if``."""
283
+ self._require_chain("else_")
284
+ return self._scope("else {", "}")
285
+
286
+ def branch(self, condition: str) -> AbstractContextManager[Body]:
287
+ """``if`` the first time, ``else if`` while a chain is still open.
288
+
289
+ Lets a chain of arbitrary length come out of a plain loop::
290
+
291
+ for condition, code in dispatch:
292
+ with b.branch(condition):
293
+ b.add(code)
294
+ with b.else_():
295
+ b.raw(fprime.write_assert("0"))
296
+ """
297
+ return (
298
+ self.elif_(condition) if self._current.open_chain else self.if_(condition)
299
+ )
300
+
301
+ def while_(
302
+ self, condition: str, *, omit_if_empty: bool = False
303
+ ) -> AbstractContextManager[Body]:
304
+ """``while (condition) { ... }``"""
305
+ return self._scope(f"while ({condition}) {{", "}", omit_if_empty=omit_if_empty)
306
+
307
+ def do_while(self, condition: str) -> AbstractContextManager[Body]:
308
+ """``do { ... } while (condition);``"""
309
+ return self._scope("do {", f"}} while ({condition});")
310
+
311
+ def for_(
312
+ self,
313
+ init: str,
314
+ condition: str,
315
+ step: str,
316
+ *,
317
+ omit_if_empty: bool = False,
318
+ staggered: bool = False,
319
+ ) -> AbstractContextManager[Body]:
320
+ """``for (init; condition; step) { ... }``.
321
+
322
+ ``staggered=True`` splits the three clauses across lines.
323
+ """
324
+ opening = (
325
+ f"""|for (
326
+ | {init};
327
+ | {condition};
328
+ | {step}
329
+ |) {{
330
+ |"""
331
+ if staggered
332
+ else f"for ({init}; {condition}; {step}) {{"
333
+ )
334
+ return self._scope(opening, "}", omit_if_empty=omit_if_empty)
335
+
336
+ def for_range(
337
+ self, declaration: str, container: str, *, omit_if_empty: bool = False
338
+ ) -> AbstractContextManager[Body]:
339
+ """``for (declaration : container) { ... }``, e.g. ``for_range("auto& e", "m_list")``."""
340
+ return self._scope(
341
+ f"for ({declaration} : {container}) {{", "}", omit_if_empty=omit_if_empty
342
+ )
343
+
344
+ def scope(
345
+ self, opening: str, closing: str, *, omit_if_empty: bool = False
346
+ ) -> AbstractContextManager[Body]:
347
+ """An arbitrary scope, for shapes this module does not cover."""
348
+ return self._scope(opening, closing, omit_if_empty=omit_if_empty)
349
+
350
+ def if_directive(
351
+ self,
352
+ directive: str,
353
+ *,
354
+ omit_if_empty: bool = True,
355
+ spaced: bool = True,
356
+ ) -> AbstractContextManager[Body]:
357
+ """Bracket the block with a preprocessor ``directive`` and ``#endif``.
358
+
359
+ ``directive`` is written verbatim and must include its ``#``. The guarded
360
+ code is not indented relative to the guard, since the directives sit at
361
+ column zero. ``spaced=False`` drops the blank lines around them.
362
+ """
363
+ gap = "\n" if spaced else ""
364
+ return self._scope(
365
+ f"{gap}{directive}",
366
+ f"{gap}#endif",
367
+ omit_if_empty=omit_if_empty,
368
+ indent=False,
369
+ )
370
+
371
+ @contextmanager
372
+ def switch(
373
+ self, selector: str, *, omit_if_empty: bool = False
374
+ ) -> Generator[Switch]:
375
+ """``switch (selector) { ... }``. Yields a :class:`Switch` for its cases."""
376
+ frame = _Frame(kind="switch")
377
+ self._frames.append(frame)
378
+ switch = Switch(self, frame)
379
+ try:
380
+ yield switch
381
+ except BaseException:
382
+ self._frames.pop()
383
+ raise
384
+ self._frames.pop()
385
+ if not frame.lines and omit_if_empty:
386
+ return
387
+ self._emit(
388
+ [
389
+ *_lines(f"switch ({selector}) {{"),
390
+ *indent_lines(frame.lines),
391
+ *_lines("}"),
392
+ ]
393
+ )
394
+
395
+
396
+ class Switch:
397
+ """The cases of an open ``switch``. Obtained from :meth:`Body.switch`."""
398
+
399
+ def __init__(self, body: Body, frame: _Frame) -> None:
400
+ self._body = body
401
+ self._frame = frame
402
+
403
+ def _check_open(self) -> None:
404
+ if self._body._current is not self._frame:
405
+ raise ScopeError(
406
+ "this switch is not the innermost open scope; close any nested "
407
+ "scope before adding another case"
408
+ )
409
+
410
+ def case(
411
+ self, *labels: str, fallthrough: bool = False, braces: bool = True
412
+ ) -> AbstractContextManager[Body]:
413
+ """One or more ``case`` labels sharing a body.
414
+
415
+ A ``break;`` is appended unless ``fallthrough=True`` or the body already
416
+ ends in a statement that transfers control. Braces let an arm declare a
417
+ local, which a bare label cannot; ``braces=False`` emits the bare label.
418
+ """
419
+ self._check_open()
420
+ if not labels:
421
+ raise ScopeError("case() needs at least one label")
422
+ last = f"case {labels[-1]}: {{" if braces else f"case {labels[-1]}:"
423
+ opening = "\n".join([*(f"case {l}:" for l in labels[:-1]), last])
424
+ return self._scoped(opening, fallthrough, braces)
425
+
426
+ def default(
427
+ self, *, fallthrough: bool = False, braces: bool = True
428
+ ) -> AbstractContextManager[Body]:
429
+ """The ``default`` label. See :meth:`case` for the arguments."""
430
+ self._check_open()
431
+ return self._scoped("default: {" if braces else "default:", fallthrough, braces)
432
+
433
+ @contextmanager
434
+ def _scoped(self, opening: str, fallthrough: bool, braces: bool) -> Generator[Body]:
435
+ body = self._body
436
+ frame = _Frame(kind="case")
437
+ body._frames.append(frame)
438
+ try:
439
+ yield body
440
+ except BaseException:
441
+ body._frames.pop()
442
+ raise
443
+ body._frames.pop()
444
+ inner = list(frame.lines)
445
+ if not fallthrough and not _terminates(frame.lines):
446
+ inner.append(_line("break;"))
447
+ closing = _lines("}") if braces else []
448
+ body._emit([*_lines(opening), *indent_lines(inner), *closing])
@@ -0,0 +1,75 @@
1
+ """The builder API: the comfortable way to assemble a C++ document.
2
+
3
+ Nesting in the generated C++ follows nesting in the Python::
4
+
5
+ doc = CppDocBuilder("Counter", description="a counter")
6
+ doc.include("Fw/FPrimeBasicTypes.hpp")
7
+
8
+ with doc.namespace("Fw") as ns:
9
+ with ns.class_("Counter", final=True) as cls:
10
+ with cls.public("Constructors and destructors"):
11
+ cls.constructor(initializers=["m_count(0)"])
12
+ cls.destructor()
13
+ with cls.public("Public member functions"):
14
+ with cls.function("bump", ret="U32") as fn:
15
+ fn.body.line("m_count++;")
16
+ fn.body.line("return m_count;")
17
+ with cls.private("Member variables"):
18
+ cls.var("U32", "m_count", comment="How many bumps so far")
19
+
20
+ doc.write("build-artifacts")
21
+
22
+ ``with`` is optional almost everywhere. A builder attaches to its parent when you
23
+ create it, fixing its position in the output, so you can keep filling it in
24
+ afterwards::
25
+
26
+ fn = cls.function("bump", ret="U32")
27
+ fn.param("U32", "by", default="1")
28
+ fn.body.line("return m_count + by;")
29
+
30
+ On access sections and preprocessor guards, ``with`` additionally makes an empty
31
+ section disappear instead of leaving a stray ``public:`` or an empty ``#if``.
32
+
33
+ A helper can build a fragment and return it for the caller to splice in with
34
+ :meth:`ClassBuilder.member`::
35
+
36
+ def accessor(cls, name, type_name):
37
+ fn = cls.function(f"get{name}", ret=type_name, const=True)
38
+ fn.body.line(f"return m_{name};")
39
+
40
+ for name, type_name in model.fields:
41
+ accessor(cls, name, type_name)
42
+
43
+ The implementation is split across submodules: :mod:`~.base` (the builder protocol
44
+ and per-document state), :mod:`~.coercion` (argument shapes), :mod:`~.definitions`
45
+ (functions, constructors, enums), :mod:`~.decoration` (banners, guards, access
46
+ sections), :mod:`~.scopes` (classes and namespaces) and :mod:`~.document` (the
47
+ document itself). Import from :mod:`fprime_cpp_codegen` or from here.
48
+ """
49
+
50
+ from __future__ import annotations
51
+
52
+ from .base import BodyLike
53
+ from .decoration import AccessSection
54
+ from .definitions import (
55
+ ConstructorBuilder,
56
+ DestructorBuilder,
57
+ EnumBuilder,
58
+ FunctionBuilder,
59
+ Radix,
60
+ )
61
+ from .document import CppDocBuilder
62
+ from .scopes import ClassBuilder, NamespaceBuilder
63
+
64
+ __all__ = [
65
+ "AccessSection",
66
+ "BodyLike",
67
+ "ClassBuilder",
68
+ "ConstructorBuilder",
69
+ "CppDocBuilder",
70
+ "DestructorBuilder",
71
+ "EnumBuilder",
72
+ "FunctionBuilder",
73
+ "NamespaceBuilder",
74
+ "Radix",
75
+ ]