gh-113317: Move more formatting helpers into libclinic (#113438)

Move the following global helpers into libclinic:
- format_escape()
- normalize_snippet()
- wrap_declarations()

Also move strip_leading_and_trailing_blank_lines() and make it internal to libclinic.
This commit is contained in:
Erlend E. Aasland 2023-12-23 18:08:10 +01:00 committed by GitHub
parent 8bce593a63
commit ca71987f4e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 160 additions and 159 deletions

View File

@ -3730,7 +3730,7 @@ class FormatHelperTests(unittest.TestCase):
) )
for lines, expected in dataset: for lines, expected in dataset:
with self.subTest(lines=lines, expected=expected): with self.subTest(lines=lines, expected=expected):
out = clinic.strip_leading_and_trailing_blank_lines(lines) out = libclinic.normalize_snippet(lines)
self.assertEqual(out, expected) self.assertEqual(out, expected)
def test_normalize_snippet(self): def test_normalize_snippet(self):
@ -3759,7 +3759,7 @@ class FormatHelperTests(unittest.TestCase):
expected_outputs = {0: zero_indent, 4: four_indent, 8: eight_indent} expected_outputs = {0: zero_indent, 4: four_indent, 8: eight_indent}
for indent, expected in expected_outputs.items(): for indent, expected in expected_outputs.items():
with self.subTest(indent=indent): with self.subTest(indent=indent):
actual = clinic.normalize_snippet(snippet, indent=indent) actual = libclinic.normalize_snippet(snippet, indent=indent)
self.assertEqual(actual, expected) self.assertEqual(actual, expected)
def test_escaped_docstring(self): def test_escaped_docstring(self):
@ -3780,7 +3780,7 @@ class FormatHelperTests(unittest.TestCase):
def test_format_escape(self): def test_format_escape(self):
line = "{}, {a}" line = "{}, {a}"
expected = "{{}}, {{a}}" expected = "{{}}, {{a}}"
out = clinic.format_escape(line) out = libclinic.format_escape(line)
self.assertEqual(out, expected) self.assertEqual(out, expected)
def test_indent_all_lines(self): def test_indent_all_lines(self):

View File

@ -189,12 +189,6 @@ def ensure_legal_c_identifier(s: str) -> str:
return s + "_value" return s + "_value"
return s return s
def format_escape(s: str) -> str:
# double up curly-braces, this string will be used
# as part of a format_map() template later
s = s.replace('{', '{{')
s = s.replace('}', '}}')
return s
def linear_format(s: str, **kwargs: str) -> str: def linear_format(s: str, **kwargs: str) -> str:
""" """
@ -475,34 +469,6 @@ def permute_optional_groups(
return tuple(accumulator) return tuple(accumulator)
def strip_leading_and_trailing_blank_lines(s: str) -> str:
lines = s.rstrip().split('\n')
while lines:
line = lines[0]
if line.strip():
break
del lines[0]
return '\n'.join(lines)
@functools.lru_cache()
def normalize_snippet(
s: str,
*,
indent: int = 0
) -> str:
"""
Reformats s:
* removes leading and trailing blank lines
* ensures that it does not end with a newline
* dedents so the first nonwhite character on any line is at column "indent"
"""
s = strip_leading_and_trailing_blank_lines(s)
s = textwrap.dedent(s)
if indent:
s = textwrap.indent(s, ' ' * indent)
return s
def declare_parser( def declare_parser(
f: Function, f: Function,
*, *,
@ -573,62 +539,7 @@ def declare_parser(
}}; }};
#undef KWTUPLE #undef KWTUPLE
""" % (format_ or fname) """ % (format_ or fname)
return normalize_snippet(declarations) return libclinic.normalize_snippet(declarations)
def wrap_declarations(
text: str,
length: int = 78
) -> str:
"""
A simple-minded text wrapper for C function declarations.
It views a declaration line as looking like this:
xxxxxxxx(xxxxxxxxx,xxxxxxxxx)
If called with length=30, it would wrap that line into
xxxxxxxx(xxxxxxxxx,
xxxxxxxxx)
(If the declaration has zero or one parameters, this
function won't wrap it.)
If this doesn't work properly, it's probably better to
start from scratch with a more sophisticated algorithm,
rather than try and improve/debug this dumb little function.
"""
lines = []
for line in text.split('\n'):
prefix, _, after_l_paren = line.partition('(')
if not after_l_paren:
lines.append(line)
continue
in_paren, _, after_r_paren = after_l_paren.partition(')')
if not _:
lines.append(line)
continue
if ',' not in in_paren:
lines.append(line)
continue
parameters = [x.strip() + ", " for x in in_paren.split(',')]
prefix += "("
if len(prefix) < length:
spaces = " " * len(prefix)
else:
spaces = " " * 4
while parameters:
line = prefix
first = True
while parameters:
if (not first and
(len(line) + len(parameters[0]) > length)):
break
line += parameters.pop(0)
first = False
if not parameters:
line = line.rstrip(", ") + ")" + after_r_paren
lines.append(line.rstrip())
prefix = spaces
return "\n".join(lines)
class CLanguage(Language): class CLanguage(Language):
@ -642,67 +553,67 @@ class CLanguage(Language):
NO_VARARG: Final[str] = "PY_SSIZE_T_MAX" NO_VARARG: Final[str] = "PY_SSIZE_T_MAX"
PARSER_PROTOTYPE_KEYWORD: Final[str] = normalize_snippet(""" PARSER_PROTOTYPE_KEYWORD: Final[str] = libclinic.normalize_snippet("""
static PyObject * static PyObject *
{c_basename}({self_type}{self_name}, PyObject *args, PyObject *kwargs) {c_basename}({self_type}{self_name}, PyObject *args, PyObject *kwargs)
""") """)
PARSER_PROTOTYPE_KEYWORD___INIT__: Final[str] = normalize_snippet(""" PARSER_PROTOTYPE_KEYWORD___INIT__: Final[str] = libclinic.normalize_snippet("""
static int static int
{c_basename}({self_type}{self_name}, PyObject *args, PyObject *kwargs) {c_basename}({self_type}{self_name}, PyObject *args, PyObject *kwargs)
""") """)
PARSER_PROTOTYPE_VARARGS: Final[str] = normalize_snippet(""" PARSER_PROTOTYPE_VARARGS: Final[str] = libclinic.normalize_snippet("""
static PyObject * static PyObject *
{c_basename}({self_type}{self_name}, PyObject *args) {c_basename}({self_type}{self_name}, PyObject *args)
""") """)
PARSER_PROTOTYPE_FASTCALL: Final[str] = normalize_snippet(""" PARSER_PROTOTYPE_FASTCALL: Final[str] = libclinic.normalize_snippet("""
static PyObject * static PyObject *
{c_basename}({self_type}{self_name}, PyObject *const *args, Py_ssize_t nargs) {c_basename}({self_type}{self_name}, PyObject *const *args, Py_ssize_t nargs)
""") """)
PARSER_PROTOTYPE_FASTCALL_KEYWORDS: Final[str] = normalize_snippet(""" PARSER_PROTOTYPE_FASTCALL_KEYWORDS: Final[str] = libclinic.normalize_snippet("""
static PyObject * static PyObject *
{c_basename}({self_type}{self_name}, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) {c_basename}({self_type}{self_name}, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
""") """)
PARSER_PROTOTYPE_DEF_CLASS: Final[str] = normalize_snippet(""" PARSER_PROTOTYPE_DEF_CLASS: Final[str] = libclinic.normalize_snippet("""
static PyObject * static PyObject *
{c_basename}({self_type}{self_name}, PyTypeObject *{defining_class_name}, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) {c_basename}({self_type}{self_name}, PyTypeObject *{defining_class_name}, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
""") """)
PARSER_PROTOTYPE_NOARGS: Final[str] = normalize_snippet(""" PARSER_PROTOTYPE_NOARGS: Final[str] = libclinic.normalize_snippet("""
static PyObject * static PyObject *
{c_basename}({self_type}{self_name}, PyObject *Py_UNUSED(ignored)) {c_basename}({self_type}{self_name}, PyObject *Py_UNUSED(ignored))
""") """)
PARSER_PROTOTYPE_GETTER: Final[str] = normalize_snippet(""" PARSER_PROTOTYPE_GETTER: Final[str] = libclinic.normalize_snippet("""
static PyObject * static PyObject *
{c_basename}({self_type}{self_name}, void *Py_UNUSED(context)) {c_basename}({self_type}{self_name}, void *Py_UNUSED(context))
""") """)
PARSER_PROTOTYPE_SETTER: Final[str] = normalize_snippet(""" PARSER_PROTOTYPE_SETTER: Final[str] = libclinic.normalize_snippet("""
static int static int
{c_basename}({self_type}{self_name}, PyObject *value, void *Py_UNUSED(context)) {c_basename}({self_type}{self_name}, PyObject *value, void *Py_UNUSED(context))
""") """)
METH_O_PROTOTYPE: Final[str] = normalize_snippet(""" METH_O_PROTOTYPE: Final[str] = libclinic.normalize_snippet("""
static PyObject * static PyObject *
{c_basename}({impl_parameters}) {c_basename}({impl_parameters})
""") """)
DOCSTRING_PROTOTYPE_VAR: Final[str] = normalize_snippet(""" DOCSTRING_PROTOTYPE_VAR: Final[str] = libclinic.normalize_snippet("""
PyDoc_VAR({c_basename}__doc__); PyDoc_VAR({c_basename}__doc__);
""") """)
DOCSTRING_PROTOTYPE_STRVAR: Final[str] = normalize_snippet(""" DOCSTRING_PROTOTYPE_STRVAR: Final[str] = libclinic.normalize_snippet("""
PyDoc_STRVAR({c_basename}__doc__, PyDoc_STRVAR({c_basename}__doc__,
{docstring}); {docstring});
""") """)
GETSET_DOCSTRING_PROTOTYPE_STRVAR: Final[str] = normalize_snippet(""" GETSET_DOCSTRING_PROTOTYPE_STRVAR: Final[str] = libclinic.normalize_snippet("""
PyDoc_STRVAR({getset_basename}__doc__, PyDoc_STRVAR({getset_basename}__doc__,
{docstring}); {docstring});
#define {getset_basename}_HAS_DOCSTR #define {getset_basename}_HAS_DOCSTR
""") """)
IMPL_DEFINITION_PROTOTYPE: Final[str] = normalize_snippet(""" IMPL_DEFINITION_PROTOTYPE: Final[str] = libclinic.normalize_snippet("""
static {impl_return_type} static {impl_return_type}
{c_basename}_impl({impl_parameters}) {c_basename}_impl({impl_parameters})
""") """)
METHODDEF_PROTOTYPE_DEFINE: Final[str] = normalize_snippet(r""" METHODDEF_PROTOTYPE_DEFINE: Final[str] = libclinic.normalize_snippet(r"""
#define {methoddef_name} \ #define {methoddef_name} \
{{"{name}", {methoddef_cast}{c_basename}{methoddef_cast_end}, {methoddef_flags}, {c_basename}__doc__}}, {{"{name}", {methoddef_cast}{c_basename}{methoddef_cast_end}, {methoddef_flags}, {c_basename}__doc__}},
""") """)
GETTERDEF_PROTOTYPE_DEFINE: Final[str] = normalize_snippet(r""" GETTERDEF_PROTOTYPE_DEFINE: Final[str] = libclinic.normalize_snippet(r"""
#if defined({getset_basename}_HAS_DOCSTR) #if defined({getset_basename}_HAS_DOCSTR)
# define {getset_basename}_DOCSTR {getset_basename}__doc__ # define {getset_basename}_DOCSTR {getset_basename}__doc__
#else #else
@ -715,7 +626,7 @@ class CLanguage(Language):
# define {getset_name}_GETSETDEF {{"{name}", (getter){getset_basename}_get, NULL, {getset_basename}_DOCSTR}}, # define {getset_name}_GETSETDEF {{"{name}", (getter){getset_basename}_get, NULL, {getset_basename}_DOCSTR}},
#endif #endif
""") """)
SETTERDEF_PROTOTYPE_DEFINE: Final[str] = normalize_snippet(r""" SETTERDEF_PROTOTYPE_DEFINE: Final[str] = libclinic.normalize_snippet(r"""
#if defined({getset_name}_HAS_DOCSTR) #if defined({getset_name}_HAS_DOCSTR)
# define {getset_basename}_DOCSTR {getset_basename}__doc__ # define {getset_basename}_DOCSTR {getset_basename}__doc__
#else #else
@ -728,7 +639,7 @@ class CLanguage(Language):
# define {getset_name}_GETSETDEF {{"{name}", NULL, (setter){getset_basename}_set, NULL}}, # define {getset_name}_GETSETDEF {{"{name}", NULL, (setter){getset_basename}_set, NULL}},
#endif #endif
""") """)
METHODDEF_PROTOTYPE_IFNDEF: Final[str] = normalize_snippet(""" METHODDEF_PROTOTYPE_IFNDEF: Final[str] = libclinic.normalize_snippet("""
#ifndef {methoddef_name} #ifndef {methoddef_name}
#define {methoddef_name} #define {methoddef_name}
#endif /* !defined({methoddef_name}) */ #endif /* !defined({methoddef_name}) */
@ -797,7 +708,7 @@ class CLanguage(Language):
minor=minversion[1], minor=minversion[1],
message=libclinic.c_repr(message), message=libclinic.c_repr(message),
) )
return normalize_snippet(code) return libclinic.normalize_snippet(code)
def deprecate_positional_use( def deprecate_positional_use(
self, self,
@ -848,7 +759,7 @@ class CLanguage(Language):
message=libclinic.wrapped_c_string_literal(message, width=64, message=libclinic.wrapped_c_string_literal(message, width=64,
subsequent_indent=20), subsequent_indent=20),
) )
return normalize_snippet(code, indent=4) return libclinic.normalize_snippet(code, indent=4)
def deprecate_keyword_use( def deprecate_keyword_use(
self, self,
@ -931,7 +842,7 @@ class CLanguage(Language):
message=libclinic.wrapped_c_string_literal(message, width=64, message=libclinic.wrapped_c_string_literal(message, width=64,
subsequent_indent=20), subsequent_indent=20),
) )
return normalize_snippet(code, indent=4) return libclinic.normalize_snippet(code, indent=4)
def output_templates( def output_templates(
self, self,
@ -1036,14 +947,14 @@ class CLanguage(Language):
lines.append(prototype) lines.append(prototype)
parser_body_fields = fields parser_body_fields = fields
preamble = normalize_snippet(""" preamble = libclinic.normalize_snippet("""
{{ {{
{return_value_declaration} {return_value_declaration}
{parser_declarations} {parser_declarations}
{declarations} {declarations}
{initializers} {initializers}
""") + "\n" """) + "\n"
finale = normalize_snippet(""" finale = libclinic.normalize_snippet("""
{modifications} {modifications}
{lock} {lock}
{return_value} = {c_basename}_impl({impl_arguments}); {return_value} = {c_basename}_impl({impl_arguments});
@ -1095,7 +1006,7 @@ class CLanguage(Language):
parser_prototype = self.PARSER_PROTOTYPE_DEF_CLASS parser_prototype = self.PARSER_PROTOTYPE_DEF_CLASS
return_error = ('return NULL;' if simple_return return_error = ('return NULL;' if simple_return
else 'goto exit;') else 'goto exit;')
parser_code = [normalize_snippet(""" parser_code = [libclinic.normalize_snippet("""
if (nargs) {{ if (nargs) {{
PyErr_SetString(PyExc_TypeError, "{name}() takes no arguments"); PyErr_SetString(PyExc_TypeError, "{name}() takes no arguments");
%s %s
@ -1135,7 +1046,7 @@ class CLanguage(Language):
argname = 'arg' argname = 'arg'
if parameters[0].name == argname: if parameters[0].name == argname:
argname += '_' argname += '_'
parser_prototype = normalize_snippet(""" parser_prototype = libclinic.normalize_snippet("""
static PyObject * static PyObject *
{c_basename}({self_type}{self_name}, PyObject *%s) {c_basename}({self_type}{self_name}, PyObject *%s)
""" % argname) """ % argname)
@ -1149,7 +1060,7 @@ class CLanguage(Language):
}} }}
""" % argname """ % argname
parser_definition = parser_body(parser_prototype, parser_definition = parser_body(parser_prototype,
normalize_snippet(parsearg, indent=4)) libclinic.normalize_snippet(parsearg, indent=4))
elif has_option_groups: elif has_option_groups:
# positional parameters with option groups # positional parameters with option groups
@ -1187,11 +1098,12 @@ class CLanguage(Language):
if limited_capi: if limited_capi:
parser_code = [] parser_code = []
if nargs != 'nargs': if nargs != 'nargs':
parser_code.append(normalize_snippet(f'Py_ssize_t nargs = {nargs};', indent=4)) nargs_def = f'Py_ssize_t nargs = {nargs};'
parser_code.append(libclinic.normalize_snippet(nargs_def, indent=4))
nargs = 'nargs' nargs = 'nargs'
if min_pos == max_args: if min_pos == max_args:
pl = '' if min_pos == 1 else 's' pl = '' if min_pos == 1 else 's'
parser_code.append(normalize_snippet(f""" parser_code.append(libclinic.normalize_snippet(f"""
if ({nargs} != {min_pos}) {{{{ if ({nargs} != {min_pos}) {{{{
PyErr_Format(PyExc_TypeError, "{{name}} expected {min_pos} argument{pl}, got %zd", {nargs}); PyErr_Format(PyExc_TypeError, "{{name}} expected {min_pos} argument{pl}, got %zd", {nargs});
goto exit; goto exit;
@ -1201,7 +1113,7 @@ class CLanguage(Language):
else: else:
if min_pos: if min_pos:
pl = '' if min_pos == 1 else 's' pl = '' if min_pos == 1 else 's'
parser_code.append(normalize_snippet(f""" parser_code.append(libclinic.normalize_snippet(f"""
if ({nargs} < {min_pos}) {{{{ if ({nargs} < {min_pos}) {{{{
PyErr_Format(PyExc_TypeError, "{{name}} expected at least {min_pos} argument{pl}, got %zd", {nargs}); PyErr_Format(PyExc_TypeError, "{{name}} expected at least {min_pos} argument{pl}, got %zd", {nargs});
goto exit; goto exit;
@ -1210,7 +1122,7 @@ class CLanguage(Language):
indent=4)) indent=4))
if max_args != self.NO_VARARG: if max_args != self.NO_VARARG:
pl = '' if max_args == 1 else 's' pl = '' if max_args == 1 else 's'
parser_code.append(normalize_snippet(f""" parser_code.append(libclinic.normalize_snippet(f"""
if ({nargs} > {max_args}) {{{{ if ({nargs} > {max_args}) {{{{
PyErr_Format(PyExc_TypeError, "{{name}} expected at most {max_args} argument{pl}, got %zd", {nargs}); PyErr_Format(PyExc_TypeError, "{{name}} expected at most {max_args} argument{pl}, got %zd", {nargs});
goto exit; goto exit;
@ -1220,7 +1132,7 @@ class CLanguage(Language):
else: else:
clinic.add_include('pycore_modsupport.h', clinic.add_include('pycore_modsupport.h',
'_PyArg_CheckPositional()') '_PyArg_CheckPositional()')
parser_code = [normalize_snippet(f""" parser_code = [libclinic.normalize_snippet(f"""
if (!_PyArg_CheckPositional("{{name}}", {nargs}, {min_pos}, {max_args})) {{{{ if (!_PyArg_CheckPositional("{{name}}", {nargs}, {min_pos}, {max_args})) {{{{
goto exit; goto exit;
}}}} }}}}
@ -1230,7 +1142,7 @@ class CLanguage(Language):
for i, p in enumerate(parameters): for i, p in enumerate(parameters):
if p.is_vararg(): if p.is_vararg():
if fastcall: if fastcall:
parser_code.append(normalize_snippet(""" parser_code.append(libclinic.normalize_snippet("""
%s = PyTuple_New(%s); %s = PyTuple_New(%s);
if (!%s) {{ if (!%s) {{
goto exit; goto exit;
@ -1247,7 +1159,7 @@ class CLanguage(Language):
max_pos max_pos
), indent=4)) ), indent=4))
else: else:
parser_code.append(normalize_snippet(""" parser_code.append(libclinic.normalize_snippet("""
%s = PyTuple_GetSlice(%d, -1); %s = PyTuple_GetSlice(%d, -1);
""" % ( """ % (
p.converter.parser_name, p.converter.parser_name,
@ -1263,12 +1175,12 @@ class CLanguage(Language):
break break
if has_optional or p.is_optional(): if has_optional or p.is_optional():
has_optional = True has_optional = True
parser_code.append(normalize_snippet(""" parser_code.append(libclinic.normalize_snippet("""
if (%s < %d) {{ if (%s < %d) {{
goto skip_optional; goto skip_optional;
}} }}
""", indent=4) % (nargs, i + 1)) """, indent=4) % (nargs, i + 1))
parser_code.append(normalize_snippet(parsearg, indent=4)) parser_code.append(libclinic.normalize_snippet(parsearg, indent=4))
if parser_code is not None: if parser_code is not None:
if has_optional: if has_optional:
@ -1279,7 +1191,7 @@ class CLanguage(Language):
if fastcall: if fastcall:
clinic.add_include('pycore_modsupport.h', clinic.add_include('pycore_modsupport.h',
'_PyArg_ParseStack()') '_PyArg_ParseStack()')
parser_code = [normalize_snippet(""" parser_code = [libclinic.normalize_snippet("""
if (!_PyArg_ParseStack(args, nargs, "{format_units}:{name}", if (!_PyArg_ParseStack(args, nargs, "{format_units}:{name}",
{parse_arguments})) {{ {parse_arguments})) {{
goto exit; goto exit;
@ -1288,7 +1200,7 @@ class CLanguage(Language):
else: else:
flags = "METH_VARARGS" flags = "METH_VARARGS"
parser_prototype = self.PARSER_PROTOTYPE_VARARGS parser_prototype = self.PARSER_PROTOTYPE_VARARGS
parser_code = [normalize_snippet(""" parser_code = [libclinic.normalize_snippet("""
if (!PyArg_ParseTuple(args, "{format_units}:{name}", if (!PyArg_ParseTuple(args, "{format_units}:{name}",
{parse_arguments})) {{ {parse_arguments})) {{
goto exit; goto exit;
@ -1343,7 +1255,7 @@ class CLanguage(Language):
declarations += "\nPyObject *argsbuf[%s];" % len(converters) declarations += "\nPyObject *argsbuf[%s];" % len(converters)
if has_optional_kw: if has_optional_kw:
declarations += "\nPy_ssize_t noptargs = %s + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - %d;" % (nargs, min_pos + min_kw_only) declarations += "\nPy_ssize_t noptargs = %s + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - %d;" % (nargs, min_pos + min_kw_only)
parser_code = [normalize_snippet(""" parser_code = [libclinic.normalize_snippet("""
args = %s(args, nargs, NULL, kwnames, &_parser, %s, argsbuf); args = %s(args, nargs, NULL, kwnames, &_parser, %s, argsbuf);
if (!args) {{ if (!args) {{
goto exit; goto exit;
@ -1361,7 +1273,7 @@ class CLanguage(Language):
declarations += "\nPy_ssize_t nargs = PyTuple_GET_SIZE(args);" declarations += "\nPy_ssize_t nargs = PyTuple_GET_SIZE(args);"
if has_optional_kw: if has_optional_kw:
declarations += "\nPy_ssize_t noptargs = %s + (kwargs ? PyDict_GET_SIZE(kwargs) : 0) - %d;" % (nargs, min_pos + min_kw_only) declarations += "\nPy_ssize_t noptargs = %s + (kwargs ? PyDict_GET_SIZE(kwargs) : 0) - %d;" % (nargs, min_pos + min_kw_only)
parser_code = [normalize_snippet(""" parser_code = [libclinic.normalize_snippet("""
fastargs = %s(_PyTuple_CAST(args)->ob_item, nargs, kwargs, NULL, &_parser, %s, argsbuf); fastargs = %s(_PyTuple_CAST(args)->ob_item, nargs, kwargs, NULL, &_parser, %s, argsbuf);
if (!fastargs) {{ if (!fastargs) {{
goto exit; goto exit;
@ -1394,19 +1306,19 @@ class CLanguage(Language):
parser_code.append("%s:" % add_label) parser_code.append("%s:" % add_label)
add_label = None add_label = None
if not p.is_optional(): if not p.is_optional():
parser_code.append(normalize_snippet(parsearg, indent=4)) parser_code.append(libclinic.normalize_snippet(parsearg, indent=4))
elif i < pos_only: elif i < pos_only:
add_label = 'skip_optional_posonly' add_label = 'skip_optional_posonly'
parser_code.append(normalize_snippet(""" parser_code.append(libclinic.normalize_snippet("""
if (nargs < %d) {{ if (nargs < %d) {{
goto %s; goto %s;
}} }}
""" % (i + 1, add_label), indent=4)) """ % (i + 1, add_label), indent=4))
if has_optional_kw: if has_optional_kw:
parser_code.append(normalize_snippet(""" parser_code.append(libclinic.normalize_snippet("""
noptargs--; noptargs--;
""", indent=4)) """, indent=4))
parser_code.append(normalize_snippet(parsearg, indent=4)) parser_code.append(libclinic.normalize_snippet(parsearg, indent=4))
else: else:
if i < max_pos: if i < max_pos:
label = 'skip_optional_pos' label = 'skip_optional_pos'
@ -1418,20 +1330,20 @@ class CLanguage(Language):
first_opt += 1 first_opt += 1
if i == first_opt: if i == first_opt:
add_label = label add_label = label
parser_code.append(normalize_snippet(""" parser_code.append(libclinic.normalize_snippet("""
if (!noptargs) {{ if (!noptargs) {{
goto %s; goto %s;
}} }}
""" % add_label, indent=4)) """ % add_label, indent=4))
if i + 1 == len(parameters): if i + 1 == len(parameters):
parser_code.append(normalize_snippet(parsearg, indent=4)) parser_code.append(libclinic.normalize_snippet(parsearg, indent=4))
else: else:
add_label = label add_label = label
parser_code.append(normalize_snippet(""" parser_code.append(libclinic.normalize_snippet("""
if (%s) {{ if (%s) {{
""" % (argname_fmt % i), indent=4)) """ % (argname_fmt % i), indent=4))
parser_code.append(normalize_snippet(parsearg, indent=8)) parser_code.append(libclinic.normalize_snippet(parsearg, indent=8))
parser_code.append(normalize_snippet(""" parser_code.append(libclinic.normalize_snippet("""
if (!--noptargs) {{ if (!--noptargs) {{
goto %s; goto %s;
}} }}
@ -1450,7 +1362,7 @@ class CLanguage(Language):
assert not fastcall assert not fastcall
flags = "METH_VARARGS|METH_KEYWORDS" flags = "METH_VARARGS|METH_KEYWORDS"
parser_prototype = self.PARSER_PROTOTYPE_KEYWORD parser_prototype = self.PARSER_PROTOTYPE_KEYWORD
parser_code = [normalize_snippet(""" parser_code = [libclinic.normalize_snippet("""
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "{format_units}:{name}", _keywords, if (!PyArg_ParseTupleAndKeywords(args, kwargs, "{format_units}:{name}", _keywords,
{parse_arguments})) {parse_arguments}))
goto exit; goto exit;
@ -1462,7 +1374,7 @@ class CLanguage(Language):
elif fastcall: elif fastcall:
clinic.add_include('pycore_modsupport.h', clinic.add_include('pycore_modsupport.h',
'_PyArg_ParseStackAndKeywords()') '_PyArg_ParseStackAndKeywords()')
parser_code = [normalize_snippet(""" parser_code = [libclinic.normalize_snippet("""
if (!_PyArg_ParseStackAndKeywords(args, nargs, kwnames, &_parser{parse_arguments_comma} if (!_PyArg_ParseStackAndKeywords(args, nargs, kwnames, &_parser{parse_arguments_comma}
{parse_arguments})) {{ {parse_arguments})) {{
goto exit; goto exit;
@ -1471,7 +1383,7 @@ class CLanguage(Language):
else: else:
clinic.add_include('pycore_modsupport.h', clinic.add_include('pycore_modsupport.h',
'_PyArg_ParseTupleAndKeywordsFast()') '_PyArg_ParseTupleAndKeywordsFast()')
parser_code = [normalize_snippet(""" parser_code = [libclinic.normalize_snippet("""
if (!_PyArg_ParseTupleAndKeywordsFast(args, kwargs, &_parser, if (!_PyArg_ParseTupleAndKeywordsFast(args, kwargs, &_parser,
{parse_arguments})) {{ {parse_arguments})) {{
goto exit; goto exit;
@ -1518,7 +1430,7 @@ class CLanguage(Language):
declarations = '{base_type_ptr}' declarations = '{base_type_ptr}'
clinic.add_include('pycore_modsupport.h', clinic.add_include('pycore_modsupport.h',
'_PyArg_NoKeywords()') '_PyArg_NoKeywords()')
fields.insert(0, normalize_snippet(""" fields.insert(0, libclinic.normalize_snippet("""
if ({self_type_check}!_PyArg_NoKeywords("{name}", kwargs)) {{ if ({self_type_check}!_PyArg_NoKeywords("{name}", kwargs)) {{
goto exit; goto exit;
}} }}
@ -1526,7 +1438,7 @@ class CLanguage(Language):
if not parses_positional: if not parses_positional:
clinic.add_include('pycore_modsupport.h', clinic.add_include('pycore_modsupport.h',
'_PyArg_NoPositional()') '_PyArg_NoPositional()')
fields.insert(0, normalize_snippet(""" fields.insert(0, libclinic.normalize_snippet("""
if ({self_type_check}!_PyArg_NoPositional("{name}", args)) {{ if ({self_type_check}!_PyArg_NoPositional("{name}", args)) {{
goto exit; goto exit;
}} }}
@ -1715,7 +1627,7 @@ class CLanguage(Language):
out.append(' goto exit;\n') out.append(' goto exit;\n')
out.append("}") out.append("}")
template_dict['option_group_parsing'] = format_escape("".join(out)) template_dict['option_group_parsing'] = libclinic.format_escape("".join(out))
def render_function( def render_function(
self, self,
@ -1825,7 +1737,7 @@ class CLanguage(Language):
else: else:
template_dict['impl_return_type'] = f.return_converter.type template_dict['impl_return_type'] = f.return_converter.type
template_dict['declarations'] = format_escape("\n".join(data.declarations)) template_dict['declarations'] = libclinic.format_escape("\n".join(data.declarations))
template_dict['initializers'] = "\n\n".join(data.initializers) template_dict['initializers'] = "\n\n".join(data.initializers)
template_dict['modifications'] = '\n\n'.join(data.modifications) template_dict['modifications'] = '\n\n'.join(data.modifications)
template_dict['keywords_c'] = ' '.join('"' + k + '",' template_dict['keywords_c'] = ' '.join('"' + k + '",'
@ -1841,9 +1753,11 @@ class CLanguage(Language):
template_dict['parse_arguments_comma'] = ''; template_dict['parse_arguments_comma'] = '';
template_dict['impl_parameters'] = ", ".join(data.impl_parameters) template_dict['impl_parameters'] = ", ".join(data.impl_parameters)
template_dict['impl_arguments'] = ", ".join(data.impl_arguments) template_dict['impl_arguments'] = ", ".join(data.impl_arguments)
template_dict['return_conversion'] = format_escape("".join(data.return_conversion).rstrip())
template_dict['post_parsing'] = format_escape("".join(data.post_parsing).rstrip()) template_dict['return_conversion'] = libclinic.format_escape("".join(data.return_conversion).rstrip())
template_dict['cleanup'] = format_escape("".join(data.cleanup)) template_dict['post_parsing'] = libclinic.format_escape("".join(data.post_parsing).rstrip())
template_dict['cleanup'] = libclinic.format_escape("".join(data.cleanup))
template_dict['return_value'] = data.return_value template_dict['return_value'] = data.return_value
template_dict['lock'] = "\n".join(data.lock) template_dict['lock'] = "\n".join(data.lock)
template_dict['unlock'] = "\n".join(data.unlock) template_dict['unlock'] = "\n".join(data.unlock)
@ -1887,7 +1801,7 @@ class CLanguage(Language):
# mild hack: # mild hack:
# reflow long impl declarations # reflow long impl declarations
if name in {"impl_prototype", "impl_definition"}: if name in {"impl_prototype", "impl_definition"}:
s = wrap_declarations(s) s = libclinic.wrap_declarations(s)
if clinic.line_prefix: if clinic.line_prefix:
s = libclinic.indent_all_lines(s, clinic.line_prefix) s = libclinic.indent_all_lines(s, clinic.line_prefix)

View File

@ -1,25 +1,31 @@
from typing import Final from typing import Final
from .formatting import ( from .formatting import (
SIG_END_MARKER,
c_repr, c_repr,
docstring_for_c_string, docstring_for_c_string,
format_escape,
indent_all_lines, indent_all_lines,
normalize_snippet,
pprint_words, pprint_words,
suffix_all_lines, suffix_all_lines,
wrap_declarations,
wrapped_c_string_literal, wrapped_c_string_literal,
SIG_END_MARKER,
) )
__all__ = [ __all__ = [
# Formatting helpers # Formatting helpers
"SIG_END_MARKER",
"c_repr", "c_repr",
"docstring_for_c_string", "docstring_for_c_string",
"format_escape",
"indent_all_lines", "indent_all_lines",
"normalize_snippet",
"pprint_words", "pprint_words",
"suffix_all_lines", "suffix_all_lines",
"wrap_declarations",
"wrapped_c_string_literal", "wrapped_c_string_literal",
"SIG_END_MARKER",
] ]

View File

@ -1,5 +1,6 @@
"""A collection of string formatting helpers.""" """A collection of string formatting helpers."""
import functools
import textwrap import textwrap
from typing import Final from typing import Final
@ -59,11 +60,7 @@ def wrapped_c_string_literal(
return initial_indent * " " + c_repr(separator.join(wrapped)) return initial_indent * " " + c_repr(separator.join(wrapped))
def _add_prefix_and_suffix( def _add_prefix_and_suffix(text: str, *, prefix: str = "", suffix: str = "") -> str:
text: str,
prefix: str = "",
suffix: str = ""
) -> str:
"""Return 'text' with 'prefix' prepended and 'suffix' appended to all lines. """Return 'text' with 'prefix' prepended and 'suffix' appended to all lines.
If the last line is empty, it remains unchanged. If the last line is empty, it remains unchanged.
@ -90,3 +87,87 @@ def pprint_words(items: list[str]) -> str:
if len(items) <= 2: if len(items) <= 2:
return " and ".join(items) return " and ".join(items)
return ", ".join(items[:-1]) + " and " + items[-1] return ", ".join(items[:-1]) + " and " + items[-1]
def _strip_leading_and_trailing_blank_lines(text: str) -> str:
lines = text.rstrip().split("\n")
while lines:
line = lines[0]
if line.strip():
break
del lines[0]
return "\n".join(lines)
@functools.lru_cache()
def normalize_snippet(text: str, *, indent: int = 0) -> str:
"""
Reformats 'text':
* removes leading and trailing blank lines
* ensures that it does not end with a newline
* dedents so the first nonwhite character on any line is at column "indent"
"""
text = _strip_leading_and_trailing_blank_lines(text)
text = textwrap.dedent(text)
if indent:
text = textwrap.indent(text, " " * indent)
return text
def format_escape(text: str) -> str:
# double up curly-braces, this string will be used
# as part of a format_map() template later
text = text.replace("{", "{{")
text = text.replace("}", "}}")
return text
def wrap_declarations(text: str, length: int = 78) -> str:
"""
A simple-minded text wrapper for C function declarations.
It views a declaration line as looking like this:
xxxxxxxx(xxxxxxxxx,xxxxxxxxx)
If called with length=30, it would wrap that line into
xxxxxxxx(xxxxxxxxx,
xxxxxxxxx)
(If the declaration has zero or one parameters, this
function won't wrap it.)
If this doesn't work properly, it's probably better to
start from scratch with a more sophisticated algorithm,
rather than try and improve/debug this dumb little function.
"""
lines = []
for line in text.split("\n"):
prefix, _, after_l_paren = line.partition("(")
if not after_l_paren:
lines.append(line)
continue
in_paren, _, after_r_paren = after_l_paren.partition(")")
if not _:
lines.append(line)
continue
if "," not in in_paren:
lines.append(line)
continue
parameters = [x.strip() + ", " for x in in_paren.split(",")]
prefix += "("
if len(prefix) < length:
spaces = " " * len(prefix)
else:
spaces = " " * 4
while parameters:
line = prefix
first = True
while parameters:
if not first and (len(line) + len(parameters[0]) > length):
break
line += parameters.pop(0)
first = False
if not parameters:
line = line.rstrip(", ") + ")" + after_r_paren
lines.append(line.rstrip())
prefix = spaces
return "\n".join(lines)