Issue #23500: Argument Clinic is now smarter about generating the "#ifndef"

(empty) definition of the methoddef macro: it's only generated once, even
if Argument Clinic processes the same symbol multiple times, and it's emitted
at the end of all processing rather than immediately after the first use.
This commit is contained in:
Larry Hastings 2015-04-03 13:09:02 -07:00
parent 5cf2b7253d
commit 0759f84d62
5 changed files with 112 additions and 93 deletions

View File

@ -268,6 +268,11 @@ Tests
Tools/Demos Tools/Demos
----------- -----------
- Issue #23500: Argument Clinic is now smarter about generating the "#ifndef"
(empty) definition of the methoddef macro: it's only generated once, even
if Argument Clinic processes the same symbol multiple times, and it's emitted
at the end of all processing rather than immediately after the first use.
- Issue #22826: The result of open() in Tools/freeze/bkfile.py is now better - Issue #22826: The result of open() in Tools/freeze/bkfile.py is now better
compatible with regular files (in particular it now supports the context compatible with regular files (in particular it now supports the context
management protocol). management protocol).

View File

@ -36,10 +36,6 @@ exit:
#endif /* defined(HAVE_GETSPNAM) */ #endif /* defined(HAVE_GETSPNAM) */
#ifndef SPWD_GETSPNAM_METHODDEF
#define SPWD_GETSPNAM_METHODDEF
#endif /* !defined(SPWD_GETSPNAM_METHODDEF) */
#if defined(HAVE_GETSPENT) #if defined(HAVE_GETSPENT)
PyDoc_STRVAR(spwd_getspall__doc__, PyDoc_STRVAR(spwd_getspall__doc__,
@ -64,7 +60,11 @@ spwd_getspall(PyModuleDef *module, PyObject *Py_UNUSED(ignored))
#endif /* defined(HAVE_GETSPENT) */ #endif /* defined(HAVE_GETSPENT) */
#ifndef SPWD_GETSPNAM_METHODDEF
#define SPWD_GETSPNAM_METHODDEF
#endif /* !defined(SPWD_GETSPNAM_METHODDEF) */
#ifndef SPWD_GETSPALL_METHODDEF #ifndef SPWD_GETSPALL_METHODDEF
#define SPWD_GETSPALL_METHODDEF #define SPWD_GETSPALL_METHODDEF
#endif /* !defined(SPWD_GETSPALL_METHODDEF) */ #endif /* !defined(SPWD_GETSPALL_METHODDEF) */
/*[clinic end generated code: output=41fec4a15b0cd2a0 input=a9049054013a1b77]*/ /*[clinic end generated code: output=ab16125c5e5f2b1b input=a9049054013a1b77]*/

View File

@ -314,10 +314,6 @@ zlib_Compress_copy(compobject *self, PyObject *Py_UNUSED(ignored))
#endif /* defined(HAVE_ZLIB_COPY) */ #endif /* defined(HAVE_ZLIB_COPY) */
#ifndef ZLIB_COMPRESS_COPY_METHODDEF
#define ZLIB_COMPRESS_COPY_METHODDEF
#endif /* !defined(ZLIB_COMPRESS_COPY_METHODDEF) */
#if defined(HAVE_ZLIB_COPY) #if defined(HAVE_ZLIB_COPY)
PyDoc_STRVAR(zlib_Decompress_copy__doc__, PyDoc_STRVAR(zlib_Decompress_copy__doc__,
@ -340,10 +336,6 @@ zlib_Decompress_copy(compobject *self, PyObject *Py_UNUSED(ignored))
#endif /* defined(HAVE_ZLIB_COPY) */ #endif /* defined(HAVE_ZLIB_COPY) */
#ifndef ZLIB_DECOMPRESS_COPY_METHODDEF
#define ZLIB_DECOMPRESS_COPY_METHODDEF
#endif /* !defined(ZLIB_DECOMPRESS_COPY_METHODDEF) */
PyDoc_STRVAR(zlib_Decompress_flush__doc__, PyDoc_STRVAR(zlib_Decompress_flush__doc__,
"flush($self, length=zlib.DEF_BUF_SIZE, /)\n" "flush($self, length=zlib.DEF_BUF_SIZE, /)\n"
"--\n" "--\n"
@ -450,4 +442,8 @@ exit:
return return_value; return return_value;
} }
/*[clinic end generated code: output=bc9473721ca7c962 input=a9049054013a1b77]*/
#ifndef ZLIB_COMPRESS_COPY_METHODDEF
#define ZLIB_COMPRESS_COPY_METHODDEF
#endif /* !defined(ZLIB_COMPRESS_COPY_METHODDEF) */
/*[clinic end generated code: output=901c18189767dc08 input=a9049054013a1b77]*/

View File

@ -17130,10 +17130,6 @@ dump buffer
#define OS_SYSTEM_METHODDEF #define OS_SYSTEM_METHODDEF
#endif /* !defined(OS_SYSTEM_METHODDEF) */ #endif /* !defined(OS_SYSTEM_METHODDEF) */
#ifndef OS_SYSTEM_METHODDEF
#define OS_SYSTEM_METHODDEF
#endif /* !defined(OS_SYSTEM_METHODDEF) */
#ifndef OS_UNAME_METHODDEF #ifndef OS_UNAME_METHODDEF
#define OS_UNAME_METHODDEF #define OS_UNAME_METHODDEF
#endif /* !defined(OS_UNAME_METHODDEF) */ #endif /* !defined(OS_UNAME_METHODDEF) */
@ -17306,10 +17302,6 @@ dump buffer
#define OS_WAITPID_METHODDEF #define OS_WAITPID_METHODDEF
#endif /* !defined(OS_WAITPID_METHODDEF) */ #endif /* !defined(OS_WAITPID_METHODDEF) */
#ifndef OS_WAITPID_METHODDEF
#define OS_WAITPID_METHODDEF
#endif /* !defined(OS_WAITPID_METHODDEF) */
#ifndef OS_WAIT_METHODDEF #ifndef OS_WAIT_METHODDEF
#define OS_WAIT_METHODDEF #define OS_WAIT_METHODDEF
#endif /* !defined(OS_WAIT_METHODDEF) */ #endif /* !defined(OS_WAIT_METHODDEF) */
@ -17410,10 +17402,6 @@ dump buffer
#define OS_PUTENV_METHODDEF #define OS_PUTENV_METHODDEF
#endif /* !defined(OS_PUTENV_METHODDEF) */ #endif /* !defined(OS_PUTENV_METHODDEF) */
#ifndef OS_PUTENV_METHODDEF
#define OS_PUTENV_METHODDEF
#endif /* !defined(OS_PUTENV_METHODDEF) */
#ifndef OS_UNSETENV_METHODDEF #ifndef OS_UNSETENV_METHODDEF
#define OS_UNSETENV_METHODDEF #define OS_UNSETENV_METHODDEF
#endif /* !defined(OS_UNSETENV_METHODDEF) */ #endif /* !defined(OS_UNSETENV_METHODDEF) */
@ -17521,7 +17509,7 @@ dump buffer
#ifndef OS_SET_HANDLE_INHERITABLE_METHODDEF #ifndef OS_SET_HANDLE_INHERITABLE_METHODDEF
#define OS_SET_HANDLE_INHERITABLE_METHODDEF #define OS_SET_HANDLE_INHERITABLE_METHODDEF
#endif /* !defined(OS_SET_HANDLE_INHERITABLE_METHODDEF) */ #endif /* !defined(OS_SET_HANDLE_INHERITABLE_METHODDEF) */
/*[clinic end generated code: output=52a6140b0b052ce6 input=524ce2e021e4eba6]*/ /*[clinic end generated code: output=b788c2d6010113e8 input=524ce2e021e4eba6]*/
static PyMethodDef posix_methods[] = { static PyMethodDef posix_methods[] = {

View File

@ -67,15 +67,19 @@ class Unknown:
unknown = Unknown() unknown = Unknown()
_text_accumulator_nt = collections.namedtuple("_text_accumulator", "text append output")
def _text_accumulator(): def _text_accumulator():
text = [] text = []
def output(): def output():
s = ''.join(text) s = ''.join(text)
text.clear() text.clear()
return s return s
return text, text.append, output return _text_accumulator_nt(text, text.append, output)
text_accumulator_nt = collections.namedtuple("text_accumulator", "text append")
def text_accumulator(): def text_accumulator():
""" """
Creates a simple text accumulator / joiner. Creates a simple text accumulator / joiner.
@ -88,7 +92,7 @@ def text_accumulator():
empties the accumulator. empties the accumulator.
""" """
text, append, output = _text_accumulator() text, append, output = _text_accumulator()
return append, output return text_accumulator_nt(append, output)
def warn_or_fail(fail=False, *args, filename=None, line_number=None): def warn_or_fail(fail=False, *args, filename=None, line_number=None):
@ -820,7 +824,8 @@ class CLanguage(Language):
cpp_if = "#if " + conditional cpp_if = "#if " + conditional
cpp_endif = "#endif /* " + conditional + " */" cpp_endif = "#endif /* " + conditional + " */"
if methoddef_define: if methoddef_define and f.name not in clinic.ifndef_symbols:
clinic.ifndef_symbols.add(f.name)
methoddef_ifndef = normalize_snippet(""" methoddef_ifndef = normalize_snippet("""
#ifndef {methoddef_name} #ifndef {methoddef_name}
#define {methoddef_name} #define {methoddef_name}
@ -1078,7 +1083,8 @@ class CLanguage(Language):
if has_option_groups: if has_option_groups:
self.render_option_group_parsing(f, template_dict) self.render_option_group_parsing(f, template_dict)
for name, destination in clinic.field_destinations.items(): # buffers, not destination
for name, destination in clinic.destination_buffers.items():
template = templates[name] template = templates[name]
if has_option_groups: if has_option_groups:
template = linear_format(template, template = linear_format(template,
@ -1403,12 +1409,48 @@ class BlockPrinter:
self.f.write(text) self.f.write(text)
class BufferSeries:
"""
Behaves like a "defaultlist".
When you ask for an index that doesn't exist yet,
the object grows the list until that item exists.
So o[n] will always work.
Supports negative indices for actual items.
e.g. o[-1] is an element immediately preceding o[0].
"""
def __init__(self):
self._start = 0
self._array = []
self._constructor = _text_accumulator
def __getitem__(self, i):
i -= self._start
if i < 0:
self._start += i
prefix = [self._constructor() for x in range(-i)]
self._array = prefix + self._array
i = 0
while i >= len(self._array):
self._array.append(self._constructor())
return self._array[i]
def clear(self):
for ta in self._array:
ta._text.clear()
def dump(self):
texts = [ta.output() for ta in self._array]
return "".join(texts)
class Destination: class Destination:
def __init__(self, name, type, clinic, *args): def __init__(self, name, type, clinic, *args):
self.name = name self.name = name
self.type = type self.type = type
self.clinic = clinic self.clinic = clinic
valid_types = ('buffer', 'file', 'suppress', 'two-pass') valid_types = ('buffer', 'file', 'suppress')
if type not in valid_types: if type not in valid_types:
fail("Invalid destination type " + repr(type) + " for " + name + " , must be " + ', '.join(valid_types)) fail("Invalid destination type " + repr(type) + " for " + name + " , must be " + ', '.join(valid_types))
extra_arguments = 1 if type == "file" else 0 extra_arguments = 1 if type == "file" else 0
@ -1427,10 +1469,8 @@ class Destination:
d['basename'] = basename d['basename'] = basename
d['basename_root'], d['basename_extension'] = os.path.splitext(filename) d['basename_root'], d['basename_extension'] = os.path.splitext(filename)
self.filename = args[0].format_map(d) self.filename = args[0].format_map(d)
if type == 'two-pass':
self.id = None
self.text, self.append, self._dump = _text_accumulator() self.buffers = BufferSeries()
def __repr__(self): def __repr__(self):
if self.type == 'file': if self.type == 'file':
@ -1442,15 +1482,10 @@ class Destination:
def clear(self): def clear(self):
if self.type != 'buffer': if self.type != 'buffer':
fail("Can't clear destination" + self.name + " , it's not of type buffer") fail("Can't clear destination" + self.name + " , it's not of type buffer")
self.text.clear() self.buffers.clear()
def dump(self): def dump(self):
if self.type == 'two-pass': return self.buffers.dump()
if self.id is None:
self.id = str(uuid.uuid4())
return self.id
fail("You can only dump a two-pass buffer exactly once!")
return self._dump()
# maps strings to Language objects. # maps strings to Language objects.
@ -1489,49 +1524,44 @@ class Clinic:
presets_text = """ presets_text = """
preset block preset block
everything block everything block
methoddef_ifndef buffer 1
docstring_prototype suppress docstring_prototype suppress
parser_prototype suppress parser_prototype suppress
cpp_if suppress cpp_if suppress
cpp_endif suppress cpp_endif suppress
methoddef_ifndef buffer
preset original preset original
everything block everything block
methoddef_ifndef buffer 1
docstring_prototype suppress docstring_prototype suppress
parser_prototype suppress parser_prototype suppress
cpp_if suppress cpp_if suppress
cpp_endif suppress cpp_endif suppress
methoddef_ifndef buffer
preset file preset file
everything file everything file
methoddef_ifndef file 1
docstring_prototype suppress docstring_prototype suppress
parser_prototype suppress parser_prototype suppress
impl_definition block impl_definition block
preset buffer preset buffer
everything buffer everything buffer
methoddef_ifndef buffer 1
impl_definition block
docstring_prototype suppress docstring_prototype suppress
impl_prototype suppress impl_prototype suppress
parser_prototype suppress parser_prototype suppress
impl_definition block
preset partial-buffer preset partial-buffer
everything buffer everything buffer
methoddef_ifndef buffer 1
docstring_prototype block docstring_prototype block
impl_prototype suppress impl_prototype suppress
methoddef_define block methoddef_define block
parser_prototype block parser_prototype block
impl_definition block impl_definition block
preset two-pass
everything buffer
docstring_prototype two-pass
impl_prototype suppress
methoddef_define two-pass
parser_prototype two-pass
impl_definition block
""" """
def __init__(self, language, printer=None, *, force=False, verify=True, filename=None): def __init__(self, language, printer=None, *, force=False, verify=True, filename=None):
@ -1555,12 +1585,11 @@ impl_definition block
self.add_destination("block", "buffer") self.add_destination("block", "buffer")
self.add_destination("suppress", "suppress") self.add_destination("suppress", "suppress")
self.add_destination("buffer", "buffer") self.add_destination("buffer", "buffer")
self.add_destination("two-pass", "two-pass")
if filename: if filename:
self.add_destination("file", "file", "{dirname}/clinic/{basename}.h") self.add_destination("file", "file", "{dirname}/clinic/{basename}.h")
d = self.destinations.get d = self.get_destination_buffer
self.field_destinations = collections.OrderedDict(( self.destination_buffers = collections.OrderedDict((
('cpp_if', d('suppress')), ('cpp_if', d('suppress')),
('docstring_prototype', d('suppress')), ('docstring_prototype', d('suppress')),
('docstring_definition', d('block')), ('docstring_definition', d('block')),
@ -1569,11 +1598,12 @@ impl_definition block
('parser_prototype', d('suppress')), ('parser_prototype', d('suppress')),
('parser_definition', d('block')), ('parser_definition', d('block')),
('cpp_endif', d('suppress')), ('cpp_endif', d('suppress')),
('methoddef_ifndef', d('buffer')), ('methoddef_ifndef', d('buffer', 1)),
('impl_definition', d('block')), ('impl_definition', d('block')),
)) ))
self.field_destinations_stack = [] self.destination_buffers_stack = []
self.ifndef_symbols = set()
self.presets = {} self.presets = {}
preset = None preset = None
@ -1581,37 +1611,43 @@ impl_definition block
line = line.strip() line = line.strip()
if not line: if not line:
continue continue
name, value = line.split() name, value, *options = line.split()
if name == 'preset': if name == 'preset':
self.presets[value] = preset = collections.OrderedDict() self.presets[value] = preset = collections.OrderedDict()
continue continue
destination = self.get_destination(value) if len(options):
index = int(options[0])
else:
index = 0
buffer = self.get_destination_buffer(value, index)
if name == 'everything': if name == 'everything':
for name in self.field_destinations: for name in self.destination_buffers:
preset[name] = destination preset[name] = buffer
continue continue
assert name in self.field_destinations assert name in self.destination_buffers
preset[name] = destination preset[name] = buffer
global clinic global clinic
clinic = self clinic = self
def get_destination(self, name, default=unspecified):
d = self.destinations.get(name)
if not d:
if default is not unspecified:
return default
fail("Destination does not exist: " + repr(name))
return d
def add_destination(self, name, type, *args): def add_destination(self, name, type, *args):
if name in self.destinations: if name in self.destinations:
fail("Destination already exists: " + repr(name)) fail("Destination already exists: " + repr(name))
self.destinations[name] = Destination(name, type, self, *args) self.destinations[name] = Destination(name, type, self, *args)
def get_destination(self, name):
d = self.destinations.get(name)
if not d:
fail("Destination does not exist: " + repr(name))
return d
def get_destination_buffer(self, name, item=0):
d = self.get_destination(name)
return d.buffers[item]
def parse(self, input): def parse(self, input):
printer = self.printer printer = self.printer
self.block_parser = BlockParser(input, self.language, verify=self.verify) self.block_parser = BlockParser(input, self.language, verify=self.verify)
@ -1631,17 +1667,11 @@ impl_definition block
second_pass_replacements = {} second_pass_replacements = {}
# these are destinations not buffers
for name, destination in self.destinations.items(): for name, destination in self.destinations.items():
if destination.type == 'suppress': if destination.type == 'suppress':
continue continue
output = destination._dump() output = destination.dump()
if destination.type == 'two-pass':
if destination.id:
second_pass_replacements[destination.id] = output
elif output:
fail("Two-pass buffer " + repr(name) + " not empty at end of file!")
continue
if output: if output:
@ -3104,43 +3134,43 @@ class DSLParser:
fail("unknown destination command", repr(command)) fail("unknown destination command", repr(command))
def directive_output(self, field, destination=''): def directive_output(self, command_or_name, destination=''):
fd = self.clinic.field_destinations fd = self.clinic.destination_buffers
if field == "preset": if command_or_name == "preset":
preset = self.clinic.presets.get(destination) preset = self.clinic.presets.get(destination)
if not preset: if not preset:
fail("Unknown preset " + repr(destination) + "!") fail("Unknown preset " + repr(destination) + "!")
fd.update(preset) fd.update(preset)
return return
if field == "push": if command_or_name == "push":
self.clinic.field_destinations_stack.append(fd.copy()) self.clinic.destination_buffers_stack.append(fd.copy())
return return
if field == "pop": if command_or_name == "pop":
if not self.clinic.field_destinations_stack: if not self.clinic.destination_buffers_stack:
fail("Can't 'output pop', stack is empty!") fail("Can't 'output pop', stack is empty!")
previous_fd = self.clinic.field_destinations_stack.pop() previous_fd = self.clinic.destination_buffers_stack.pop()
fd.update(previous_fd) fd.update(previous_fd)
return return
# secret command for debugging! # secret command for debugging!
if field == "print": if command_or_name == "print":
self.block.output.append(pprint.pformat(fd)) self.block.output.append(pprint.pformat(fd))
self.block.output.append('\n') self.block.output.append('\n')
return return
d = self.clinic.get_destination(destination) d = self.clinic.get_destination(destination)
if field == "everything": if command_or_name == "everything":
for name in list(fd): for name in list(fd):
fd[name] = d fd[name] = d
return return
if field not in fd: if command_or_name not in fd:
fail("Invalid field " + repr(field) + ", must be one of:\n preset push pop print everything " + " ".join(fd)) fail("Invalid command / destination name " + repr(command_or_name) + ", must be one of:\n preset push pop print everything " + " ".join(fd))
fd[field] = d fd[command_or_name] = d
def directive_dump(self, name): def directive_dump(self, name):
self.block.output.append(self.clinic.get_destination(name).dump()) self.block.output.append(self.clinic.get_destination(name).dump())