1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271
|
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):
if not isinstance(parent, Class):
if field in parent.modules:
parent = module = parent.modules[field]
continue
if field in parent.classes:
parent = cls = parent.classes[field]
else:
fullname = ".".join(fields[idx:])
fail(f"Parent class or module {fullname!r} does not exist.")
return module, cls
def __repr__(self) -> str:
return "<clinic.Clinic object>"
|