mirror of https://github.com/python/cpython
272 lines
8.7 KiB
Python
272 lines
8.7 KiB
Python
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>"
|