Coverage for src/pystencilssfg/context.py: 97%
100 statements
« prev ^ index » next coverage.py v7.8.0, created at 2025-04-04 07:16 +0000
« prev ^ index » next coverage.py v7.8.0, created at 2025-04-04 07:16 +0000
1from __future__ import annotations
2from typing import Sequence, Any, Generator
3from contextlib import contextmanager
5from .config import CodeStyle, ClangFormatOptions
6from .ir import (
7 SfgSourceFile,
8 SfgNamespace,
9 SfgNamespaceBlock,
10 SfgCodeEntity,
11 SfgGlobalNamespace,
12)
13from .ir.syntax import SfgNamespaceElement
14from .exceptions import SfgException
17class SfgContext:
18 """Manages context information during the execution of a generator script."""
20 def __init__(
21 self,
22 header_file: SfgSourceFile,
23 impl_file: SfgSourceFile | None,
24 namespace: str | None = None,
25 codestyle: CodeStyle | None = None,
26 clang_format_opts: ClangFormatOptions | None = None,
27 argv: Sequence[str] | None = None,
28 project_info: Any = None,
29 ):
30 self._argv = argv
31 self._project_info = project_info
33 self._outer_namespace = namespace
34 self._inner_namespace: str | None = None
36 self._codestyle = codestyle if codestyle is not None else CodeStyle()
37 self._clang_format: ClangFormatOptions = (
38 clang_format_opts if clang_format_opts is not None else ClangFormatOptions()
39 )
41 self._header_file = header_file
42 self._impl_file = impl_file
44 self._global_namespace = SfgGlobalNamespace()
46 current_namespace: SfgNamespace
47 if namespace is not None:
48 current_namespace = self._global_namespace.get_child_namespace(namespace)
49 else:
50 current_namespace = self._global_namespace
52 self._cursor = SfgCursor(self, current_namespace)
54 @property
55 def argv(self) -> Sequence[str]:
56 """If this context was created by a `pystencilssfg.SourceFileGenerator`, provides the command
57 line arguments given to the generator script, with configuration arguments for the code generator
58 stripped away.
60 Otherwise, throws an exception."""
61 if self._argv is None:
62 raise SfgException("This context provides no command-line arguments.")
63 return self._argv
65 @property
66 def project_info(self) -> Any:
67 """Project-specific information provided by a build system."""
68 return self._project_info
70 @property
71 def outer_namespace(self) -> str | None:
72 """Outer code namespace. Set by constructor argument `outer_namespace`."""
73 return self._outer_namespace
75 @property
76 def codestyle(self) -> CodeStyle:
77 """The code style object for this generation context."""
78 return self._codestyle
80 @property
81 def clang_format(self) -> ClangFormatOptions:
82 return self._clang_format
84 @property
85 def header_file(self) -> SfgSourceFile:
86 return self._header_file
88 @property
89 def impl_file(self) -> SfgSourceFile | None:
90 return self._impl_file
92 @property
93 def cursor(self) -> SfgCursor:
94 return self._cursor
96 @property
97 def files(self) -> Generator[SfgSourceFile, None, None]:
98 yield self._header_file
99 if self._impl_file is not None:
100 yield self._impl_file
102 @property
103 def global_namespace(self) -> SfgNamespace:
104 return self._global_namespace
107class SfgCursor:
108 """Cursor that tracks the current location in the source file(s) during execution of the generator script."""
110 def __init__(self, ctx: SfgContext, namespace: SfgNamespace) -> None:
111 self._ctx = ctx
113 self._cur_namespace: SfgNamespace = namespace
115 self._loc: dict[SfgSourceFile, list[SfgNamespaceElement]] = dict()
116 for f in self._ctx.files:
117 if not isinstance(namespace, SfgGlobalNamespace):
118 block = SfgNamespaceBlock(
119 self._cur_namespace, self._cur_namespace.fqname
120 )
121 f.elements.append(block)
122 self._loc[f] = block.elements
123 else:
124 self._loc[f] = f.elements
126 @property
127 def context(self) -> SfgContext:
128 return self._ctx
130 @property
131 def current_namespace(self) -> SfgNamespace:
132 return self._cur_namespace
134 def get_entity(self, name: str) -> SfgCodeEntity | None:
135 return self._cur_namespace.get_entity(name)
137 def add_entity(self, entity: SfgCodeEntity):
138 self._cur_namespace.add_entity(entity)
140 def write_header(self, elem: SfgNamespaceElement) -> None:
141 self._loc[self._ctx.header_file].append(elem)
143 def write_impl(self, elem: SfgNamespaceElement) -> None:
144 impl_file = self._ctx.impl_file
145 if impl_file is None:
146 raise SfgException(
147 f"Cannot write element {elem} to implemenation file since no implementation file is being generated."
148 )
149 self._loc[impl_file].append(elem)
151 def enter_namespace(self, qual_name: str):
152 namespace = self._cur_namespace.get_child_namespace(qual_name)
154 outer_locs = self._loc.copy()
156 for f in self._ctx.files:
157 block = SfgNamespaceBlock(namespace, qual_name)
158 self._loc[f].append(block)
159 self._loc[f] = block.elements
161 outer_namespace = self._cur_namespace
162 self._cur_namespace = namespace
164 @contextmanager
165 def ctxmgr():
166 try:
167 yield None
168 finally:
169 # Have the cursor step back out of the nested namespace blocks
170 self._loc = outer_locs
171 self._cur_namespace = outer_namespace
173 return ctxmgr()