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