| from __future__ import annotations |
| import os |
| |
| from collections.abc import Callable, Sequence |
| from typing import Any, TYPE_CHECKING |
| |
| |
| import libclinic |
| from libclinic import fail, warn |
| from libclinic.function import Class |
| from libclinic.block_parser import Block, BlockParser |
| from libclinic.codegen import BlockPrinter, Destination, CodeGen |
| from libclinic.parser import Parser, PythonParser |
| from libclinic.dsl_parser import DSLParser |
| if TYPE_CHECKING: |
| from libclinic.clanguage import CLanguage |
| from libclinic.function import ( |
| Module, Function, ClassDict, ModuleDict) |
| from libclinic.codegen import DestinationDict |
| |
| |
| # maps strings to callables. |
| # the callable should return an object |
| # that implements the clinic parser |
| # interface (__init__ and parse). |
| # |
| # example parsers: |
| # "clinic", handles the Clinic DSL |
| # "python", handles running Python code |
| # |
| parsers: dict[str, Callable[[Clinic], Parser]] = { |
| 'clinic': DSLParser, |
| 'python': PythonParser, |
| } |
| |
| |
| class Clinic: |
| |
| presets_text = """ |
| preset block |
| everything block |
| methoddef_ifndef buffer 1 |
| docstring_prototype suppress |
| parser_prototype suppress |
| cpp_if suppress |
| cpp_endif suppress |
| |
| preset original |
| everything block |
| methoddef_ifndef buffer 1 |
| docstring_prototype suppress |
| parser_prototype suppress |
| cpp_if suppress |
| cpp_endif suppress |
| |
| preset file |
| everything file |
| methoddef_ifndef file 1 |
| docstring_prototype suppress |
| parser_prototype suppress |
| impl_definition block |
| |
| preset buffer |
| everything buffer |
| methoddef_ifndef buffer 1 |
| impl_definition block |
| docstring_prototype suppress |
| impl_prototype suppress |
| parser_prototype suppress |
| |
| preset partial-buffer |
| everything buffer |
| methoddef_ifndef buffer 1 |
| docstring_prototype block |
| impl_prototype suppress |
| methoddef_define block |
| parser_prototype block |
| impl_definition block |
| |
| """ |
| |
| def __init__( |
| self, |
| language: CLanguage, |
| printer: BlockPrinter | None = None, |
| *, |
| filename: str, |
| limited_capi: bool, |
| verify: bool = True, |
| ) -> None: |
| # maps strings to Parser objects. |
| # (instantiated from the "parsers" global.) |
| self.parsers: dict[str, Parser] = {} |
| self.language: CLanguage = language |
| if printer: |
| fail("Custom printers are broken right now") |
| self.printer = printer or BlockPrinter(language) |
| self.verify = verify |
| self.limited_capi = limited_capi |
| self.filename = filename |
| self.modules: ModuleDict = {} |
| self.classes: ClassDict = {} |
| self.functions: list[Function] = [] |
| self.codegen = CodeGen(self.limited_capi) |
| |
| self.line_prefix = self.line_suffix = '' |
| |
| self.destinations: DestinationDict = {} |
| self.add_destination("block", "buffer") |
| self.add_destination("suppress", "suppress") |
| self.add_destination("buffer", "buffer") |
| if filename: |
| self.add_destination("file", "file", "{dirname}/clinic/{basename}.h") |
| |
| d = self.get_destination_buffer |
| self.destination_buffers = { |
| 'cpp_if': d('file'), |
| 'docstring_prototype': d('suppress'), |
| 'docstring_definition': d('file'), |
| 'methoddef_define': d('file'), |
| 'impl_prototype': d('file'), |
| 'parser_prototype': d('suppress'), |
| 'parser_definition': d('file'), |
| 'cpp_endif': d('file'), |
| 'methoddef_ifndef': d('file', 1), |
| 'impl_definition': d('block'), |
| } |
| |
| DestBufferType = dict[str, list[str]] |
| DestBufferList = list[DestBufferType] |
| |
| self.destination_buffers_stack: DestBufferList = [] |
| |
| self.presets: dict[str, dict[Any, Any]] = {} |
| preset = None |
| for line in self.presets_text.strip().split('\n'): |
| line = line.strip() |
| if not line: |
| continue |
| name, value, *options = line.split() |
| if name == 'preset': |
| self.presets[value] = preset = {} |
| continue |
| |
| if len(options): |
| index = int(options[0]) |
| else: |
| index = 0 |
| buffer = self.get_destination_buffer(value, index) |
| |
| if name == 'everything': |
| for name in self.destination_buffers: |
| preset[name] = buffer |
| continue |
| |
| assert name in self.destination_buffers |
| preset[name] = buffer |
| |
| def add_destination( |
| self, |
| name: str, |
| type: str, |
| *args: str |
| ) -> None: |
| if name in self.destinations: |
| fail(f"Destination already exists: {name!r}") |
| self.destinations[name] = Destination(name, type, self, args) |
| |
| def get_destination(self, name: str) -> Destination: |
| d = self.destinations.get(name) |
| if not d: |
| fail(f"Destination does not exist: {name!r}") |
| return d |
| |
| def get_destination_buffer( |
| self, |
| name: str, |
| item: int = 0 |
| ) -> list[str]: |
| d = self.get_destination(name) |
| return d.buffers[item] |
| |
| def parse(self, input: str) -> str: |
| printer = self.printer |
| self.block_parser = BlockParser(input, self.language, verify=self.verify) |
| for block in self.block_parser: |
| dsl_name = block.dsl_name |
| if dsl_name: |
| if dsl_name not in self.parsers: |
| assert dsl_name in parsers, f"No parser to handle {dsl_name!r} block." |
| self.parsers[dsl_name] = parsers[dsl_name](self) |
| parser = self.parsers[dsl_name] |
| parser.parse(block) |
| printer.print_block(block) |
| |
| # these are destinations not buffers |
| for name, destination in self.destinations.items(): |
| if destination.type == 'suppress': |
| continue |
| output = destination.dump() |
| |
| if output: |
| block = Block("", dsl_name="clinic", output=output) |
| |
| if destination.type == 'buffer': |
| block.input = "dump " + name + "\n" |
| warn("Destination buffer " + repr(name) + " not empty at end of file, emptying.") |
| printer.write("\n") |
| printer.print_block(block) |
| continue |
| |
| if destination.type == 'file': |
| try: |
| dirname = os.path.dirname(destination.filename) |
| try: |
| os.makedirs(dirname) |
| except FileExistsError: |
| if not os.path.isdir(dirname): |
| fail(f"Can't write to destination " |
| f"{destination.filename!r}; " |
| f"can't make directory {dirname!r}!") |
| if self.verify: |
| with open(destination.filename) as f: |
| parser_2 = BlockParser(f.read(), language=self.language) |
| blocks = list(parser_2) |
| if (len(blocks) != 1) or (blocks[0].input != 'preserve\n'): |
| fail(f"Modified destination file " |
| f"{destination.filename!r}; not overwriting!") |
| except FileNotFoundError: |
| pass |
| |
| block.input = 'preserve\n' |
| includes = self.codegen.get_includes() |
| |
| printer_2 = BlockPrinter(self.language) |
| printer_2.print_block(block, header_includes=includes) |
| libclinic.write_file(destination.filename, |
| printer_2.f.getvalue()) |
| continue |
| |
| return printer.f.getvalue() |
| |
| def _module_and_class( |
| self, fields: Sequence[str] |
| ) -> tuple[Module | Clinic, Class | None]: |
| """ |
| fields should be an iterable of field names. |
| returns a tuple of (module, class). |
| the module object could actually be self (a clinic object). |
| this function is only ever used to find the parent of where |
| a new class/module should go. |
| """ |
| parent: Clinic | Module | Class = self |
| module: Clinic | Module = self |
| cls: Class | None = None |
| |
| for idx, field in enumerate(fields): |
| fullname = ".".join(fields[:idx + 1]) |
| if not isinstance(parent, Class): |
| if fullname in parent.modules: |
| parent = module = parent.modules[fullname] |
| continue |
| if field in parent.classes: |
| parent = cls = parent.classes[field] |
| else: |
| fail(f"Parent class or module {fullname!r} does not exist.") |
| |
| return module, cls |
| |
| def __repr__(self) -> str: |
| return "<clinic.Clinic object>" |