diff options
author | Larry Hastings <larry@hastings.org> | 2015-04-03 20:09:02 (GMT) |
---|---|---|
committer | Larry Hastings <larry@hastings.org> | 2015-04-03 20:09:02 (GMT) |
commit | 0759f84d6260bad1234b802212e73fdc5873d261 (patch) | |
tree | 10a4aa8c54a9e95e0f7c19189af3827538f55b24 /Tools | |
parent | 5cf2b7253dc43b203c2f918416b4d25ad1dbfa7d (diff) | |
download | cpython-0759f84d6260bad1234b802212e73fdc5873d261.zip cpython-0759f84d6260bad1234b802212e73fdc5873d261.tar.gz cpython-0759f84d6260bad1234b802212e73fdc5873d261.tar.bz2 |
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.
Diffstat (limited to 'Tools')
-rwxr-xr-x | Tools/clinic/clinic.py | 160 |
1 files changed, 95 insertions, 65 deletions
diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index 64b9bf7..6432951 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -67,14 +67,18 @@ class Unknown: unknown = Unknown() +_text_accumulator_nt = collections.namedtuple("_text_accumulator", "text append output") + def _text_accumulator(): text = [] def output(): s = ''.join(text) text.clear() 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(): """ @@ -88,7 +92,7 @@ def text_accumulator(): empties the 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): @@ -820,7 +824,8 @@ class CLanguage(Language): cpp_if = "#if " + 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(""" #ifndef {methoddef_name} #define {methoddef_name} @@ -1078,7 +1083,8 @@ class CLanguage(Language): if has_option_groups: 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] if has_option_groups: template = linear_format(template, @@ -1403,12 +1409,48 @@ class BlockPrinter: 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: def __init__(self, name, type, clinic, *args): self.name = name self.type = type self.clinic = clinic - valid_types = ('buffer', 'file', 'suppress', 'two-pass') + valid_types = ('buffer', 'file', 'suppress') if type not in valid_types: fail("Invalid destination type " + repr(type) + " for " + name + " , must be " + ', '.join(valid_types)) extra_arguments = 1 if type == "file" else 0 @@ -1427,10 +1469,8 @@ class Destination: d['basename'] = basename d['basename_root'], d['basename_extension'] = os.path.splitext(filename) 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): if self.type == 'file': @@ -1442,15 +1482,10 @@ class Destination: def clear(self): if self.type != 'buffer': fail("Can't clear destination" + self.name + " , it's not of type buffer") - self.text.clear() + self.buffers.clear() def dump(self): - if self.type == 'two-pass': - 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() + return self.buffers.dump() # maps strings to Language objects. @@ -1489,49 +1524,44 @@ class Clinic: presets_text = """ preset block everything block +methoddef_ifndef buffer 1 docstring_prototype suppress parser_prototype suppress cpp_if suppress cpp_endif suppress -methoddef_ifndef buffer preset original everything block +methoddef_ifndef buffer 1 docstring_prototype suppress parser_prototype suppress cpp_if suppress cpp_endif suppress -methoddef_ifndef buffer 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 -impl_definition block preset partial-buffer everything buffer +methoddef_ifndef buffer 1 docstring_prototype block impl_prototype suppress methoddef_define block parser_prototype 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): @@ -1555,12 +1585,11 @@ impl_definition block self.add_destination("block", "buffer") self.add_destination("suppress", "suppress") self.add_destination("buffer", "buffer") - self.add_destination("two-pass", "two-pass") if filename: self.add_destination("file", "file", "{dirname}/clinic/{basename}.h") - d = self.destinations.get - self.field_destinations = collections.OrderedDict(( + d = self.get_destination_buffer + self.destination_buffers = collections.OrderedDict(( ('cpp_if', d('suppress')), ('docstring_prototype', d('suppress')), ('docstring_definition', d('block')), @@ -1569,11 +1598,12 @@ impl_definition block ('parser_prototype', d('suppress')), ('parser_definition', d('block')), ('cpp_endif', d('suppress')), - ('methoddef_ifndef', d('buffer')), + ('methoddef_ifndef', d('buffer', 1)), ('impl_definition', d('block')), )) - self.field_destinations_stack = [] + self.destination_buffers_stack = [] + self.ifndef_symbols = set() self.presets = {} preset = None @@ -1581,36 +1611,42 @@ impl_definition block line = line.strip() if not line: continue - name, value = line.split() + name, value, *options = line.split() if name == 'preset': self.presets[value] = preset = collections.OrderedDict() 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': - for name in self.field_destinations: - preset[name] = destination + for name in self.destination_buffers: + preset[name] = buffer continue - assert name in self.field_destinations - preset[name] = destination + assert name in self.destination_buffers + preset[name] = buffer global clinic clinic = self - def get_destination(self, name, default=unspecified): + def add_destination(self, name, type, *args): + if name in self.destinations: + fail("Destination already exists: " + repr(name)) + self.destinations[name] = Destination(name, type, self, *args) + + def get_destination(self, name): 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): - if name in self.destinations: - fail("Destination already exists: " + repr(name)) - self.destinations[name] = Destination(name, type, self, *args) + def get_destination_buffer(self, name, item=0): + d = self.get_destination(name) + return d.buffers[item] def parse(self, input): printer = self.printer @@ -1631,17 +1667,11 @@ impl_definition block second_pass_replacements = {} + # these are destinations not buffers for name, destination in self.destinations.items(): if destination.type == 'suppress': continue - 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 + output = destination.dump() if output: @@ -3104,43 +3134,43 @@ class DSLParser: fail("unknown destination command", repr(command)) - def directive_output(self, field, destination=''): - fd = self.clinic.field_destinations + def directive_output(self, command_or_name, destination=''): + fd = self.clinic.destination_buffers - if field == "preset": + if command_or_name == "preset": preset = self.clinic.presets.get(destination) if not preset: fail("Unknown preset " + repr(destination) + "!") fd.update(preset) return - if field == "push": - self.clinic.field_destinations_stack.append(fd.copy()) + if command_or_name == "push": + self.clinic.destination_buffers_stack.append(fd.copy()) return - if field == "pop": - if not self.clinic.field_destinations_stack: + if command_or_name == "pop": + if not self.clinic.destination_buffers_stack: 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) return # secret command for debugging! - if field == "print": + if command_or_name == "print": self.block.output.append(pprint.pformat(fd)) self.block.output.append('\n') return d = self.clinic.get_destination(destination) - if field == "everything": + if command_or_name == "everything": for name in list(fd): fd[name] = d return - if field not in fd: - fail("Invalid field " + repr(field) + ", must be one of:\n preset push pop print everything " + " ".join(fd)) - fd[field] = d + if command_or_name not in fd: + fail("Invalid command / destination name " + repr(command_or_name) + ", must be one of:\n preset push pop print everything " + " ".join(fd)) + fd[command_or_name] = d def directive_dump(self, name): self.block.output.append(self.clinic.get_destination(name).dump()) |