summaryrefslogtreecommitdiffstats
path: root/Tools/clinic/clinic.py
diff options
context:
space:
mode:
authorLarry Hastings <larry@hastings.org>2015-04-03 20:09:02 (GMT)
committerLarry Hastings <larry@hastings.org>2015-04-03 20:09:02 (GMT)
commit0759f84d6260bad1234b802212e73fdc5873d261 (patch)
tree10a4aa8c54a9e95e0f7c19189af3827538f55b24 /Tools/clinic/clinic.py
parent5cf2b7253dc43b203c2f918416b4d25ad1dbfa7d (diff)
downloadcpython-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/clinic/clinic.py')
-rwxr-xr-xTools/clinic/clinic.py160
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())