• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1from __future__ import annotations
2import os
3
4from collections.abc import Callable, Sequence
5from typing import Any, TYPE_CHECKING
6
7
8import libclinic
9from libclinic import fail, warn
10from libclinic.function import Class
11from libclinic.block_parser import Block, BlockParser
12from libclinic.codegen import BlockPrinter, Destination, CodeGen
13from libclinic.parser import Parser, PythonParser
14from libclinic.dsl_parser import DSLParser
15if TYPE_CHECKING:
16    from libclinic.clanguage import CLanguage
17    from libclinic.function import (
18        Module, Function, ClassDict, ModuleDict)
19    from libclinic.codegen import DestinationDict
20
21
22# maps strings to callables.
23# the callable should return an object
24# that implements the clinic parser
25# interface (__init__ and parse).
26#
27# example parsers:
28#   "clinic", handles the Clinic DSL
29#   "python", handles running Python code
30#
31parsers: dict[str, Callable[[Clinic], Parser]] = {
32    'clinic': DSLParser,
33    'python': PythonParser,
34}
35
36
37class Clinic:
38
39    presets_text = """
40preset block
41everything block
42methoddef_ifndef buffer 1
43docstring_prototype suppress
44parser_prototype suppress
45cpp_if suppress
46cpp_endif suppress
47
48preset original
49everything block
50methoddef_ifndef buffer 1
51docstring_prototype suppress
52parser_prototype suppress
53cpp_if suppress
54cpp_endif suppress
55
56preset file
57everything file
58methoddef_ifndef file 1
59docstring_prototype suppress
60parser_prototype suppress
61impl_definition block
62
63preset buffer
64everything buffer
65methoddef_ifndef buffer 1
66impl_definition block
67docstring_prototype suppress
68impl_prototype suppress
69parser_prototype suppress
70
71preset partial-buffer
72everything buffer
73methoddef_ifndef buffer 1
74docstring_prototype block
75impl_prototype suppress
76methoddef_define block
77parser_prototype block
78impl_definition block
79
80"""
81
82    def __init__(
83        self,
84        language: CLanguage,
85        printer: BlockPrinter | None = None,
86        *,
87        filename: str,
88        limited_capi: bool,
89        verify: bool = True,
90    ) -> None:
91        # maps strings to Parser objects.
92        # (instantiated from the "parsers" global.)
93        self.parsers: dict[str, Parser] = {}
94        self.language: CLanguage = language
95        if printer:
96            fail("Custom printers are broken right now")
97        self.printer = printer or BlockPrinter(language)
98        self.verify = verify
99        self.limited_capi = limited_capi
100        self.filename = filename
101        self.modules: ModuleDict = {}
102        self.classes: ClassDict = {}
103        self.functions: list[Function] = []
104        self.codegen = CodeGen(self.limited_capi)
105
106        self.line_prefix = self.line_suffix = ''
107
108        self.destinations: DestinationDict = {}
109        self.add_destination("block", "buffer")
110        self.add_destination("suppress", "suppress")
111        self.add_destination("buffer", "buffer")
112        if filename:
113            self.add_destination("file", "file", "{dirname}/clinic/{basename}.h")
114
115        d = self.get_destination_buffer
116        self.destination_buffers = {
117            'cpp_if': d('file'),
118            'docstring_prototype': d('suppress'),
119            'docstring_definition': d('file'),
120            'methoddef_define': d('file'),
121            'impl_prototype': d('file'),
122            'parser_prototype': d('suppress'),
123            'parser_definition': d('file'),
124            'cpp_endif': d('file'),
125            'methoddef_ifndef': d('file', 1),
126            'impl_definition': d('block'),
127        }
128
129        DestBufferType = dict[str, list[str]]
130        DestBufferList = list[DestBufferType]
131
132        self.destination_buffers_stack: DestBufferList = []
133
134        self.presets: dict[str, dict[Any, Any]] = {}
135        preset = None
136        for line in self.presets_text.strip().split('\n'):
137            line = line.strip()
138            if not line:
139                continue
140            name, value, *options = line.split()
141            if name == 'preset':
142                self.presets[value] = preset = {}
143                continue
144
145            if len(options):
146                index = int(options[0])
147            else:
148                index = 0
149            buffer = self.get_destination_buffer(value, index)
150
151            if name == 'everything':
152                for name in self.destination_buffers:
153                    preset[name] = buffer
154                continue
155
156            assert name in self.destination_buffers
157            preset[name] = buffer
158
159    def add_destination(
160        self,
161        name: str,
162        type: str,
163        *args: str
164    ) -> None:
165        if name in self.destinations:
166            fail(f"Destination already exists: {name!r}")
167        self.destinations[name] = Destination(name, type, self, args)
168
169    def get_destination(self, name: str) -> Destination:
170        d = self.destinations.get(name)
171        if not d:
172            fail(f"Destination does not exist: {name!r}")
173        return d
174
175    def get_destination_buffer(
176        self,
177        name: str,
178        item: int = 0
179    ) -> list[str]:
180        d = self.get_destination(name)
181        return d.buffers[item]
182
183    def parse(self, input: str) -> str:
184        printer = self.printer
185        self.block_parser = BlockParser(input, self.language, verify=self.verify)
186        for block in self.block_parser:
187            dsl_name = block.dsl_name
188            if dsl_name:
189                if dsl_name not in self.parsers:
190                    assert dsl_name in parsers, f"No parser to handle {dsl_name!r} block."
191                    self.parsers[dsl_name] = parsers[dsl_name](self)
192                parser = self.parsers[dsl_name]
193                parser.parse(block)
194            printer.print_block(block)
195
196        # these are destinations not buffers
197        for name, destination in self.destinations.items():
198            if destination.type == 'suppress':
199                continue
200            output = destination.dump()
201
202            if output:
203                block = Block("", dsl_name="clinic", output=output)
204
205                if destination.type == 'buffer':
206                    block.input = "dump " + name + "\n"
207                    warn("Destination buffer " + repr(name) + " not empty at end of file, emptying.")
208                    printer.write("\n")
209                    printer.print_block(block)
210                    continue
211
212                if destination.type == 'file':
213                    try:
214                        dirname = os.path.dirname(destination.filename)
215                        try:
216                            os.makedirs(dirname)
217                        except FileExistsError:
218                            if not os.path.isdir(dirname):
219                                fail(f"Can't write to destination "
220                                     f"{destination.filename!r}; "
221                                     f"can't make directory {dirname!r}!")
222                        if self.verify:
223                            with open(destination.filename) as f:
224                                parser_2 = BlockParser(f.read(), language=self.language)
225                                blocks = list(parser_2)
226                                if (len(blocks) != 1) or (blocks[0].input != 'preserve\n'):
227                                    fail(f"Modified destination file "
228                                         f"{destination.filename!r}; not overwriting!")
229                    except FileNotFoundError:
230                        pass
231
232                    block.input = 'preserve\n'
233                    includes = self.codegen.get_includes()
234
235                    printer_2 = BlockPrinter(self.language)
236                    printer_2.print_block(block, header_includes=includes)
237                    libclinic.write_file(destination.filename,
238                                         printer_2.f.getvalue())
239                    continue
240
241        return printer.f.getvalue()
242
243    def _module_and_class(
244        self, fields: Sequence[str]
245    ) -> tuple[Module | Clinic, Class | None]:
246        """
247        fields should be an iterable of field names.
248        returns a tuple of (module, class).
249        the module object could actually be self (a clinic object).
250        this function is only ever used to find the parent of where
251        a new class/module should go.
252        """
253        parent: Clinic | Module | Class = self
254        module: Clinic | Module = self
255        cls: Class | None = None
256
257        for idx, field in enumerate(fields):
258            if not isinstance(parent, Class):
259                if field in parent.modules:
260                    parent = module = parent.modules[field]
261                    continue
262            if field in parent.classes:
263                parent = cls = parent.classes[field]
264            else:
265                fullname = ".".join(fields[idx:])
266                fail(f"Parent class or module {fullname!r} does not exist.")
267
268        return module, cls
269
270    def __repr__(self) -> str:
271        return "<clinic.Clinic object>"
272