Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
allendowney
GitHub Repository: allendowney/cpython
Path: blob/main/Tools/clinic/clinic.py
12 views
1
#!/usr/bin/env python3
2
#
3
# Argument Clinic
4
# Copyright 2012-2013 by Larry Hastings.
5
# Licensed to the PSF under a contributor agreement.
6
#
7
8
import abc
9
import ast
10
import builtins as bltns
11
import collections
12
import contextlib
13
import copy
14
import cpp
15
import enum
16
import functools
17
import hashlib
18
import inspect
19
import io
20
import itertools
21
import os
22
import pprint
23
import re
24
import shlex
25
import string
26
import sys
27
import textwrap
28
import traceback
29
30
from collections.abc import Callable
31
from types import FunctionType, NoneType
32
from typing import Any, Final, NamedTuple, NoReturn, Literal, overload
33
34
# TODO:
35
#
36
# soon:
37
#
38
# * allow mixing any two of {positional-only, positional-or-keyword,
39
# keyword-only}
40
# * dict constructor uses positional-only and keyword-only
41
# * max and min use positional only with an optional group
42
# and keyword-only
43
#
44
45
version = '1'
46
47
NO_VARARG = "PY_SSIZE_T_MAX"
48
CLINIC_PREFIX = "__clinic_"
49
CLINIC_PREFIXED_ARGS = {
50
"_keywords",
51
"_parser",
52
"args",
53
"argsbuf",
54
"fastargs",
55
"kwargs",
56
"kwnames",
57
"nargs",
58
"noptargs",
59
"return_value",
60
}
61
62
63
class Sentinels(enum.Enum):
64
unspecified = "unspecified"
65
unknown = "unknown"
66
67
def __repr__(self) -> str:
68
return f"<{self.value.capitalize()}>"
69
70
71
unspecified: Final = Sentinels.unspecified
72
unknown: Final = Sentinels.unknown
73
74
75
# This one needs to be a distinct class, unlike the other two
76
class Null:
77
def __repr__(self) -> str:
78
return '<Null>'
79
80
81
NULL = Null()
82
83
sig_end_marker = '--'
84
85
Appender = Callable[[str], None]
86
Outputter = Callable[[], str]
87
TemplateDict = dict[str, str]
88
89
class _TextAccumulator(NamedTuple):
90
text: list[str]
91
append: Appender
92
output: Outputter
93
94
def _text_accumulator() -> _TextAccumulator:
95
text: list[str] = []
96
def output():
97
s = ''.join(text)
98
text.clear()
99
return s
100
return _TextAccumulator(text, text.append, output)
101
102
103
class TextAccumulator(NamedTuple):
104
append: Appender
105
output: Outputter
106
107
def text_accumulator() -> TextAccumulator:
108
"""
109
Creates a simple text accumulator / joiner.
110
111
Returns a pair of callables:
112
append, output
113
"append" appends a string to the accumulator.
114
"output" returns the contents of the accumulator
115
joined together (''.join(accumulator)) and
116
empties the accumulator.
117
"""
118
text, append, output = _text_accumulator()
119
return TextAccumulator(append, output)
120
121
@overload
122
def warn_or_fail(
123
*args: object,
124
fail: Literal[True],
125
filename: str | None = None,
126
line_number: int | None = None,
127
) -> NoReturn: ...
128
129
@overload
130
def warn_or_fail(
131
*args: object,
132
fail: Literal[False] = False,
133
filename: str | None = None,
134
line_number: int | None = None,
135
) -> None: ...
136
137
def warn_or_fail(
138
*args: object,
139
fail: bool = False,
140
filename: str | None = None,
141
line_number: int | None = None,
142
) -> None:
143
joined = " ".join([str(a) for a in args])
144
add, output = text_accumulator()
145
if fail:
146
add("Error")
147
else:
148
add("Warning")
149
if clinic:
150
if filename is None:
151
filename = clinic.filename
152
if getattr(clinic, 'block_parser', None) and (line_number is None):
153
line_number = clinic.block_parser.line_number
154
if filename is not None:
155
add(' in file "' + filename + '"')
156
if line_number is not None:
157
add(" on line " + str(line_number))
158
add(':\n')
159
add(joined)
160
print(output())
161
if fail:
162
sys.exit(-1)
163
164
165
def warn(
166
*args: object,
167
filename: str | None = None,
168
line_number: int | None = None,
169
) -> None:
170
return warn_or_fail(*args, filename=filename, line_number=line_number, fail=False)
171
172
def fail(
173
*args: object,
174
filename: str | None = None,
175
line_number: int | None = None,
176
) -> NoReturn:
177
warn_or_fail(*args, filename=filename, line_number=line_number, fail=True)
178
179
180
def quoted_for_c_string(s: str) -> str:
181
for old, new in (
182
('\\', '\\\\'), # must be first!
183
('"', '\\"'),
184
("'", "\\'"),
185
):
186
s = s.replace(old, new)
187
return s
188
189
def c_repr(s: str) -> str:
190
return '"' + s + '"'
191
192
193
is_legal_c_identifier = re.compile('^[A-Za-z_][A-Za-z0-9_]*$').match
194
195
def is_legal_py_identifier(s: str) -> bool:
196
return all(is_legal_c_identifier(field) for field in s.split('.'))
197
198
# identifiers that are okay in Python but aren't a good idea in C.
199
# so if they're used Argument Clinic will add "_value" to the end
200
# of the name in C.
201
c_keywords = set("""
202
asm auto break case char const continue default do double
203
else enum extern float for goto if inline int long
204
register return short signed sizeof static struct switch
205
typedef typeof union unsigned void volatile while
206
""".strip().split())
207
208
def ensure_legal_c_identifier(s: str) -> str:
209
# for now, just complain if what we're given isn't legal
210
if not is_legal_c_identifier(s):
211
fail("Illegal C identifier:", s)
212
# but if we picked a C keyword, pick something else
213
if s in c_keywords:
214
return s + "_value"
215
return s
216
217
def rstrip_lines(s: str) -> str:
218
text, add, output = _text_accumulator()
219
for line in s.split('\n'):
220
add(line.rstrip())
221
add('\n')
222
text.pop()
223
return output()
224
225
def format_escape(s: str) -> str:
226
# double up curly-braces, this string will be used
227
# as part of a format_map() template later
228
s = s.replace('{', '{{')
229
s = s.replace('}', '}}')
230
return s
231
232
def linear_format(s: str, **kwargs: str) -> str:
233
"""
234
Perform str.format-like substitution, except:
235
* The strings substituted must be on lines by
236
themselves. (This line is the "source line".)
237
* If the substitution text is empty, the source line
238
is removed in the output.
239
* If the field is not recognized, the original line
240
is passed unmodified through to the output.
241
* If the substitution text is not empty:
242
* Each line of the substituted text is indented
243
by the indent of the source line.
244
* A newline will be added to the end.
245
"""
246
247
add, output = text_accumulator()
248
for line in s.split('\n'):
249
indent, curly, trailing = line.partition('{')
250
if not curly:
251
add(line)
252
add('\n')
253
continue
254
255
name, curly, trailing = trailing.partition('}')
256
if not curly or name not in kwargs:
257
add(line)
258
add('\n')
259
continue
260
261
if trailing:
262
fail("Text found after {" + name + "} block marker! It must be on a line by itself.")
263
if indent.strip():
264
fail("Non-whitespace characters found before {" + name + "} block marker! It must be on a line by itself.")
265
266
value = kwargs[name]
267
if not value:
268
continue
269
270
value = textwrap.indent(rstrip_lines(value), indent)
271
add(value)
272
add('\n')
273
274
return output()[:-1]
275
276
def indent_all_lines(s: str, prefix: str) -> str:
277
"""
278
Returns 's', with 'prefix' prepended to all lines.
279
280
If the last line is empty, prefix is not prepended
281
to it. (If s is blank, returns s unchanged.)
282
283
(textwrap.indent only adds to non-blank lines.)
284
"""
285
split = s.split('\n')
286
last = split.pop()
287
final = []
288
for line in split:
289
final.append(prefix)
290
final.append(line)
291
final.append('\n')
292
if last:
293
final.append(prefix)
294
final.append(last)
295
return ''.join(final)
296
297
def suffix_all_lines(s: str, suffix: str) -> str:
298
"""
299
Returns 's', with 'suffix' appended to all lines.
300
301
If the last line is empty, suffix is not appended
302
to it. (If s is blank, returns s unchanged.)
303
"""
304
split = s.split('\n')
305
last = split.pop()
306
final = []
307
for line in split:
308
final.append(line)
309
final.append(suffix)
310
final.append('\n')
311
if last:
312
final.append(last)
313
final.append(suffix)
314
return ''.join(final)
315
316
317
def version_splitter(s: str) -> tuple[int, ...]:
318
"""Splits a version string into a tuple of integers.
319
320
The following ASCII characters are allowed, and employ
321
the following conversions:
322
a -> -3
323
b -> -2
324
c -> -1
325
(This permits Python-style version strings such as "1.4b3".)
326
"""
327
version: list[int] = []
328
accumulator: list[str] = []
329
def flush() -> None:
330
if not accumulator:
331
raise ValueError('Unsupported version string: ' + repr(s))
332
version.append(int(''.join(accumulator)))
333
accumulator.clear()
334
335
for c in s:
336
if c.isdigit():
337
accumulator.append(c)
338
elif c == '.':
339
flush()
340
elif c in 'abc':
341
flush()
342
version.append('abc'.index(c) - 3)
343
else:
344
raise ValueError('Illegal character ' + repr(c) + ' in version string ' + repr(s))
345
flush()
346
return tuple(version)
347
348
def version_comparitor(version1: str, version2: str) -> Literal[-1, 0, 1]:
349
iterator = itertools.zip_longest(version_splitter(version1), version_splitter(version2), fillvalue=0)
350
for i, (a, b) in enumerate(iterator):
351
if a < b:
352
return -1
353
if a > b:
354
return 1
355
return 0
356
357
358
class CRenderData:
359
def __init__(self):
360
361
# The C statements to declare variables.
362
# Should be full lines with \n eol characters.
363
self.declarations = []
364
365
# The C statements required to initialize the variables before the parse call.
366
# Should be full lines with \n eol characters.
367
self.initializers = []
368
369
# The C statements needed to dynamically modify the values
370
# parsed by the parse call, before calling the impl.
371
self.modifications = []
372
373
# The entries for the "keywords" array for PyArg_ParseTuple.
374
# Should be individual strings representing the names.
375
self.keywords = []
376
377
# The "format units" for PyArg_ParseTuple.
378
# Should be individual strings that will get
379
self.format_units = []
380
381
# The varargs arguments for PyArg_ParseTuple.
382
self.parse_arguments = []
383
384
# The parameter declarations for the impl function.
385
self.impl_parameters = []
386
387
# The arguments to the impl function at the time it's called.
388
self.impl_arguments = []
389
390
# For return converters: the name of the variable that
391
# should receive the value returned by the impl.
392
self.return_value = "return_value"
393
394
# For return converters: the code to convert the return
395
# value from the parse function. This is also where
396
# you should check the _return_value for errors, and
397
# "goto exit" if there are any.
398
self.return_conversion = []
399
self.converter_retval = "_return_value"
400
401
# The C statements required to do some operations
402
# after the end of parsing but before cleaning up.
403
# These operations may be, for example, memory deallocations which
404
# can only be done without any error happening during argument parsing.
405
self.post_parsing = []
406
407
# The C statements required to clean up after the impl call.
408
self.cleanup = []
409
410
411
class FormatCounterFormatter(string.Formatter):
412
"""
413
This counts how many instances of each formatter
414
"replacement string" appear in the format string.
415
416
e.g. after evaluating "string {a}, {b}, {c}, {a}"
417
the counts dict would now look like
418
{'a': 2, 'b': 1, 'c': 1}
419
"""
420
def __init__(self):
421
self.counts = collections.Counter()
422
423
def get_value(self, key, args, kwargs):
424
self.counts[key] += 1
425
return ''
426
427
class Language(metaclass=abc.ABCMeta):
428
429
start_line = ""
430
body_prefix = ""
431
stop_line = ""
432
checksum_line = ""
433
434
def __init__(self, filename):
435
pass
436
437
@abc.abstractmethod
438
def render(self, clinic, signatures):
439
pass
440
441
def parse_line(self, line):
442
pass
443
444
def validate(self):
445
def assert_only_one(attr, *additional_fields):
446
"""
447
Ensures that the string found at getattr(self, attr)
448
contains exactly one formatter replacement string for
449
each valid field. The list of valid fields is
450
['dsl_name'] extended by additional_fields.
451
452
e.g.
453
self.fmt = "{dsl_name} {a} {b}"
454
455
# this passes
456
self.assert_only_one('fmt', 'a', 'b')
457
458
# this fails, the format string has a {b} in it
459
self.assert_only_one('fmt', 'a')
460
461
# this fails, the format string doesn't have a {c} in it
462
self.assert_only_one('fmt', 'a', 'b', 'c')
463
464
# this fails, the format string has two {a}s in it,
465
# it must contain exactly one
466
self.fmt2 = '{dsl_name} {a} {a}'
467
self.assert_only_one('fmt2', 'a')
468
469
"""
470
fields = ['dsl_name']
471
fields.extend(additional_fields)
472
line = getattr(self, attr)
473
fcf = FormatCounterFormatter()
474
fcf.format(line)
475
def local_fail(should_be_there_but_isnt):
476
if should_be_there_but_isnt:
477
fail("{} {} must contain {{{}}} exactly once!".format(
478
self.__class__.__name__, attr, name))
479
else:
480
fail("{} {} must not contain {{{}}}!".format(
481
self.__class__.__name__, attr, name))
482
483
for name, count in fcf.counts.items():
484
if name in fields:
485
if count > 1:
486
local_fail(True)
487
else:
488
local_fail(False)
489
for name in fields:
490
if fcf.counts.get(name) != 1:
491
local_fail(True)
492
493
assert_only_one('start_line')
494
assert_only_one('stop_line')
495
496
field = "arguments" if "{arguments}" in self.checksum_line else "checksum"
497
assert_only_one('checksum_line', field)
498
499
500
501
class PythonLanguage(Language):
502
503
language = 'Python'
504
start_line = "#/*[{dsl_name} input]"
505
body_prefix = "#"
506
stop_line = "#[{dsl_name} start generated code]*/"
507
checksum_line = "#/*[{dsl_name} end generated code: {arguments}]*/"
508
509
510
def permute_left_option_groups(l):
511
"""
512
Given [1, 2, 3], should yield:
513
()
514
(3,)
515
(2, 3)
516
(1, 2, 3)
517
"""
518
yield tuple()
519
accumulator = []
520
for group in reversed(l):
521
accumulator = list(group) + accumulator
522
yield tuple(accumulator)
523
524
525
def permute_right_option_groups(l):
526
"""
527
Given [1, 2, 3], should yield:
528
()
529
(1,)
530
(1, 2)
531
(1, 2, 3)
532
"""
533
yield tuple()
534
accumulator = []
535
for group in l:
536
accumulator.extend(group)
537
yield tuple(accumulator)
538
539
540
def permute_optional_groups(left, required, right):
541
"""
542
Generator function that computes the set of acceptable
543
argument lists for the provided iterables of
544
argument groups. (Actually it generates a tuple of tuples.)
545
546
Algorithm: prefer left options over right options.
547
548
If required is empty, left must also be empty.
549
"""
550
required = tuple(required)
551
if not required:
552
if left:
553
raise ValueError("required is empty but left is not")
554
555
accumulator = []
556
counts = set()
557
for r in permute_right_option_groups(right):
558
for l in permute_left_option_groups(left):
559
t = l + required + r
560
if len(t) in counts:
561
continue
562
counts.add(len(t))
563
accumulator.append(t)
564
565
accumulator.sort(key=len)
566
return tuple(accumulator)
567
568
569
def strip_leading_and_trailing_blank_lines(s):
570
lines = s.rstrip().split('\n')
571
while lines:
572
line = lines[0]
573
if line.strip():
574
break
575
del lines[0]
576
return '\n'.join(lines)
577
578
@functools.lru_cache()
579
def normalize_snippet(s, *, indent=0):
580
"""
581
Reformats s:
582
* removes leading and trailing blank lines
583
* ensures that it does not end with a newline
584
* dedents so the first nonwhite character on any line is at column "indent"
585
"""
586
s = strip_leading_and_trailing_blank_lines(s)
587
s = textwrap.dedent(s)
588
if indent:
589
s = textwrap.indent(s, ' ' * indent)
590
return s
591
592
593
def declare_parser(f, *, hasformat=False):
594
"""
595
Generates the code template for a static local PyArg_Parser variable,
596
with an initializer. For core code (incl. builtin modules) the
597
kwtuple field is also statically initialized. Otherwise
598
it is initialized at runtime.
599
"""
600
if hasformat:
601
fname = ''
602
format_ = '.format = "{format_units}:{name}",'
603
else:
604
fname = '.fname = "{name}",'
605
format_ = ''
606
607
num_keywords = len([
608
p for p in f.parameters.values()
609
if not p.is_positional_only() and not p.is_vararg()
610
])
611
if num_keywords == 0:
612
declarations = """
613
#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
614
# define KWTUPLE (PyObject *)&_Py_SINGLETON(tuple_empty)
615
#else
616
# define KWTUPLE NULL
617
#endif
618
"""
619
else:
620
declarations = """
621
#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
622
623
#define NUM_KEYWORDS %d
624
static struct {{
625
PyGC_Head _this_is_not_used;
626
PyObject_VAR_HEAD
627
PyObject *ob_item[NUM_KEYWORDS];
628
}} _kwtuple = {{
629
.ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS)
630
.ob_item = {{ {keywords_py} }},
631
}};
632
#undef NUM_KEYWORDS
633
#define KWTUPLE (&_kwtuple.ob_base.ob_base)
634
635
#else // !Py_BUILD_CORE
636
# define KWTUPLE NULL
637
#endif // !Py_BUILD_CORE
638
""" % num_keywords
639
640
declarations += """
641
static const char * const _keywords[] = {{{keywords_c} NULL}};
642
static _PyArg_Parser _parser = {{
643
.keywords = _keywords,
644
%s
645
.kwtuple = KWTUPLE,
646
}};
647
#undef KWTUPLE
648
""" % (format_ or fname)
649
return normalize_snippet(declarations)
650
651
652
def wrap_declarations(text, length=78):
653
"""
654
A simple-minded text wrapper for C function declarations.
655
656
It views a declaration line as looking like this:
657
xxxxxxxx(xxxxxxxxx,xxxxxxxxx)
658
If called with length=30, it would wrap that line into
659
xxxxxxxx(xxxxxxxxx,
660
xxxxxxxxx)
661
(If the declaration has zero or one parameters, this
662
function won't wrap it.)
663
664
If this doesn't work properly, it's probably better to
665
start from scratch with a more sophisticated algorithm,
666
rather than try and improve/debug this dumb little function.
667
"""
668
lines = []
669
for line in text.split('\n'):
670
prefix, _, after_l_paren = line.partition('(')
671
if not after_l_paren:
672
lines.append(line)
673
continue
674
parameters, _, after_r_paren = after_l_paren.partition(')')
675
if not _:
676
lines.append(line)
677
continue
678
if ',' not in parameters:
679
lines.append(line)
680
continue
681
parameters = [x.strip() + ", " for x in parameters.split(',')]
682
prefix += "("
683
if len(prefix) < length:
684
spaces = " " * len(prefix)
685
else:
686
spaces = " " * 4
687
688
while parameters:
689
line = prefix
690
first = True
691
while parameters:
692
if (not first and
693
(len(line) + len(parameters[0]) > length)):
694
break
695
line += parameters.pop(0)
696
first = False
697
if not parameters:
698
line = line.rstrip(", ") + ")" + after_r_paren
699
lines.append(line.rstrip())
700
prefix = spaces
701
return "\n".join(lines)
702
703
704
class CLanguage(Language):
705
706
body_prefix = "#"
707
language = 'C'
708
start_line = "/*[{dsl_name} input]"
709
body_prefix = ""
710
stop_line = "[{dsl_name} start generated code]*/"
711
checksum_line = "/*[{dsl_name} end generated code: {arguments}]*/"
712
713
def __init__(self, filename):
714
super().__init__(filename)
715
self.cpp = cpp.Monitor(filename)
716
self.cpp.fail = fail
717
718
def parse_line(self, line):
719
self.cpp.writeline(line)
720
721
def render(self, clinic, signatures):
722
function = None
723
for o in signatures:
724
if isinstance(o, Function):
725
if function:
726
fail("You may specify at most one function per block.\nFound a block containing at least two:\n\t" + repr(function) + " and " + repr(o))
727
function = o
728
return self.render_function(clinic, function)
729
730
def docstring_for_c_string(self, f):
731
if re.search(r'[^\x00-\x7F]', f.docstring):
732
warn("Non-ascii character appear in docstring.")
733
734
text, add, output = _text_accumulator()
735
# turn docstring into a properly quoted C string
736
for line in f.docstring.split('\n'):
737
add('"')
738
add(quoted_for_c_string(line))
739
add('\\n"\n')
740
741
if text[-2] == sig_end_marker:
742
# If we only have a signature, add the blank line that the
743
# __text_signature__ getter expects to be there.
744
add('"\\n"')
745
else:
746
text.pop()
747
add('"')
748
return ''.join(text)
749
750
def output_templates(self, f):
751
parameters = list(f.parameters.values())
752
assert parameters
753
assert isinstance(parameters[0].converter, self_converter)
754
del parameters[0]
755
requires_defining_class = False
756
if parameters and isinstance(parameters[0].converter, defining_class_converter):
757
requires_defining_class = True
758
del parameters[0]
759
converters = [p.converter for p in parameters]
760
761
has_option_groups = parameters and (parameters[0].group or parameters[-1].group)
762
default_return_converter = (not f.return_converter or
763
f.return_converter.type == 'PyObject *')
764
765
new_or_init = f.kind in (METHOD_NEW, METHOD_INIT)
766
767
vararg = NO_VARARG
768
pos_only = min_pos = max_pos = min_kw_only = pseudo_args = 0
769
for i, p in enumerate(parameters, 1):
770
if p.is_keyword_only():
771
assert not p.is_positional_only()
772
if not p.is_optional():
773
min_kw_only = i - max_pos
774
elif p.is_vararg():
775
if vararg != NO_VARARG:
776
fail("Too many var args")
777
pseudo_args += 1
778
vararg = i - 1
779
else:
780
if vararg == NO_VARARG:
781
max_pos = i
782
if p.is_positional_only():
783
pos_only = i
784
if not p.is_optional():
785
min_pos = i
786
787
meth_o = (len(parameters) == 1 and
788
parameters[0].is_positional_only() and
789
not converters[0].is_optional() and
790
not requires_defining_class and
791
not new_or_init)
792
793
# we have to set these things before we're done:
794
#
795
# docstring_prototype
796
# docstring_definition
797
# impl_prototype
798
# methoddef_define
799
# parser_prototype
800
# parser_definition
801
# impl_definition
802
# cpp_if
803
# cpp_endif
804
# methoddef_ifndef
805
806
return_value_declaration = "PyObject *return_value = NULL;"
807
808
methoddef_define = normalize_snippet("""
809
#define {methoddef_name} \\
810
{{"{name}", {methoddef_cast}{c_basename}{methoddef_cast_end}, {methoddef_flags}, {c_basename}__doc__}},
811
""")
812
if new_or_init and not f.docstring:
813
docstring_prototype = docstring_definition = ''
814
else:
815
docstring_prototype = normalize_snippet("""
816
PyDoc_VAR({c_basename}__doc__);
817
""")
818
docstring_definition = normalize_snippet("""
819
PyDoc_STRVAR({c_basename}__doc__,
820
{docstring});
821
""")
822
impl_definition = normalize_snippet("""
823
static {impl_return_type}
824
{c_basename}_impl({impl_parameters})
825
""")
826
impl_prototype = parser_prototype = parser_definition = None
827
828
parser_prototype_keyword = normalize_snippet("""
829
static PyObject *
830
{c_basename}({self_type}{self_name}, PyObject *args, PyObject *kwargs)
831
""")
832
833
parser_prototype_varargs = normalize_snippet("""
834
static PyObject *
835
{c_basename}({self_type}{self_name}, PyObject *args)
836
""")
837
838
parser_prototype_fastcall = normalize_snippet("""
839
static PyObject *
840
{c_basename}({self_type}{self_name}, PyObject *const *args, Py_ssize_t nargs)
841
""")
842
843
parser_prototype_fastcall_keywords = normalize_snippet("""
844
static PyObject *
845
{c_basename}({self_type}{self_name}, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
846
""")
847
848
parser_prototype_def_class = normalize_snippet("""
849
static PyObject *
850
{c_basename}({self_type}{self_name}, PyTypeObject *{defining_class_name}, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
851
""")
852
853
# parser_body_fields remembers the fields passed in to the
854
# previous call to parser_body. this is used for an awful hack.
855
parser_body_fields = ()
856
def parser_body(prototype, *fields, declarations=''):
857
nonlocal parser_body_fields
858
add, output = text_accumulator()
859
add(prototype)
860
parser_body_fields = fields
861
862
fields = list(fields)
863
fields.insert(0, normalize_snippet("""
864
{{
865
{return_value_declaration}
866
{parser_declarations}
867
{declarations}
868
{initializers}
869
""") + "\n")
870
# just imagine--your code is here in the middle
871
fields.append(normalize_snippet("""
872
{modifications}
873
{return_value} = {c_basename}_impl({impl_arguments});
874
{return_conversion}
875
{post_parsing}
876
877
{exit_label}
878
{cleanup}
879
return return_value;
880
}}
881
"""))
882
for field in fields:
883
add('\n')
884
add(field)
885
return linear_format(output(), parser_declarations=declarations)
886
887
if not parameters:
888
if not requires_defining_class:
889
# no parameters, METH_NOARGS
890
flags = "METH_NOARGS"
891
892
parser_prototype = normalize_snippet("""
893
static PyObject *
894
{c_basename}({self_type}{self_name}, PyObject *Py_UNUSED(ignored))
895
""")
896
parser_code = []
897
898
else:
899
assert not new_or_init
900
901
flags = "METH_METHOD|METH_FASTCALL|METH_KEYWORDS"
902
903
parser_prototype = parser_prototype_def_class
904
return_error = ('return NULL;' if default_return_converter
905
else 'goto exit;')
906
parser_code = [normalize_snippet("""
907
if (nargs) {{
908
PyErr_SetString(PyExc_TypeError, "{name}() takes no arguments");
909
%s
910
}}
911
""" % return_error, indent=4)]
912
913
if default_return_converter:
914
parser_definition = '\n'.join([
915
parser_prototype,
916
'{{',
917
*parser_code,
918
' return {c_basename}_impl({impl_arguments});',
919
'}}'])
920
else:
921
parser_definition = parser_body(parser_prototype, *parser_code)
922
923
elif meth_o:
924
flags = "METH_O"
925
926
if (isinstance(converters[0], object_converter) and
927
converters[0].format_unit == 'O'):
928
meth_o_prototype = normalize_snippet("""
929
static PyObject *
930
{c_basename}({impl_parameters})
931
""")
932
933
if default_return_converter:
934
# maps perfectly to METH_O, doesn't need a return converter.
935
# so we skip making a parse function
936
# and call directly into the impl function.
937
impl_prototype = parser_prototype = parser_definition = ''
938
impl_definition = meth_o_prototype
939
else:
940
# SLIGHT HACK
941
# use impl_parameters for the parser here!
942
parser_prototype = meth_o_prototype
943
parser_definition = parser_body(parser_prototype)
944
945
else:
946
argname = 'arg'
947
if parameters[0].name == argname:
948
argname += '_'
949
parser_prototype = normalize_snippet("""
950
static PyObject *
951
{c_basename}({self_type}{self_name}, PyObject *%s)
952
""" % argname)
953
954
displayname = parameters[0].get_displayname(0)
955
parsearg = converters[0].parse_arg(argname, displayname)
956
if parsearg is None:
957
parsearg = """
958
if (!PyArg_Parse(%s, "{format_units}:{name}", {parse_arguments})) {{
959
goto exit;
960
}}
961
""" % argname
962
parser_definition = parser_body(parser_prototype,
963
normalize_snippet(parsearg, indent=4))
964
965
elif has_option_groups:
966
# positional parameters with option groups
967
# (we have to generate lots of PyArg_ParseTuple calls
968
# in a big switch statement)
969
970
flags = "METH_VARARGS"
971
parser_prototype = parser_prototype_varargs
972
973
parser_definition = parser_body(parser_prototype, ' {option_group_parsing}')
974
975
elif not requires_defining_class and pos_only == len(parameters) - pseudo_args:
976
if not new_or_init:
977
# positional-only, but no option groups
978
# we only need one call to _PyArg_ParseStack
979
980
flags = "METH_FASTCALL"
981
parser_prototype = parser_prototype_fastcall
982
nargs = 'nargs'
983
argname_fmt = 'args[%d]'
984
else:
985
# positional-only, but no option groups
986
# we only need one call to PyArg_ParseTuple
987
988
flags = "METH_VARARGS"
989
parser_prototype = parser_prototype_varargs
990
nargs = 'PyTuple_GET_SIZE(args)'
991
argname_fmt = 'PyTuple_GET_ITEM(args, %d)'
992
993
994
left_args = f"{nargs} - {max_pos}"
995
max_args = NO_VARARG if (vararg != NO_VARARG) else max_pos
996
parser_code = [normalize_snippet("""
997
if (!_PyArg_CheckPositional("{name}", %s, %d, %s)) {{
998
goto exit;
999
}}
1000
""" % (nargs, min_pos, max_args), indent=4)]
1001
1002
has_optional = False
1003
for i, p in enumerate(parameters):
1004
displayname = p.get_displayname(i+1)
1005
argname = argname_fmt % i
1006
1007
if p.is_vararg():
1008
if not new_or_init:
1009
parser_code.append(normalize_snippet("""
1010
%s = PyTuple_New(%s);
1011
if (!%s) {{
1012
goto exit;
1013
}}
1014
for (Py_ssize_t i = 0; i < %s; ++i) {{
1015
PyTuple_SET_ITEM(%s, i, Py_NewRef(args[%d + i]));
1016
}}
1017
""" % (
1018
p.converter.parser_name,
1019
left_args,
1020
p.converter.parser_name,
1021
left_args,
1022
p.converter.parser_name,
1023
max_pos
1024
), indent=4))
1025
else:
1026
parser_code.append(normalize_snippet("""
1027
%s = PyTuple_GetSlice(%d, -1);
1028
""" % (
1029
p.converter.parser_name,
1030
max_pos
1031
), indent=4))
1032
continue
1033
1034
parsearg = p.converter.parse_arg(argname, displayname)
1035
if parsearg is None:
1036
#print('Cannot convert %s %r for %s' % (p.converter.__class__.__name__, p.converter.format_unit, p.converter.name), file=sys.stderr)
1037
parser_code = None
1038
break
1039
if has_optional or p.is_optional():
1040
has_optional = True
1041
parser_code.append(normalize_snippet("""
1042
if (%s < %d) {{
1043
goto skip_optional;
1044
}}
1045
""", indent=4) % (nargs, i + 1))
1046
parser_code.append(normalize_snippet(parsearg, indent=4))
1047
1048
if parser_code is not None:
1049
if has_optional:
1050
parser_code.append("skip_optional:")
1051
else:
1052
if not new_or_init:
1053
parser_code = [normalize_snippet("""
1054
if (!_PyArg_ParseStack(args, nargs, "{format_units}:{name}",
1055
{parse_arguments})) {{
1056
goto exit;
1057
}}
1058
""", indent=4)]
1059
else:
1060
parser_code = [normalize_snippet("""
1061
if (!PyArg_ParseTuple(args, "{format_units}:{name}",
1062
{parse_arguments})) {{
1063
goto exit;
1064
}}
1065
""", indent=4)]
1066
parser_definition = parser_body(parser_prototype, *parser_code)
1067
1068
else:
1069
has_optional_kw = (max(pos_only, min_pos) + min_kw_only < len(converters) - int(vararg != NO_VARARG))
1070
if vararg == NO_VARARG:
1071
args_declaration = "_PyArg_UnpackKeywords", "%s, %s, %s" % (
1072
min_pos,
1073
max_pos,
1074
min_kw_only
1075
)
1076
nargs = "nargs"
1077
else:
1078
args_declaration = "_PyArg_UnpackKeywordsWithVararg", "%s, %s, %s, %s" % (
1079
min_pos,
1080
max_pos,
1081
min_kw_only,
1082
vararg
1083
)
1084
nargs = f"Py_MIN(nargs, {max_pos})" if max_pos else "0"
1085
if not new_or_init:
1086
flags = "METH_FASTCALL|METH_KEYWORDS"
1087
parser_prototype = parser_prototype_fastcall_keywords
1088
argname_fmt = 'args[%d]'
1089
declarations = declare_parser(f)
1090
declarations += "\nPyObject *argsbuf[%s];" % len(converters)
1091
if has_optional_kw:
1092
declarations += "\nPy_ssize_t noptargs = %s + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - %d;" % (nargs, min_pos + min_kw_only)
1093
parser_code = [normalize_snippet("""
1094
args = %s(args, nargs, NULL, kwnames, &_parser, %s, argsbuf);
1095
if (!args) {{
1096
goto exit;
1097
}}
1098
""" % args_declaration, indent=4)]
1099
else:
1100
# positional-or-keyword arguments
1101
flags = "METH_VARARGS|METH_KEYWORDS"
1102
parser_prototype = parser_prototype_keyword
1103
argname_fmt = 'fastargs[%d]'
1104
declarations = declare_parser(f)
1105
declarations += "\nPyObject *argsbuf[%s];" % len(converters)
1106
declarations += "\nPyObject * const *fastargs;"
1107
declarations += "\nPy_ssize_t nargs = PyTuple_GET_SIZE(args);"
1108
if has_optional_kw:
1109
declarations += "\nPy_ssize_t noptargs = %s + (kwargs ? PyDict_GET_SIZE(kwargs) : 0) - %d;" % (nargs, min_pos + min_kw_only)
1110
parser_code = [normalize_snippet("""
1111
fastargs = %s(_PyTuple_CAST(args)->ob_item, nargs, kwargs, NULL, &_parser, %s, argsbuf);
1112
if (!fastargs) {{
1113
goto exit;
1114
}}
1115
""" % args_declaration, indent=4)]
1116
1117
if requires_defining_class:
1118
flags = 'METH_METHOD|' + flags
1119
parser_prototype = parser_prototype_def_class
1120
1121
add_label = None
1122
for i, p in enumerate(parameters):
1123
if isinstance(p.converter, defining_class_converter):
1124
raise ValueError("defining_class should be the first "
1125
"parameter (after self)")
1126
displayname = p.get_displayname(i+1)
1127
parsearg = p.converter.parse_arg(argname_fmt % i, displayname)
1128
if parsearg is None:
1129
#print('Cannot convert %s %r for %s' % (p.converter.__class__.__name__, p.converter.format_unit, p.converter.name), file=sys.stderr)
1130
parser_code = None
1131
break
1132
if add_label and (i == pos_only or i == max_pos):
1133
parser_code.append("%s:" % add_label)
1134
add_label = None
1135
if not p.is_optional():
1136
parser_code.append(normalize_snippet(parsearg, indent=4))
1137
elif i < pos_only:
1138
add_label = 'skip_optional_posonly'
1139
parser_code.append(normalize_snippet("""
1140
if (nargs < %d) {{
1141
goto %s;
1142
}}
1143
""" % (i + 1, add_label), indent=4))
1144
if has_optional_kw:
1145
parser_code.append(normalize_snippet("""
1146
noptargs--;
1147
""", indent=4))
1148
parser_code.append(normalize_snippet(parsearg, indent=4))
1149
else:
1150
if i < max_pos:
1151
label = 'skip_optional_pos'
1152
first_opt = max(min_pos, pos_only)
1153
else:
1154
label = 'skip_optional_kwonly'
1155
first_opt = max_pos + min_kw_only
1156
if vararg != NO_VARARG:
1157
first_opt += 1
1158
if i == first_opt:
1159
add_label = label
1160
parser_code.append(normalize_snippet("""
1161
if (!noptargs) {{
1162
goto %s;
1163
}}
1164
""" % add_label, indent=4))
1165
if i + 1 == len(parameters):
1166
parser_code.append(normalize_snippet(parsearg, indent=4))
1167
else:
1168
add_label = label
1169
parser_code.append(normalize_snippet("""
1170
if (%s) {{
1171
""" % (argname_fmt % i), indent=4))
1172
parser_code.append(normalize_snippet(parsearg, indent=8))
1173
parser_code.append(normalize_snippet("""
1174
if (!--noptargs) {{
1175
goto %s;
1176
}}
1177
}}
1178
""" % add_label, indent=4))
1179
1180
if parser_code is not None:
1181
if add_label:
1182
parser_code.append("%s:" % add_label)
1183
else:
1184
declarations = declare_parser(f, hasformat=True)
1185
if not new_or_init:
1186
parser_code = [normalize_snippet("""
1187
if (!_PyArg_ParseStackAndKeywords(args, nargs, kwnames, &_parser{parse_arguments_comma}
1188
{parse_arguments})) {{
1189
goto exit;
1190
}}
1191
""", indent=4)]
1192
else:
1193
parser_code = [normalize_snippet("""
1194
if (!_PyArg_ParseTupleAndKeywordsFast(args, kwargs, &_parser,
1195
{parse_arguments})) {{
1196
goto exit;
1197
}}
1198
""", indent=4)]
1199
parser_definition = parser_body(parser_prototype, *parser_code,
1200
declarations=declarations)
1201
1202
1203
if new_or_init:
1204
methoddef_define = ''
1205
1206
if f.kind == METHOD_NEW:
1207
parser_prototype = parser_prototype_keyword
1208
else:
1209
return_value_declaration = "int return_value = -1;"
1210
parser_prototype = normalize_snippet("""
1211
static int
1212
{c_basename}({self_type}{self_name}, PyObject *args, PyObject *kwargs)
1213
""")
1214
1215
fields = list(parser_body_fields)
1216
parses_positional = 'METH_NOARGS' not in flags
1217
parses_keywords = 'METH_KEYWORDS' in flags
1218
if parses_keywords:
1219
assert parses_positional
1220
1221
if requires_defining_class:
1222
raise ValueError("Slot methods cannot access their defining class.")
1223
1224
if not parses_keywords:
1225
declarations = '{base_type_ptr}'
1226
fields.insert(0, normalize_snippet("""
1227
if ({self_type_check}!_PyArg_NoKeywords("{name}", kwargs)) {{
1228
goto exit;
1229
}}
1230
""", indent=4))
1231
if not parses_positional:
1232
fields.insert(0, normalize_snippet("""
1233
if ({self_type_check}!_PyArg_NoPositional("{name}", args)) {{
1234
goto exit;
1235
}}
1236
""", indent=4))
1237
1238
parser_definition = parser_body(parser_prototype, *fields,
1239
declarations=declarations)
1240
1241
1242
if flags in ('METH_NOARGS', 'METH_O', 'METH_VARARGS'):
1243
methoddef_cast = "(PyCFunction)"
1244
methoddef_cast_end = ""
1245
else:
1246
methoddef_cast = "_PyCFunction_CAST("
1247
methoddef_cast_end = ")"
1248
1249
if f.methoddef_flags:
1250
flags += '|' + f.methoddef_flags
1251
1252
methoddef_define = methoddef_define.replace('{methoddef_flags}', flags)
1253
methoddef_define = methoddef_define.replace('{methoddef_cast}', methoddef_cast)
1254
methoddef_define = methoddef_define.replace('{methoddef_cast_end}', methoddef_cast_end)
1255
1256
methoddef_ifndef = ''
1257
conditional = self.cpp.condition()
1258
if not conditional:
1259
cpp_if = cpp_endif = ''
1260
else:
1261
cpp_if = "#if " + conditional
1262
cpp_endif = "#endif /* " + conditional + " */"
1263
1264
if methoddef_define and f.full_name not in clinic.ifndef_symbols:
1265
clinic.ifndef_symbols.add(f.full_name)
1266
methoddef_ifndef = normalize_snippet("""
1267
#ifndef {methoddef_name}
1268
#define {methoddef_name}
1269
#endif /* !defined({methoddef_name}) */
1270
""")
1271
1272
1273
# add ';' to the end of parser_prototype and impl_prototype
1274
# (they mustn't be None, but they could be an empty string.)
1275
assert parser_prototype is not None
1276
if parser_prototype:
1277
assert not parser_prototype.endswith(';')
1278
parser_prototype += ';'
1279
1280
if impl_prototype is None:
1281
impl_prototype = impl_definition
1282
if impl_prototype:
1283
impl_prototype += ";"
1284
1285
parser_definition = parser_definition.replace("{return_value_declaration}", return_value_declaration)
1286
1287
d = {
1288
"docstring_prototype" : docstring_prototype,
1289
"docstring_definition" : docstring_definition,
1290
"impl_prototype" : impl_prototype,
1291
"methoddef_define" : methoddef_define,
1292
"parser_prototype" : parser_prototype,
1293
"parser_definition" : parser_definition,
1294
"impl_definition" : impl_definition,
1295
"cpp_if" : cpp_if,
1296
"cpp_endif" : cpp_endif,
1297
"methoddef_ifndef" : methoddef_ifndef,
1298
}
1299
1300
# make sure we didn't forget to assign something,
1301
# and wrap each non-empty value in \n's
1302
d2 = {}
1303
for name, value in d.items():
1304
assert value is not None, "got a None value for template " + repr(name)
1305
if value:
1306
value = '\n' + value + '\n'
1307
d2[name] = value
1308
return d2
1309
1310
@staticmethod
1311
def group_to_variable_name(group):
1312
adjective = "left_" if group < 0 else "right_"
1313
return "group_" + adjective + str(abs(group))
1314
1315
def render_option_group_parsing(self, f, template_dict):
1316
# positional only, grouped, optional arguments!
1317
# can be optional on the left or right.
1318
# here's an example:
1319
#
1320
# [ [ [ A1 A2 ] B1 B2 B3 ] C1 C2 ] D1 D2 D3 [ E1 E2 E3 [ F1 F2 F3 ] ]
1321
#
1322
# Here group D are required, and all other groups are optional.
1323
# (Group D's "group" is actually None.)
1324
# We can figure out which sets of arguments we have based on
1325
# how many arguments are in the tuple.
1326
#
1327
# Note that you need to count up on both sides. For example,
1328
# you could have groups C+D, or C+D+E, or C+D+E+F.
1329
#
1330
# What if the number of arguments leads us to an ambiguous result?
1331
# Clinic prefers groups on the left. So in the above example,
1332
# five arguments would map to B+C, not C+D.
1333
1334
add, output = text_accumulator()
1335
parameters = list(f.parameters.values())
1336
if isinstance(parameters[0].converter, self_converter):
1337
del parameters[0]
1338
1339
group = None
1340
left = []
1341
right = []
1342
required = []
1343
last = unspecified
1344
1345
for p in parameters:
1346
group_id = p.group
1347
if group_id != last:
1348
last = group_id
1349
group = []
1350
if group_id < 0:
1351
left.append(group)
1352
elif group_id == 0:
1353
group = required
1354
else:
1355
right.append(group)
1356
group.append(p)
1357
1358
count_min = sys.maxsize
1359
count_max = -1
1360
1361
add("switch (PyTuple_GET_SIZE(args)) {\n")
1362
for subset in permute_optional_groups(left, required, right):
1363
count = len(subset)
1364
count_min = min(count_min, count)
1365
count_max = max(count_max, count)
1366
1367
if count == 0:
1368
add(""" case 0:
1369
break;
1370
""")
1371
continue
1372
1373
group_ids = {p.group for p in subset} # eliminate duplicates
1374
d = {}
1375
d['count'] = count
1376
d['name'] = f.name
1377
d['format_units'] = "".join(p.converter.format_unit for p in subset)
1378
1379
parse_arguments = []
1380
for p in subset:
1381
p.converter.parse_argument(parse_arguments)
1382
d['parse_arguments'] = ", ".join(parse_arguments)
1383
1384
group_ids.discard(0)
1385
lines = [self.group_to_variable_name(g) + " = 1;" for g in group_ids]
1386
lines = "\n".join(lines)
1387
1388
s = """\
1389
case {count}:
1390
if (!PyArg_ParseTuple(args, "{format_units}:{name}", {parse_arguments})) {{
1391
goto exit;
1392
}}
1393
{group_booleans}
1394
break;
1395
"""
1396
s = linear_format(s, group_booleans=lines)
1397
s = s.format_map(d)
1398
add(s)
1399
1400
add(" default:\n")
1401
s = ' PyErr_SetString(PyExc_TypeError, "{} requires {} to {} arguments");\n'
1402
add(s.format(f.full_name, count_min, count_max))
1403
add(' goto exit;\n')
1404
add("}")
1405
template_dict['option_group_parsing'] = format_escape(output())
1406
1407
def render_function(self, clinic, f):
1408
if not f:
1409
return ""
1410
1411
add, output = text_accumulator()
1412
data = CRenderData()
1413
1414
assert f.parameters, "We should always have a 'self' at this point!"
1415
parameters = f.render_parameters
1416
converters = [p.converter for p in parameters]
1417
1418
templates = self.output_templates(f)
1419
1420
f_self = parameters[0]
1421
selfless = parameters[1:]
1422
assert isinstance(f_self.converter, self_converter), "No self parameter in " + repr(f.full_name) + "!"
1423
1424
last_group = 0
1425
first_optional = len(selfless)
1426
positional = selfless and selfless[-1].is_positional_only()
1427
new_or_init = f.kind in (METHOD_NEW, METHOD_INIT)
1428
has_option_groups = False
1429
1430
# offset i by -1 because first_optional needs to ignore self
1431
for i, p in enumerate(parameters, -1):
1432
c = p.converter
1433
1434
if (i != -1) and (p.default is not unspecified):
1435
first_optional = min(first_optional, i)
1436
1437
if p.is_vararg():
1438
data.cleanup.append(f"Py_XDECREF({c.parser_name});")
1439
1440
# insert group variable
1441
group = p.group
1442
if last_group != group:
1443
last_group = group
1444
if group:
1445
group_name = self.group_to_variable_name(group)
1446
data.impl_arguments.append(group_name)
1447
data.declarations.append("int " + group_name + " = 0;")
1448
data.impl_parameters.append("int " + group_name)
1449
has_option_groups = True
1450
1451
c.render(p, data)
1452
1453
if has_option_groups and (not positional):
1454
fail("You cannot use optional groups ('[' and ']')\nunless all parameters are positional-only ('/').")
1455
1456
# HACK
1457
# when we're METH_O, but have a custom return converter,
1458
# we use "impl_parameters" for the parsing function
1459
# because that works better. but that means we must
1460
# suppress actually declaring the impl's parameters
1461
# as variables in the parsing function. but since it's
1462
# METH_O, we have exactly one anyway, so we know exactly
1463
# where it is.
1464
if ("METH_O" in templates['methoddef_define'] and
1465
'{impl_parameters}' in templates['parser_prototype']):
1466
data.declarations.pop(0)
1467
1468
template_dict = {}
1469
1470
full_name = f.full_name
1471
template_dict['full_name'] = full_name
1472
1473
if new_or_init:
1474
name = f.cls.name
1475
else:
1476
name = f.name
1477
1478
template_dict['name'] = name
1479
1480
if f.c_basename:
1481
c_basename = f.c_basename
1482
else:
1483
fields = full_name.split(".")
1484
if fields[-1] == '__new__':
1485
fields.pop()
1486
c_basename = "_".join(fields)
1487
1488
template_dict['c_basename'] = c_basename
1489
1490
template_dict['methoddef_name'] = c_basename.upper() + "_METHODDEF"
1491
1492
template_dict['docstring'] = self.docstring_for_c_string(f)
1493
1494
template_dict['self_name'] = template_dict['self_type'] = template_dict['self_type_check'] = ''
1495
for converter in converters:
1496
converter.set_template_dict(template_dict)
1497
1498
f.return_converter.render(f, data)
1499
template_dict['impl_return_type'] = f.return_converter.type
1500
1501
template_dict['declarations'] = format_escape("\n".join(data.declarations))
1502
template_dict['initializers'] = "\n\n".join(data.initializers)
1503
template_dict['modifications'] = '\n\n'.join(data.modifications)
1504
template_dict['keywords_c'] = ' '.join('"' + k + '",'
1505
for k in data.keywords)
1506
keywords = [k for k in data.keywords if k]
1507
template_dict['keywords_py'] = ' '.join('&_Py_ID(' + k + '),'
1508
for k in keywords)
1509
template_dict['format_units'] = ''.join(data.format_units)
1510
template_dict['parse_arguments'] = ', '.join(data.parse_arguments)
1511
if data.parse_arguments:
1512
template_dict['parse_arguments_comma'] = ',';
1513
else:
1514
template_dict['parse_arguments_comma'] = '';
1515
template_dict['impl_parameters'] = ", ".join(data.impl_parameters)
1516
template_dict['impl_arguments'] = ", ".join(data.impl_arguments)
1517
template_dict['return_conversion'] = format_escape("".join(data.return_conversion).rstrip())
1518
template_dict['post_parsing'] = format_escape("".join(data.post_parsing).rstrip())
1519
template_dict['cleanup'] = format_escape("".join(data.cleanup))
1520
template_dict['return_value'] = data.return_value
1521
1522
# used by unpack tuple code generator
1523
unpack_min = first_optional
1524
unpack_max = len(selfless)
1525
template_dict['unpack_min'] = str(unpack_min)
1526
template_dict['unpack_max'] = str(unpack_max)
1527
1528
if has_option_groups:
1529
self.render_option_group_parsing(f, template_dict)
1530
1531
# buffers, not destination
1532
for name, destination in clinic.destination_buffers.items():
1533
template = templates[name]
1534
if has_option_groups:
1535
template = linear_format(template,
1536
option_group_parsing=template_dict['option_group_parsing'])
1537
template = linear_format(template,
1538
declarations=template_dict['declarations'],
1539
return_conversion=template_dict['return_conversion'],
1540
initializers=template_dict['initializers'],
1541
modifications=template_dict['modifications'],
1542
post_parsing=template_dict['post_parsing'],
1543
cleanup=template_dict['cleanup'],
1544
)
1545
1546
# Only generate the "exit:" label
1547
# if we have any gotos
1548
need_exit_label = "goto exit;" in template
1549
template = linear_format(template,
1550
exit_label="exit:" if need_exit_label else ''
1551
)
1552
1553
s = template.format_map(template_dict)
1554
1555
# mild hack:
1556
# reflow long impl declarations
1557
if name in {"impl_prototype", "impl_definition"}:
1558
s = wrap_declarations(s)
1559
1560
if clinic.line_prefix:
1561
s = indent_all_lines(s, clinic.line_prefix)
1562
if clinic.line_suffix:
1563
s = suffix_all_lines(s, clinic.line_suffix)
1564
1565
destination.append(s)
1566
1567
return clinic.get_destination('block').dump()
1568
1569
1570
1571
1572
@contextlib.contextmanager
1573
def OverrideStdioWith(stdout):
1574
saved_stdout = sys.stdout
1575
sys.stdout = stdout
1576
try:
1577
yield
1578
finally:
1579
assert sys.stdout is stdout
1580
sys.stdout = saved_stdout
1581
1582
1583
def create_regex(before, after, word=True, whole_line=True):
1584
"""Create an re object for matching marker lines."""
1585
group_re = r"\w+" if word else ".+"
1586
pattern = r'{}({}){}'
1587
if whole_line:
1588
pattern = '^' + pattern + '$'
1589
pattern = pattern.format(re.escape(before), group_re, re.escape(after))
1590
return re.compile(pattern)
1591
1592
1593
class Block:
1594
r"""
1595
Represents a single block of text embedded in
1596
another file. If dsl_name is None, the block represents
1597
verbatim text, raw original text from the file, in
1598
which case "input" will be the only non-false member.
1599
If dsl_name is not None, the block represents a Clinic
1600
block.
1601
1602
input is always str, with embedded \n characters.
1603
input represents the original text from the file;
1604
if it's a Clinic block, it is the original text with
1605
the body_prefix and redundant leading whitespace removed.
1606
1607
dsl_name is either str or None. If str, it's the text
1608
found on the start line of the block between the square
1609
brackets.
1610
1611
signatures is either list or None. If it's a list,
1612
it may only contain clinic.Module, clinic.Class, and
1613
clinic.Function objects. At the moment it should
1614
contain at most one of each.
1615
1616
output is either str or None. If str, it's the output
1617
from this block, with embedded '\n' characters.
1618
1619
indent is either str or None. It's the leading whitespace
1620
that was found on every line of input. (If body_prefix is
1621
not empty, this is the indent *after* removing the
1622
body_prefix.)
1623
1624
preindent is either str or None. It's the whitespace that
1625
was found in front of every line of input *before* the
1626
"body_prefix" (see the Language object). If body_prefix
1627
is empty, preindent must always be empty too.
1628
1629
To illustrate indent and preindent: Assume that '_'
1630
represents whitespace. If the block processed was in a
1631
Python file, and looked like this:
1632
____#/*[python]
1633
____#__for a in range(20):
1634
____#____print(a)
1635
____#[python]*/
1636
"preindent" would be "____" and "indent" would be "__".
1637
1638
"""
1639
def __init__(self, input, dsl_name=None, signatures=None, output=None, indent='', preindent=''):
1640
assert isinstance(input, str)
1641
self.input = input
1642
self.dsl_name = dsl_name
1643
self.signatures = signatures or []
1644
self.output = output
1645
self.indent = indent
1646
self.preindent = preindent
1647
1648
def __repr__(self):
1649
dsl_name = self.dsl_name or "text"
1650
def summarize(s):
1651
s = repr(s)
1652
if len(s) > 30:
1653
return s[:26] + "..." + s[0]
1654
return s
1655
return "".join((
1656
"<Block ", dsl_name, " input=", summarize(self.input), " output=", summarize(self.output), ">"))
1657
1658
1659
class BlockParser:
1660
"""
1661
Block-oriented parser for Argument Clinic.
1662
Iterator, yields Block objects.
1663
"""
1664
1665
def __init__(self, input, language, *, verify=True):
1666
"""
1667
"input" should be a str object
1668
with embedded \n characters.
1669
1670
"language" should be a Language object.
1671
"""
1672
language.validate()
1673
1674
self.input = collections.deque(reversed(input.splitlines(keepends=True)))
1675
self.block_start_line_number = self.line_number = 0
1676
1677
self.language = language
1678
before, _, after = language.start_line.partition('{dsl_name}')
1679
assert _ == '{dsl_name}'
1680
self.find_start_re = create_regex(before, after, whole_line=False)
1681
self.start_re = create_regex(before, after)
1682
self.verify = verify
1683
self.last_checksum_re = None
1684
self.last_dsl_name = None
1685
self.dsl_name = None
1686
self.first_block = True
1687
1688
def __iter__(self):
1689
return self
1690
1691
def __next__(self):
1692
while True:
1693
if not self.input:
1694
raise StopIteration
1695
1696
if self.dsl_name:
1697
return_value = self.parse_clinic_block(self.dsl_name)
1698
self.dsl_name = None
1699
self.first_block = False
1700
return return_value
1701
block = self.parse_verbatim_block()
1702
if self.first_block and not block.input:
1703
continue
1704
self.first_block = False
1705
return block
1706
1707
1708
def is_start_line(self, line):
1709
match = self.start_re.match(line.lstrip())
1710
return match.group(1) if match else None
1711
1712
def _line(self, lookahead=False):
1713
self.line_number += 1
1714
line = self.input.pop()
1715
if not lookahead:
1716
self.language.parse_line(line)
1717
return line
1718
1719
def parse_verbatim_block(self):
1720
add, output = text_accumulator()
1721
self.block_start_line_number = self.line_number
1722
1723
while self.input:
1724
line = self._line()
1725
dsl_name = self.is_start_line(line)
1726
if dsl_name:
1727
self.dsl_name = dsl_name
1728
break
1729
add(line)
1730
1731
return Block(output())
1732
1733
def parse_clinic_block(self, dsl_name):
1734
input_add, input_output = text_accumulator()
1735
self.block_start_line_number = self.line_number + 1
1736
stop_line = self.language.stop_line.format(dsl_name=dsl_name)
1737
body_prefix = self.language.body_prefix.format(dsl_name=dsl_name)
1738
1739
def is_stop_line(line):
1740
# make sure to recognize stop line even if it
1741
# doesn't end with EOL (it could be the very end of the file)
1742
if line.startswith(stop_line):
1743
remainder = line.removeprefix(stop_line)
1744
if remainder and not remainder.isspace():
1745
fail(f"Garbage after stop line: {remainder!r}")
1746
return True
1747
else:
1748
# gh-92256: don't allow incorrectly formatted stop lines
1749
if line.lstrip().startswith(stop_line):
1750
fail(f"Whitespace is not allowed before the stop line: {line!r}")
1751
return False
1752
1753
# consume body of program
1754
while self.input:
1755
line = self._line()
1756
if is_stop_line(line) or self.is_start_line(line):
1757
break
1758
if body_prefix:
1759
line = line.lstrip()
1760
assert line.startswith(body_prefix)
1761
line = line.removeprefix(body_prefix)
1762
input_add(line)
1763
1764
# consume output and checksum line, if present.
1765
if self.last_dsl_name == dsl_name:
1766
checksum_re = self.last_checksum_re
1767
else:
1768
before, _, after = self.language.checksum_line.format(dsl_name=dsl_name, arguments='{arguments}').partition('{arguments}')
1769
assert _ == '{arguments}'
1770
checksum_re = create_regex(before, after, word=False)
1771
self.last_dsl_name = dsl_name
1772
self.last_checksum_re = checksum_re
1773
1774
# scan forward for checksum line
1775
output_add, output_output = text_accumulator()
1776
arguments = None
1777
while self.input:
1778
line = self._line(lookahead=True)
1779
match = checksum_re.match(line.lstrip())
1780
arguments = match.group(1) if match else None
1781
if arguments:
1782
break
1783
output_add(line)
1784
if self.is_start_line(line):
1785
break
1786
1787
output = output_output()
1788
if arguments:
1789
d = {}
1790
for field in shlex.split(arguments):
1791
name, equals, value = field.partition('=')
1792
if not equals:
1793
fail("Mangled Argument Clinic marker line:", repr(line))
1794
d[name.strip()] = value.strip()
1795
1796
if self.verify:
1797
if 'input' in d:
1798
checksum = d['output']
1799
else:
1800
checksum = d['checksum']
1801
1802
computed = compute_checksum(output, len(checksum))
1803
if checksum != computed:
1804
fail("Checksum mismatch!\nExpected: {}\nComputed: {}\n"
1805
"Suggested fix: remove all generated code including "
1806
"the end marker,\n"
1807
"or use the '-f' option."
1808
.format(checksum, computed))
1809
else:
1810
# put back output
1811
output_lines = output.splitlines(keepends=True)
1812
self.line_number -= len(output_lines)
1813
self.input.extend(reversed(output_lines))
1814
output = None
1815
1816
return Block(input_output(), dsl_name, output=output)
1817
1818
1819
class BlockPrinter:
1820
1821
def __init__(self, language, f=None):
1822
self.language = language
1823
self.f = f or io.StringIO()
1824
1825
def print_block(self, block, *, core_includes=False):
1826
input = block.input
1827
output = block.output
1828
dsl_name = block.dsl_name
1829
write = self.f.write
1830
1831
assert not ((dsl_name is None) ^ (output is None)), "you must specify dsl_name and output together, dsl_name " + repr(dsl_name)
1832
1833
if not dsl_name:
1834
write(input)
1835
return
1836
1837
write(self.language.start_line.format(dsl_name=dsl_name))
1838
write("\n")
1839
1840
body_prefix = self.language.body_prefix.format(dsl_name=dsl_name)
1841
if not body_prefix:
1842
write(input)
1843
else:
1844
for line in input.split('\n'):
1845
write(body_prefix)
1846
write(line)
1847
write("\n")
1848
1849
write(self.language.stop_line.format(dsl_name=dsl_name))
1850
write("\n")
1851
1852
output = ''
1853
if core_includes:
1854
output += textwrap.dedent("""
1855
#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
1856
# include "pycore_gc.h" // PyGC_Head
1857
# include "pycore_runtime.h" // _Py_ID()
1858
#endif
1859
1860
""")
1861
1862
input = ''.join(block.input)
1863
output += ''.join(block.output)
1864
if output:
1865
if not output.endswith('\n'):
1866
output += '\n'
1867
write(output)
1868
1869
arguments = "output={output} input={input}".format(
1870
output=compute_checksum(output, 16),
1871
input=compute_checksum(input, 16)
1872
)
1873
write(self.language.checksum_line.format(dsl_name=dsl_name, arguments=arguments))
1874
write("\n")
1875
1876
def write(self, text):
1877
self.f.write(text)
1878
1879
1880
class BufferSeries:
1881
"""
1882
Behaves like a "defaultlist".
1883
When you ask for an index that doesn't exist yet,
1884
the object grows the list until that item exists.
1885
So o[n] will always work.
1886
1887
Supports negative indices for actual items.
1888
e.g. o[-1] is an element immediately preceding o[0].
1889
"""
1890
1891
def __init__(self):
1892
self._start = 0
1893
self._array = []
1894
self._constructor = _text_accumulator
1895
1896
def __getitem__(self, i):
1897
i -= self._start
1898
if i < 0:
1899
self._start += i
1900
prefix = [self._constructor() for x in range(-i)]
1901
self._array = prefix + self._array
1902
i = 0
1903
while i >= len(self._array):
1904
self._array.append(self._constructor())
1905
return self._array[i]
1906
1907
def clear(self):
1908
for ta in self._array:
1909
ta._text.clear()
1910
1911
def dump(self):
1912
texts = [ta.output() for ta in self._array]
1913
return "".join(texts)
1914
1915
1916
class Destination:
1917
def __init__(self, name, type, clinic, *args):
1918
self.name = name
1919
self.type = type
1920
self.clinic = clinic
1921
valid_types = ('buffer', 'file', 'suppress')
1922
if type not in valid_types:
1923
fail("Invalid destination type " + repr(type) + " for " + name + " , must be " + ', '.join(valid_types))
1924
extra_arguments = 1 if type == "file" else 0
1925
if len(args) < extra_arguments:
1926
fail("Not enough arguments for destination " + name + " new " + type)
1927
if len(args) > extra_arguments:
1928
fail("Too many arguments for destination " + name + " new " + type)
1929
if type =='file':
1930
d = {}
1931
filename = clinic.filename
1932
d['path'] = filename
1933
dirname, basename = os.path.split(filename)
1934
if not dirname:
1935
dirname = '.'
1936
d['dirname'] = dirname
1937
d['basename'] = basename
1938
d['basename_root'], d['basename_extension'] = os.path.splitext(filename)
1939
self.filename = args[0].format_map(d)
1940
1941
self.buffers = BufferSeries()
1942
1943
def __repr__(self):
1944
if self.type == 'file':
1945
file_repr = " " + repr(self.filename)
1946
else:
1947
file_repr = ''
1948
return "".join(("<Destination ", self.name, " ", self.type, file_repr, ">"))
1949
1950
def clear(self):
1951
if self.type != 'buffer':
1952
fail("Can't clear destination" + self.name + " , it's not of type buffer")
1953
self.buffers.clear()
1954
1955
def dump(self):
1956
return self.buffers.dump()
1957
1958
1959
# maps strings to Language objects.
1960
# "languages" maps the name of the language ("C", "Python").
1961
# "extensions" maps the file extension ("c", "py").
1962
LangDict = dict[str, Callable[[str], Language]]
1963
1964
languages = { 'C': CLanguage, 'Python': PythonLanguage }
1965
extensions: LangDict = { name: CLanguage for name in "c cc cpp cxx h hh hpp hxx".split() }
1966
extensions['py'] = PythonLanguage
1967
1968
1969
def file_changed(filename: str, new_contents: str) -> bool:
1970
"""Return true if file contents changed (meaning we must update it)"""
1971
try:
1972
with open(filename, encoding="utf-8") as fp:
1973
old_contents = fp.read()
1974
return old_contents != new_contents
1975
except FileNotFoundError:
1976
return True
1977
1978
1979
def write_file(filename: str, new_contents: str):
1980
# Atomic write using a temporary file and os.replace()
1981
filename_new = f"{filename}.new"
1982
with open(filename_new, "w", encoding="utf-8") as fp:
1983
fp.write(new_contents)
1984
1985
try:
1986
os.replace(filename_new, filename)
1987
except:
1988
os.unlink(filename_new)
1989
raise
1990
1991
1992
ClassDict = dict[str, "Class"]
1993
DestinationDict = dict[str, Destination]
1994
ModuleDict = dict[str, "Module"]
1995
ParserDict = dict[str, "DSLParser"]
1996
1997
clinic = None
1998
class Clinic:
1999
2000
presets_text = """
2001
preset block
2002
everything block
2003
methoddef_ifndef buffer 1
2004
docstring_prototype suppress
2005
parser_prototype suppress
2006
cpp_if suppress
2007
cpp_endif suppress
2008
2009
preset original
2010
everything block
2011
methoddef_ifndef buffer 1
2012
docstring_prototype suppress
2013
parser_prototype suppress
2014
cpp_if suppress
2015
cpp_endif suppress
2016
2017
preset file
2018
everything file
2019
methoddef_ifndef file 1
2020
docstring_prototype suppress
2021
parser_prototype suppress
2022
impl_definition block
2023
2024
preset buffer
2025
everything buffer
2026
methoddef_ifndef buffer 1
2027
impl_definition block
2028
docstring_prototype suppress
2029
impl_prototype suppress
2030
parser_prototype suppress
2031
2032
preset partial-buffer
2033
everything buffer
2034
methoddef_ifndef buffer 1
2035
docstring_prototype block
2036
impl_prototype suppress
2037
methoddef_define block
2038
parser_prototype block
2039
impl_definition block
2040
2041
"""
2042
2043
def __init__(
2044
self,
2045
language: CLanguage,
2046
printer: BlockPrinter | None = None,
2047
*,
2048
verify: bool = True,
2049
filename: str | None = None
2050
) -> None:
2051
# maps strings to Parser objects.
2052
# (instantiated from the "parsers" global.)
2053
self.parsers: ParserDict = {}
2054
self.language: CLanguage = language
2055
if printer:
2056
fail("Custom printers are broken right now")
2057
self.printer = printer or BlockPrinter(language)
2058
self.verify = verify
2059
self.filename = filename
2060
self.modules: ModuleDict = {}
2061
self.classes: ClassDict = {}
2062
self.functions: list[Function] = []
2063
2064
self.line_prefix = self.line_suffix = ''
2065
2066
self.destinations: DestinationDict = {}
2067
self.add_destination("block", "buffer")
2068
self.add_destination("suppress", "suppress")
2069
self.add_destination("buffer", "buffer")
2070
if filename:
2071
self.add_destination("file", "file", "{dirname}/clinic/{basename}.h")
2072
2073
d = self.get_destination_buffer
2074
self.destination_buffers = {
2075
'cpp_if': d('file'),
2076
'docstring_prototype': d('suppress'),
2077
'docstring_definition': d('file'),
2078
'methoddef_define': d('file'),
2079
'impl_prototype': d('file'),
2080
'parser_prototype': d('suppress'),
2081
'parser_definition': d('file'),
2082
'cpp_endif': d('file'),
2083
'methoddef_ifndef': d('file', 1),
2084
'impl_definition': d('block'),
2085
}
2086
2087
DestBufferType = dict[str, Callable[..., Any]]
2088
DestBufferList = list[DestBufferType]
2089
2090
self.destination_buffers_stack: DestBufferList = []
2091
self.ifndef_symbols: set[str] = set()
2092
2093
self.presets: dict[str, dict[Any, Any]] = {}
2094
preset = None
2095
for line in self.presets_text.strip().split('\n'):
2096
line = line.strip()
2097
if not line:
2098
continue
2099
name, value, *options = line.split()
2100
if name == 'preset':
2101
self.presets[value] = preset = {}
2102
continue
2103
2104
if len(options):
2105
index = int(options[0])
2106
else:
2107
index = 0
2108
buffer = self.get_destination_buffer(value, index)
2109
2110
if name == 'everything':
2111
for name in self.destination_buffers:
2112
preset[name] = buffer
2113
continue
2114
2115
assert name in self.destination_buffers
2116
preset[name] = buffer
2117
2118
global clinic
2119
clinic = self
2120
2121
def add_destination(
2122
self,
2123
name: str,
2124
type: str,
2125
*args
2126
) -> None:
2127
if name in self.destinations:
2128
fail("Destination already exists: " + repr(name))
2129
self.destinations[name] = Destination(name, type, self, *args)
2130
2131
def get_destination(self, name: str) -> Destination:
2132
d = self.destinations.get(name)
2133
if not d:
2134
fail("Destination does not exist: " + repr(name))
2135
return d
2136
2137
def get_destination_buffer(
2138
self,
2139
name: str,
2140
item: int = 0
2141
):
2142
d = self.get_destination(name)
2143
return d.buffers[item]
2144
2145
def parse(self, input):
2146
printer = self.printer
2147
self.block_parser = BlockParser(input, self.language, verify=self.verify)
2148
for block in self.block_parser:
2149
dsl_name = block.dsl_name
2150
if dsl_name:
2151
if dsl_name not in self.parsers:
2152
assert dsl_name in parsers, f"No parser to handle {dsl_name!r} block."
2153
self.parsers[dsl_name] = parsers[dsl_name](self)
2154
parser = self.parsers[dsl_name]
2155
try:
2156
parser.parse(block)
2157
except Exception:
2158
fail('Exception raised during parsing:\n' +
2159
traceback.format_exc().rstrip())
2160
printer.print_block(block)
2161
2162
clinic_out = []
2163
2164
# these are destinations not buffers
2165
for name, destination in self.destinations.items():
2166
if destination.type == 'suppress':
2167
continue
2168
output = destination.dump()
2169
2170
if output:
2171
2172
block = Block("", dsl_name="clinic", output=output)
2173
2174
if destination.type == 'buffer':
2175
block.input = "dump " + name + "\n"
2176
warn("Destination buffer " + repr(name) + " not empty at end of file, emptying.")
2177
printer.write("\n")
2178
printer.print_block(block)
2179
continue
2180
2181
if destination.type == 'file':
2182
try:
2183
dirname = os.path.dirname(destination.filename)
2184
try:
2185
os.makedirs(dirname)
2186
except FileExistsError:
2187
if not os.path.isdir(dirname):
2188
fail("Can't write to destination {}, "
2189
"can't make directory {}!".format(
2190
destination.filename, dirname))
2191
if self.verify:
2192
with open(destination.filename) as f:
2193
parser_2 = BlockParser(f.read(), language=self.language)
2194
blocks = list(parser_2)
2195
if (len(blocks) != 1) or (blocks[0].input != 'preserve\n'):
2196
fail("Modified destination file " + repr(destination.filename) + ", not overwriting!")
2197
except FileNotFoundError:
2198
pass
2199
2200
block.input = 'preserve\n'
2201
printer_2 = BlockPrinter(self.language)
2202
printer_2.print_block(block, core_includes=True)
2203
pair = destination.filename, printer_2.f.getvalue()
2204
clinic_out.append(pair)
2205
continue
2206
2207
return printer.f.getvalue(), clinic_out
2208
2209
2210
def _module_and_class(self, fields):
2211
"""
2212
fields should be an iterable of field names.
2213
returns a tuple of (module, class).
2214
the module object could actually be self (a clinic object).
2215
this function is only ever used to find the parent of where
2216
a new class/module should go.
2217
"""
2218
in_classes = False
2219
parent = module = self
2220
cls = None
2221
so_far = []
2222
2223
for field in fields:
2224
so_far.append(field)
2225
if not in_classes:
2226
child = parent.modules.get(field)
2227
if child:
2228
parent = module = child
2229
continue
2230
in_classes = True
2231
if not hasattr(parent, 'classes'):
2232
return module, cls
2233
child = parent.classes.get(field)
2234
if not child:
2235
fail('Parent class or module ' + '.'.join(so_far) + " does not exist.")
2236
cls = parent = child
2237
2238
return module, cls
2239
2240
2241
def parse_file(
2242
filename: str,
2243
*,
2244
verify: bool = True,
2245
output: str | None = None
2246
) -> None:
2247
if not output:
2248
output = filename
2249
2250
extension = os.path.splitext(filename)[1][1:]
2251
if not extension:
2252
fail("Can't extract file type for file " + repr(filename))
2253
2254
try:
2255
language = extensions[extension](filename)
2256
except KeyError:
2257
fail("Can't identify file type for file " + repr(filename))
2258
2259
with open(filename, encoding="utf-8") as f:
2260
raw = f.read()
2261
2262
# exit quickly if there are no clinic markers in the file
2263
find_start_re = BlockParser("", language).find_start_re
2264
if not find_start_re.search(raw):
2265
return
2266
2267
assert isinstance(language, CLanguage)
2268
clinic = Clinic(language, verify=verify, filename=filename)
2269
src_out, clinic_out = clinic.parse(raw)
2270
2271
changes = [(fn, data) for fn, data in clinic_out if file_changed(fn, data)]
2272
if changes:
2273
# Always (re)write the source file.
2274
write_file(output, src_out)
2275
for fn, data in clinic_out:
2276
write_file(fn, data)
2277
2278
2279
def compute_checksum(
2280
input: str | None,
2281
length: int | None = None
2282
) -> str:
2283
input = input or ''
2284
s = hashlib.sha1(input.encode('utf-8')).hexdigest()
2285
if length:
2286
s = s[:length]
2287
return s
2288
2289
2290
2291
2292
class PythonParser:
2293
def __init__(self, clinic: Clinic) -> None:
2294
pass
2295
2296
def parse(self, block: Block) -> None:
2297
s = io.StringIO()
2298
with OverrideStdioWith(s):
2299
exec(block.input)
2300
block.output = s.getvalue()
2301
2302
2303
class Module:
2304
def __init__(
2305
self,
2306
name: str,
2307
module = None
2308
) -> None:
2309
self.name = name
2310
self.module = self.parent = module
2311
2312
self.modules: ModuleDict = {}
2313
self.classes: ClassDict = {}
2314
self.functions: list[Function] = []
2315
2316
def __repr__(self) -> str:
2317
return "<clinic.Module " + repr(self.name) + " at " + str(id(self)) + ">"
2318
2319
2320
class Class:
2321
def __init__(
2322
self,
2323
name: str,
2324
module: Module | None = None,
2325
cls = None,
2326
typedef: str | None = None,
2327
type_object: str | None = None
2328
) -> None:
2329
self.name = name
2330
self.module = module
2331
self.cls = cls
2332
self.typedef = typedef
2333
self.type_object = type_object
2334
self.parent = cls or module
2335
2336
self.classes: ClassDict = {}
2337
self.functions: list[Function] = []
2338
2339
def __repr__(self) -> str:
2340
return "<clinic.Class " + repr(self.name) + " at " + str(id(self)) + ">"
2341
2342
2343
unsupported_special_methods: set[str] = set("""
2344
2345
__abs__
2346
__add__
2347
__and__
2348
__call__
2349
__delitem__
2350
__divmod__
2351
__eq__
2352
__float__
2353
__floordiv__
2354
__ge__
2355
__getattr__
2356
__getattribute__
2357
__getitem__
2358
__gt__
2359
__hash__
2360
__iadd__
2361
__iand__
2362
__ifloordiv__
2363
__ilshift__
2364
__imatmul__
2365
__imod__
2366
__imul__
2367
__index__
2368
__int__
2369
__invert__
2370
__ior__
2371
__ipow__
2372
__irshift__
2373
__isub__
2374
__iter__
2375
__itruediv__
2376
__ixor__
2377
__le__
2378
__len__
2379
__lshift__
2380
__lt__
2381
__matmul__
2382
__mod__
2383
__mul__
2384
__neg__
2385
__next__
2386
__or__
2387
__pos__
2388
__pow__
2389
__radd__
2390
__rand__
2391
__rdivmod__
2392
__repr__
2393
__rfloordiv__
2394
__rlshift__
2395
__rmatmul__
2396
__rmod__
2397
__rmul__
2398
__ror__
2399
__rpow__
2400
__rrshift__
2401
__rshift__
2402
__rsub__
2403
__rtruediv__
2404
__rxor__
2405
__setattr__
2406
__setitem__
2407
__str__
2408
__sub__
2409
__truediv__
2410
__xor__
2411
2412
""".strip().split())
2413
2414
2415
INVALID, CALLABLE, STATIC_METHOD, CLASS_METHOD, METHOD_INIT, METHOD_NEW = """
2416
INVALID, CALLABLE, STATIC_METHOD, CLASS_METHOD, METHOD_INIT, METHOD_NEW
2417
""".replace(",", "").strip().split()
2418
2419
ParamDict = dict[str, "Parameter"]
2420
ReturnConverterType = Callable[..., "CReturnConverter"]
2421
2422
class Function:
2423
"""
2424
Mutable duck type for inspect.Function.
2425
2426
docstring - a str containing
2427
* embedded line breaks
2428
* text outdented to the left margin
2429
* no trailing whitespace.
2430
It will always be true that
2431
(not docstring) or ((not docstring[0].isspace()) and (docstring.rstrip() == docstring))
2432
"""
2433
2434
def __init__(
2435
self,
2436
parameters: ParamDict | None = None,
2437
*,
2438
name: str,
2439
module: Module,
2440
cls: Class | None = None,
2441
c_basename: str | None = None,
2442
full_name: str | None = None,
2443
return_converter: ReturnConverterType,
2444
return_annotation = inspect.Signature.empty,
2445
docstring: str | None = None,
2446
kind: str = CALLABLE,
2447
coexist: bool = False,
2448
docstring_only: bool = False
2449
) -> None:
2450
self.parameters = parameters or {}
2451
self.return_annotation = return_annotation
2452
self.name = name
2453
self.full_name = full_name
2454
self.module = module
2455
self.cls = cls
2456
self.parent = cls or module
2457
self.c_basename = c_basename
2458
self.return_converter = return_converter
2459
self.docstring = docstring or ''
2460
self.kind = kind
2461
self.coexist = coexist
2462
self.self_converter = None
2463
# docstring_only means "don't generate a machine-readable
2464
# signature, just a normal docstring". it's True for
2465
# functions with optional groups because we can't represent
2466
# those accurately with inspect.Signature in 3.4.
2467
self.docstring_only = docstring_only
2468
2469
self.rendered_parameters = None
2470
2471
__render_parameters__ = None
2472
@property
2473
def render_parameters(self):
2474
if not self.__render_parameters__:
2475
self.__render_parameters__ = l = []
2476
for p in self.parameters.values():
2477
p = p.copy()
2478
p.converter.pre_render()
2479
l.append(p)
2480
return self.__render_parameters__
2481
2482
@property
2483
def methoddef_flags(self) -> str | None:
2484
if self.kind in (METHOD_INIT, METHOD_NEW):
2485
return None
2486
flags = []
2487
if self.kind == CLASS_METHOD:
2488
flags.append('METH_CLASS')
2489
elif self.kind == STATIC_METHOD:
2490
flags.append('METH_STATIC')
2491
else:
2492
assert self.kind == CALLABLE, "unknown kind: " + repr(self.kind)
2493
if self.coexist:
2494
flags.append('METH_COEXIST')
2495
return '|'.join(flags)
2496
2497
def __repr__(self) -> str:
2498
return '<clinic.Function ' + self.name + '>'
2499
2500
def copy(self, **overrides) -> "Function":
2501
kwargs = {
2502
'name': self.name, 'module': self.module, 'parameters': self.parameters,
2503
'cls': self.cls, 'c_basename': self.c_basename,
2504
'full_name': self.full_name,
2505
'return_converter': self.return_converter, 'return_annotation': self.return_annotation,
2506
'docstring': self.docstring, 'kind': self.kind, 'coexist': self.coexist,
2507
'docstring_only': self.docstring_only,
2508
}
2509
kwargs.update(overrides)
2510
f = Function(**kwargs)
2511
f.parameters = {
2512
name: value.copy(function=f)
2513
for name, value in f.parameters.items()
2514
}
2515
return f
2516
2517
2518
class Parameter:
2519
"""
2520
Mutable duck type of inspect.Parameter.
2521
"""
2522
2523
def __init__(
2524
self,
2525
name: str,
2526
kind: str,
2527
*,
2528
default = inspect.Parameter.empty,
2529
function: Function,
2530
converter: "CConverter",
2531
annotation = inspect.Parameter.empty,
2532
docstring: str | None = None,
2533
group: int = 0
2534
) -> None:
2535
self.name = name
2536
self.kind = kind
2537
self.default = default
2538
self.function = function
2539
self.converter = converter
2540
self.annotation = annotation
2541
self.docstring = docstring or ''
2542
self.group = group
2543
2544
def __repr__(self) -> str:
2545
return '<clinic.Parameter ' + self.name + '>'
2546
2547
def is_keyword_only(self) -> bool:
2548
return self.kind == inspect.Parameter.KEYWORD_ONLY
2549
2550
def is_positional_only(self) -> bool:
2551
return self.kind == inspect.Parameter.POSITIONAL_ONLY
2552
2553
def is_vararg(self) -> bool:
2554
return self.kind == inspect.Parameter.VAR_POSITIONAL
2555
2556
def is_optional(self) -> bool:
2557
return not self.is_vararg() and (self.default is not unspecified)
2558
2559
def copy(self, **overrides) -> "Parameter":
2560
kwargs = {
2561
'name': self.name, 'kind': self.kind, 'default':self.default,
2562
'function': self.function, 'converter': self.converter, 'annotation': self.annotation,
2563
'docstring': self.docstring, 'group': self.group,
2564
}
2565
kwargs.update(overrides)
2566
if 'converter' not in overrides:
2567
converter = copy.copy(self.converter)
2568
converter.function = kwargs['function']
2569
kwargs['converter'] = converter
2570
return Parameter(**kwargs)
2571
2572
def get_displayname(self, i: int) -> str:
2573
if i == 0:
2574
return '"argument"'
2575
if not self.is_positional_only():
2576
return f'"argument {self.name!r}"'
2577
else:
2578
return f'"argument {i}"'
2579
2580
2581
class LandMine:
2582
# try to access any
2583
def __init__(self, message: str) -> None:
2584
self.__message__ = message
2585
2586
def __repr__(self) -> str:
2587
return '<LandMine ' + repr(self.__message__) + ">"
2588
2589
def __getattribute__(self, name: str):
2590
if name in ('__repr__', '__message__'):
2591
return super().__getattribute__(name)
2592
# raise RuntimeError(repr(name))
2593
fail("Stepped on a land mine, trying to access attribute " + repr(name) + ":\n" + self.__message__)
2594
2595
2596
def add_c_converter(f, name=None):
2597
if not name:
2598
name = f.__name__
2599
if not name.endswith('_converter'):
2600
return f
2601
name = name.removesuffix('_converter')
2602
converters[name] = f
2603
return f
2604
2605
def add_default_legacy_c_converter(cls):
2606
# automatically add converter for default format unit
2607
# (but without stomping on the existing one if it's already
2608
# set, in case you subclass)
2609
if ((cls.format_unit not in ('O&', '')) and
2610
(cls.format_unit not in legacy_converters)):
2611
legacy_converters[cls.format_unit] = cls
2612
return cls
2613
2614
def add_legacy_c_converter(format_unit, **kwargs):
2615
"""
2616
Adds a legacy converter.
2617
"""
2618
def closure(f):
2619
if not kwargs:
2620
added_f = f
2621
else:
2622
added_f = functools.partial(f, **kwargs)
2623
if format_unit:
2624
legacy_converters[format_unit] = added_f
2625
return f
2626
return closure
2627
2628
class CConverterAutoRegister(type):
2629
def __init__(cls, name, bases, classdict):
2630
add_c_converter(cls)
2631
add_default_legacy_c_converter(cls)
2632
2633
class CConverter(metaclass=CConverterAutoRegister):
2634
"""
2635
For the init function, self, name, function, and default
2636
must be keyword-or-positional parameters. All other
2637
parameters must be keyword-only.
2638
"""
2639
2640
# The C name to use for this variable.
2641
name: str | None = None
2642
2643
# The Python name to use for this variable.
2644
py_name: str | None = None
2645
2646
# The C type to use for this variable.
2647
# 'type' should be a Python string specifying the type, e.g. "int".
2648
# If this is a pointer type, the type string should end with ' *'.
2649
type: str | None = None
2650
2651
# The Python default value for this parameter, as a Python value.
2652
# Or the magic value "unspecified" if there is no default.
2653
# Or the magic value "unknown" if this value is a cannot be evaluated
2654
# at Argument-Clinic-preprocessing time (but is presumed to be valid
2655
# at runtime).
2656
default: object = unspecified
2657
2658
# If not None, default must be isinstance() of this type.
2659
# (You can also specify a tuple of types.)
2660
default_type: bltns.type[Any] | tuple[bltns.type[Any], ...] | None = None
2661
2662
# "default" converted into a C value, as a string.
2663
# Or None if there is no default.
2664
c_default: str | None = None
2665
2666
# "default" converted into a Python value, as a string.
2667
# Or None if there is no default.
2668
py_default: str | None = None
2669
2670
# The default value used to initialize the C variable when
2671
# there is no default, but not specifying a default may
2672
# result in an "uninitialized variable" warning. This can
2673
# easily happen when using option groups--although
2674
# properly-written code won't actually use the variable,
2675
# the variable does get passed in to the _impl. (Ah, if
2676
# only dataflow analysis could inline the static function!)
2677
#
2678
# This value is specified as a string.
2679
# Every non-abstract subclass should supply a valid value.
2680
c_ignored_default: str = 'NULL'
2681
2682
# If true, wrap with Py_UNUSED.
2683
unused = False
2684
2685
# The C converter *function* to be used, if any.
2686
# (If this is not None, format_unit must be 'O&'.)
2687
converter: str | None = None
2688
2689
# Should Argument Clinic add a '&' before the name of
2690
# the variable when passing it into the _impl function?
2691
impl_by_reference = False
2692
2693
# Should Argument Clinic add a '&' before the name of
2694
# the variable when passing it into PyArg_ParseTuple (AndKeywords)?
2695
parse_by_reference = True
2696
2697
#############################################################
2698
#############################################################
2699
## You shouldn't need to read anything below this point to ##
2700
## write your own converter functions. ##
2701
#############################################################
2702
#############################################################
2703
2704
# The "format unit" to specify for this variable when
2705
# parsing arguments using PyArg_ParseTuple (AndKeywords).
2706
# Custom converters should always use the default value of 'O&'.
2707
format_unit = 'O&'
2708
2709
# What encoding do we want for this variable? Only used
2710
# by format units starting with 'e'.
2711
encoding: str | None = None
2712
2713
# Should this object be required to be a subclass of a specific type?
2714
# If not None, should be a string representing a pointer to a
2715
# PyTypeObject (e.g. "&PyUnicode_Type").
2716
# Only used by the 'O!' format unit (and the "object" converter).
2717
subclass_of = None
2718
2719
# Do we want an adjacent '_length' variable for this variable?
2720
# Only used by format units ending with '#'.
2721
length = False
2722
2723
# Should we show this parameter in the generated
2724
# __text_signature__? This is *almost* always True.
2725
# (It's only False for __new__, __init__, and METH_STATIC functions.)
2726
show_in_signature = True
2727
2728
# Overrides the name used in a text signature.
2729
# The name used for a "self" parameter must be one of
2730
# self, type, or module; however users can set their own.
2731
# This lets the self_converter overrule the user-settable
2732
# name, *just* for the text signature.
2733
# Only set by self_converter.
2734
signature_name = None
2735
2736
# keep in sync with self_converter.__init__!
2737
def __init__(self,
2738
# Positional args:
2739
name: str,
2740
py_name: str,
2741
function,
2742
default: object = unspecified,
2743
*, # Keyword only args:
2744
c_default: str | None = None,
2745
py_default: str | None = None,
2746
annotation: str | Literal[Sentinels.unspecified] = unspecified,
2747
unused: bool = False,
2748
**kwargs
2749
):
2750
self.name = ensure_legal_c_identifier(name)
2751
self.py_name = py_name
2752
self.unused = unused
2753
2754
if default is not unspecified:
2755
if (self.default_type
2756
and default is not unknown
2757
and not isinstance(default, self.default_type)
2758
):
2759
if isinstance(self.default_type, type):
2760
types_str = self.default_type.__name__
2761
else:
2762
names = [cls.__name__ for cls in self.default_type]
2763
types_str = ', '.join(names)
2764
fail("{}: default value {!r} for field {} is not of type {}".format(
2765
self.__class__.__name__, default, name, types_str))
2766
self.default = default
2767
2768
if c_default:
2769
self.c_default = c_default
2770
if py_default:
2771
self.py_default = py_default
2772
2773
if annotation is not unspecified:
2774
fail("The 'annotation' parameter is not currently permitted.")
2775
2776
# this is deliberate, to prevent you from caching information
2777
# about the function in the init.
2778
# (that breaks if we get cloned.)
2779
# so after this change we will noisily fail.
2780
self.function = LandMine("Don't access members of self.function inside converter_init!")
2781
self.converter_init(**kwargs)
2782
self.function = function
2783
2784
def converter_init(self):
2785
pass
2786
2787
def is_optional(self) -> bool:
2788
return (self.default is not unspecified)
2789
2790
def _render_self(self, parameter: str, data: CRenderData) -> None:
2791
self.parameter = parameter
2792
name = self.parser_name
2793
2794
# impl_arguments
2795
s = ("&" if self.impl_by_reference else "") + name
2796
data.impl_arguments.append(s)
2797
if self.length:
2798
data.impl_arguments.append(self.length_name())
2799
2800
# impl_parameters
2801
data.impl_parameters.append(self.simple_declaration(by_reference=self.impl_by_reference))
2802
if self.length:
2803
data.impl_parameters.append("Py_ssize_t " + self.length_name())
2804
2805
def _render_non_self(self, parameter, data):
2806
self.parameter = parameter
2807
name = self.name
2808
2809
# declarations
2810
d = self.declaration(in_parser=True)
2811
data.declarations.append(d)
2812
2813
# initializers
2814
initializers = self.initialize()
2815
if initializers:
2816
data.initializers.append('/* initializers for ' + name + ' */\n' + initializers.rstrip())
2817
2818
# modifications
2819
modifications = self.modify()
2820
if modifications:
2821
data.modifications.append('/* modifications for ' + name + ' */\n' + modifications.rstrip())
2822
2823
# keywords
2824
if parameter.is_vararg():
2825
pass
2826
elif parameter.is_positional_only():
2827
data.keywords.append('')
2828
else:
2829
data.keywords.append(parameter.name)
2830
2831
# format_units
2832
if self.is_optional() and '|' not in data.format_units:
2833
data.format_units.append('|')
2834
if parameter.is_keyword_only() and '$' not in data.format_units:
2835
data.format_units.append('$')
2836
data.format_units.append(self.format_unit)
2837
2838
# parse_arguments
2839
self.parse_argument(data.parse_arguments)
2840
2841
# post_parsing
2842
if post_parsing := self.post_parsing():
2843
data.post_parsing.append('/* Post parse cleanup for ' + name + ' */\n' + post_parsing.rstrip() + '\n')
2844
2845
# cleanup
2846
cleanup = self.cleanup()
2847
if cleanup:
2848
data.cleanup.append('/* Cleanup for ' + name + ' */\n' + cleanup.rstrip() + "\n")
2849
2850
def render(self, parameter: str, data: CRenderData) -> None:
2851
"""
2852
parameter is a clinic.Parameter instance.
2853
data is a CRenderData instance.
2854
"""
2855
self._render_self(parameter, data)
2856
self._render_non_self(parameter, data)
2857
2858
def length_name(self):
2859
"""Computes the name of the associated "length" variable."""
2860
if not self.length:
2861
return None
2862
return self.parser_name + "_length"
2863
2864
# Why is this one broken out separately?
2865
# For "positional-only" function parsing,
2866
# which generates a bunch of PyArg_ParseTuple calls.
2867
def parse_argument(self, list):
2868
assert not (self.converter and self.encoding)
2869
if self.format_unit == 'O&':
2870
assert self.converter
2871
list.append(self.converter)
2872
2873
if self.encoding:
2874
list.append(c_repr(self.encoding))
2875
elif self.subclass_of:
2876
list.append(self.subclass_of)
2877
2878
s = ("&" if self.parse_by_reference else "") + self.name
2879
list.append(s)
2880
2881
if self.length:
2882
list.append("&" + self.length_name())
2883
2884
#
2885
# All the functions after here are intended as extension points.
2886
#
2887
2888
def simple_declaration(self, by_reference=False, *, in_parser=False):
2889
"""
2890
Computes the basic declaration of the variable.
2891
Used in computing the prototype declaration and the
2892
variable declaration.
2893
"""
2894
prototype = [self.type]
2895
if by_reference or not self.type.endswith('*'):
2896
prototype.append(" ")
2897
if by_reference:
2898
prototype.append('*')
2899
if in_parser:
2900
name = self.parser_name
2901
else:
2902
name = self.name
2903
if self.unused:
2904
name = f"Py_UNUSED({name})"
2905
prototype.append(name)
2906
return "".join(prototype)
2907
2908
def declaration(self, *, in_parser=False):
2909
"""
2910
The C statement to declare this variable.
2911
"""
2912
declaration = [self.simple_declaration(in_parser=True)]
2913
default = self.c_default
2914
if not default and self.parameter.group:
2915
default = self.c_ignored_default
2916
if default:
2917
declaration.append(" = ")
2918
declaration.append(default)
2919
declaration.append(";")
2920
if self.length:
2921
declaration.append('\nPy_ssize_t ')
2922
declaration.append(self.length_name())
2923
declaration.append(';')
2924
return "".join(declaration)
2925
2926
def initialize(self) -> str:
2927
"""
2928
The C statements required to set up this variable before parsing.
2929
Returns a string containing this code indented at column 0.
2930
If no initialization is necessary, returns an empty string.
2931
"""
2932
return ""
2933
2934
def modify(self) -> str:
2935
"""
2936
The C statements required to modify this variable after parsing.
2937
Returns a string containing this code indented at column 0.
2938
If no modification is necessary, returns an empty string.
2939
"""
2940
return ""
2941
2942
def post_parsing(self) -> str:
2943
"""
2944
The C statements required to do some operations after the end of parsing but before cleaning up.
2945
Return a string containing this code indented at column 0.
2946
If no operation is necessary, return an empty string.
2947
"""
2948
return ""
2949
2950
def cleanup(self) -> str:
2951
"""
2952
The C statements required to clean up after this variable.
2953
Returns a string containing this code indented at column 0.
2954
If no cleanup is necessary, returns an empty string.
2955
"""
2956
return ""
2957
2958
def pre_render(self):
2959
"""
2960
A second initialization function, like converter_init,
2961
called just before rendering.
2962
You are permitted to examine self.function here.
2963
"""
2964
pass
2965
2966
def parse_arg(self, argname, displayname):
2967
if self.format_unit == 'O&':
2968
return """
2969
if (!{converter}({argname}, &{paramname})) {{{{
2970
goto exit;
2971
}}}}
2972
""".format(argname=argname, paramname=self.parser_name,
2973
converter=self.converter)
2974
if self.format_unit == 'O!':
2975
cast = '(%s)' % self.type if self.type != 'PyObject *' else ''
2976
if self.subclass_of in type_checks:
2977
typecheck, typename = type_checks[self.subclass_of]
2978
return """
2979
if (!{typecheck}({argname})) {{{{
2980
_PyArg_BadArgument("{{name}}", {displayname}, "{typename}", {argname});
2981
goto exit;
2982
}}}}
2983
{paramname} = {cast}{argname};
2984
""".format(argname=argname, paramname=self.parser_name,
2985
displayname=displayname, typecheck=typecheck,
2986
typename=typename, cast=cast)
2987
return """
2988
if (!PyObject_TypeCheck({argname}, {subclass_of})) {{{{
2989
_PyArg_BadArgument("{{name}}", {displayname}, ({subclass_of})->tp_name, {argname});
2990
goto exit;
2991
}}}}
2992
{paramname} = {cast}{argname};
2993
""".format(argname=argname, paramname=self.parser_name,
2994
subclass_of=self.subclass_of, cast=cast,
2995
displayname=displayname)
2996
if self.format_unit == 'O':
2997
cast = '(%s)' % self.type if self.type != 'PyObject *' else ''
2998
return """
2999
{paramname} = {cast}{argname};
3000
""".format(argname=argname, paramname=self.parser_name, cast=cast)
3001
return None
3002
3003
def set_template_dict(self, template_dict: TemplateDict) -> None:
3004
pass
3005
3006
@property
3007
def parser_name(self):
3008
if self.name in CLINIC_PREFIXED_ARGS: # bpo-39741
3009
return CLINIC_PREFIX + self.name
3010
else:
3011
return self.name
3012
3013
type_checks = {
3014
'&PyLong_Type': ('PyLong_Check', 'int'),
3015
'&PyTuple_Type': ('PyTuple_Check', 'tuple'),
3016
'&PyList_Type': ('PyList_Check', 'list'),
3017
'&PySet_Type': ('PySet_Check', 'set'),
3018
'&PyFrozenSet_Type': ('PyFrozenSet_Check', 'frozenset'),
3019
'&PyDict_Type': ('PyDict_Check', 'dict'),
3020
'&PyUnicode_Type': ('PyUnicode_Check', 'str'),
3021
'&PyBytes_Type': ('PyBytes_Check', 'bytes'),
3022
'&PyByteArray_Type': ('PyByteArray_Check', 'bytearray'),
3023
}
3024
3025
3026
ConverterType = Callable[..., CConverter]
3027
ConverterDict = dict[str, ConverterType]
3028
3029
# maps strings to callables.
3030
# these callables must be of the form:
3031
# def foo(name, default, *, ...)
3032
# The callable may have any number of keyword-only parameters.
3033
# The callable must return a CConverter object.
3034
# The callable should not call builtins.print.
3035
converters: ConverterDict = {}
3036
3037
# maps strings to callables.
3038
# these callables follow the same rules as those for "converters" above.
3039
# note however that they will never be called with keyword-only parameters.
3040
legacy_converters: ConverterDict = {}
3041
3042
# maps strings to callables.
3043
# these callables must be of the form:
3044
# def foo(*, ...)
3045
# The callable may have any number of keyword-only parameters.
3046
# The callable must return a CReturnConverter object.
3047
# The callable should not call builtins.print.
3048
ReturnConverterDict = dict[str, ReturnConverterType]
3049
return_converters: ReturnConverterDict = {}
3050
3051
TypeSet = set[bltns.type[Any]]
3052
3053
3054
class bool_converter(CConverter):
3055
type = 'int'
3056
default_type = bool
3057
format_unit = 'p'
3058
c_ignored_default = '0'
3059
3060
def converter_init(self, *, accept: TypeSet = {object}) -> None:
3061
if accept == {int}:
3062
self.format_unit = 'i'
3063
elif accept != {object}:
3064
fail("bool_converter: illegal 'accept' argument " + repr(accept))
3065
if self.default is not unspecified:
3066
self.default = bool(self.default)
3067
self.c_default = str(int(self.default))
3068
3069
def parse_arg(self, argname: str, displayname: str) -> str:
3070
if self.format_unit == 'i':
3071
return """
3072
{paramname} = _PyLong_AsInt({argname});
3073
if ({paramname} == -1 && PyErr_Occurred()) {{{{
3074
goto exit;
3075
}}}}
3076
""".format(argname=argname, paramname=self.parser_name)
3077
elif self.format_unit == 'p':
3078
return """
3079
{paramname} = PyObject_IsTrue({argname});
3080
if ({paramname} < 0) {{{{
3081
goto exit;
3082
}}}}
3083
""".format(argname=argname, paramname=self.parser_name)
3084
return super().parse_arg(argname, displayname)
3085
3086
class defining_class_converter(CConverter):
3087
"""
3088
A special-case converter:
3089
this is the default converter used for the defining class.
3090
"""
3091
type = 'PyTypeObject *'
3092
format_unit = ''
3093
show_in_signature = False
3094
3095
def converter_init(self, *, type=None) -> None:
3096
self.specified_type = type
3097
3098
def render(self, parameter, data) -> None:
3099
self._render_self(parameter, data)
3100
3101
def set_template_dict(self, template_dict):
3102
template_dict['defining_class_name'] = self.name
3103
3104
3105
class char_converter(CConverter):
3106
type = 'char'
3107
default_type = (bytes, bytearray)
3108
format_unit = 'c'
3109
c_ignored_default = "'\0'"
3110
3111
def converter_init(self) -> None:
3112
if isinstance(self.default, self.default_type):
3113
if len(self.default) != 1:
3114
fail("char_converter: illegal default value " + repr(self.default))
3115
3116
self.c_default = repr(bytes(self.default))[1:]
3117
if self.c_default == '"\'"':
3118
self.c_default = r"'\''"
3119
3120
def parse_arg(self, argname: str, displayname: str) -> str:
3121
if self.format_unit == 'c':
3122
return """
3123
if (PyBytes_Check({argname}) && PyBytes_GET_SIZE({argname}) == 1) {{{{
3124
{paramname} = PyBytes_AS_STRING({argname})[0];
3125
}}}}
3126
else if (PyByteArray_Check({argname}) && PyByteArray_GET_SIZE({argname}) == 1) {{{{
3127
{paramname} = PyByteArray_AS_STRING({argname})[0];
3128
}}}}
3129
else {{{{
3130
_PyArg_BadArgument("{{name}}", {displayname}, "a byte string of length 1", {argname});
3131
goto exit;
3132
}}}}
3133
""".format(argname=argname, paramname=self.parser_name,
3134
displayname=displayname)
3135
return super().parse_arg(argname, displayname)
3136
3137
3138
@add_legacy_c_converter('B', bitwise=True)
3139
class unsigned_char_converter(CConverter):
3140
type = 'unsigned char'
3141
default_type = int
3142
format_unit = 'b'
3143
c_ignored_default = "'\0'"
3144
3145
def converter_init(self, *, bitwise: bool = False) -> None:
3146
if bitwise:
3147
self.format_unit = 'B'
3148
3149
def parse_arg(self, argname: str, displayname: str) -> str:
3150
if self.format_unit == 'b':
3151
return """
3152
{{{{
3153
long ival = PyLong_AsLong({argname});
3154
if (ival == -1 && PyErr_Occurred()) {{{{
3155
goto exit;
3156
}}}}
3157
else if (ival < 0) {{{{
3158
PyErr_SetString(PyExc_OverflowError,
3159
"unsigned byte integer is less than minimum");
3160
goto exit;
3161
}}}}
3162
else if (ival > UCHAR_MAX) {{{{
3163
PyErr_SetString(PyExc_OverflowError,
3164
"unsigned byte integer is greater than maximum");
3165
goto exit;
3166
}}}}
3167
else {{{{
3168
{paramname} = (unsigned char) ival;
3169
}}}}
3170
}}}}
3171
""".format(argname=argname, paramname=self.parser_name)
3172
elif self.format_unit == 'B':
3173
return """
3174
{{{{
3175
unsigned long ival = PyLong_AsUnsignedLongMask({argname});
3176
if (ival == (unsigned long)-1 && PyErr_Occurred()) {{{{
3177
goto exit;
3178
}}}}
3179
else {{{{
3180
{paramname} = (unsigned char) ival;
3181
}}}}
3182
}}}}
3183
""".format(argname=argname, paramname=self.parser_name)
3184
return super().parse_arg(argname, displayname)
3185
3186
class byte_converter(unsigned_char_converter): pass
3187
3188
class short_converter(CConverter):
3189
type = 'short'
3190
default_type = int
3191
format_unit = 'h'
3192
c_ignored_default = "0"
3193
3194
def parse_arg(self, argname: str, displayname: str) -> str:
3195
if self.format_unit == 'h':
3196
return """
3197
{{{{
3198
long ival = PyLong_AsLong({argname});
3199
if (ival == -1 && PyErr_Occurred()) {{{{
3200
goto exit;
3201
}}}}
3202
else if (ival < SHRT_MIN) {{{{
3203
PyErr_SetString(PyExc_OverflowError,
3204
"signed short integer is less than minimum");
3205
goto exit;
3206
}}}}
3207
else if (ival > SHRT_MAX) {{{{
3208
PyErr_SetString(PyExc_OverflowError,
3209
"signed short integer is greater than maximum");
3210
goto exit;
3211
}}}}
3212
else {{{{
3213
{paramname} = (short) ival;
3214
}}}}
3215
}}}}
3216
""".format(argname=argname, paramname=self.parser_name)
3217
return super().parse_arg(argname, displayname)
3218
3219
class unsigned_short_converter(CConverter):
3220
type = 'unsigned short'
3221
default_type = int
3222
c_ignored_default = "0"
3223
3224
def converter_init(self, *, bitwise: bool = False) -> None:
3225
if bitwise:
3226
self.format_unit = 'H'
3227
else:
3228
self.converter = '_PyLong_UnsignedShort_Converter'
3229
3230
def parse_arg(self, argname: str, displayname: str) -> str:
3231
if self.format_unit == 'H':
3232
return """
3233
{paramname} = (unsigned short)PyLong_AsUnsignedLongMask({argname});
3234
if ({paramname} == (unsigned short)-1 && PyErr_Occurred()) {{{{
3235
goto exit;
3236
}}}}
3237
""".format(argname=argname, paramname=self.parser_name)
3238
return super().parse_arg(argname, displayname)
3239
3240
@add_legacy_c_converter('C', accept={str})
3241
class int_converter(CConverter):
3242
type = 'int'
3243
default_type = int
3244
format_unit = 'i'
3245
c_ignored_default = "0"
3246
3247
def converter_init(self, *, accept: TypeSet = {int}, type=None) -> None:
3248
if accept == {str}:
3249
self.format_unit = 'C'
3250
elif accept != {int}:
3251
fail("int_converter: illegal 'accept' argument " + repr(accept))
3252
if type is not None:
3253
self.type = type
3254
3255
def parse_arg(self, argname: str, displayname: str) -> str:
3256
if self.format_unit == 'i':
3257
return """
3258
{paramname} = _PyLong_AsInt({argname});
3259
if ({paramname} == -1 && PyErr_Occurred()) {{{{
3260
goto exit;
3261
}}}}
3262
""".format(argname=argname, paramname=self.parser_name)
3263
elif self.format_unit == 'C':
3264
return """
3265
if (!PyUnicode_Check({argname})) {{{{
3266
_PyArg_BadArgument("{{name}}", {displayname}, "a unicode character", {argname});
3267
goto exit;
3268
}}}}
3269
if (PyUnicode_GET_LENGTH({argname}) != 1) {{{{
3270
_PyArg_BadArgument("{{name}}", {displayname}, "a unicode character", {argname});
3271
goto exit;
3272
}}}}
3273
{paramname} = PyUnicode_READ_CHAR({argname}, 0);
3274
""".format(argname=argname, paramname=self.parser_name,
3275
displayname=displayname)
3276
return super().parse_arg(argname, displayname)
3277
3278
class unsigned_int_converter(CConverter):
3279
type = 'unsigned int'
3280
default_type = int
3281
c_ignored_default = "0"
3282
3283
def converter_init(self, *, bitwise: bool = False) -> None:
3284
if bitwise:
3285
self.format_unit = 'I'
3286
else:
3287
self.converter = '_PyLong_UnsignedInt_Converter'
3288
3289
def parse_arg(self, argname: str, displayname: str) -> str:
3290
if self.format_unit == 'I':
3291
return """
3292
{paramname} = (unsigned int)PyLong_AsUnsignedLongMask({argname});
3293
if ({paramname} == (unsigned int)-1 && PyErr_Occurred()) {{{{
3294
goto exit;
3295
}}}}
3296
""".format(argname=argname, paramname=self.parser_name)
3297
return super().parse_arg(argname, displayname)
3298
3299
class long_converter(CConverter):
3300
type = 'long'
3301
default_type = int
3302
format_unit = 'l'
3303
c_ignored_default = "0"
3304
3305
def parse_arg(self, argname: str, displayname: str) -> str:
3306
if self.format_unit == 'l':
3307
return """
3308
{paramname} = PyLong_AsLong({argname});
3309
if ({paramname} == -1 && PyErr_Occurred()) {{{{
3310
goto exit;
3311
}}}}
3312
""".format(argname=argname, paramname=self.parser_name)
3313
return super().parse_arg(argname, displayname)
3314
3315
class unsigned_long_converter(CConverter):
3316
type = 'unsigned long'
3317
default_type = int
3318
c_ignored_default = "0"
3319
3320
def converter_init(self, *, bitwise: bool = False) -> None:
3321
if bitwise:
3322
self.format_unit = 'k'
3323
else:
3324
self.converter = '_PyLong_UnsignedLong_Converter'
3325
3326
def parse_arg(self, argname: str, displayname: str) -> str:
3327
if self.format_unit == 'k':
3328
return """
3329
if (!PyLong_Check({argname})) {{{{
3330
_PyArg_BadArgument("{{name}}", {displayname}, "int", {argname});
3331
goto exit;
3332
}}}}
3333
{paramname} = PyLong_AsUnsignedLongMask({argname});
3334
""".format(argname=argname, paramname=self.parser_name,
3335
displayname=displayname)
3336
return super().parse_arg(argname, displayname)
3337
3338
class long_long_converter(CConverter):
3339
type = 'long long'
3340
default_type = int
3341
format_unit = 'L'
3342
c_ignored_default = "0"
3343
3344
def parse_arg(self, argname: str, displayname: str) -> str:
3345
if self.format_unit == 'L':
3346
return """
3347
{paramname} = PyLong_AsLongLong({argname});
3348
if ({paramname} == -1 && PyErr_Occurred()) {{{{
3349
goto exit;
3350
}}}}
3351
""".format(argname=argname, paramname=self.parser_name)
3352
return super().parse_arg(argname, displayname)
3353
3354
class unsigned_long_long_converter(CConverter):
3355
type = 'unsigned long long'
3356
default_type = int
3357
c_ignored_default = "0"
3358
3359
def converter_init(self, *, bitwise: bool = False) -> None:
3360
if bitwise:
3361
self.format_unit = 'K'
3362
else:
3363
self.converter = '_PyLong_UnsignedLongLong_Converter'
3364
3365
def parse_arg(self, argname: str, displayname: str) -> str:
3366
if self.format_unit == 'K':
3367
return """
3368
if (!PyLong_Check({argname})) {{{{
3369
_PyArg_BadArgument("{{name}}", {displayname}, "int", {argname});
3370
goto exit;
3371
}}}}
3372
{paramname} = PyLong_AsUnsignedLongLongMask({argname});
3373
""".format(argname=argname, paramname=self.parser_name,
3374
displayname=displayname)
3375
return super().parse_arg(argname, displayname)
3376
3377
class Py_ssize_t_converter(CConverter):
3378
type = 'Py_ssize_t'
3379
c_ignored_default = "0"
3380
3381
def converter_init(self, *, accept: TypeSet = {int}) -> None:
3382
if accept == {int}:
3383
self.format_unit = 'n'
3384
self.default_type = int
3385
elif accept == {int, NoneType}:
3386
self.converter = '_Py_convert_optional_to_ssize_t'
3387
else:
3388
fail("Py_ssize_t_converter: illegal 'accept' argument " + repr(accept))
3389
3390
def parse_arg(self, argname: str, displayname: str) -> str:
3391
if self.format_unit == 'n':
3392
return """
3393
{{{{
3394
Py_ssize_t ival = -1;
3395
PyObject *iobj = _PyNumber_Index({argname});
3396
if (iobj != NULL) {{{{
3397
ival = PyLong_AsSsize_t(iobj);
3398
Py_DECREF(iobj);
3399
}}}}
3400
if (ival == -1 && PyErr_Occurred()) {{{{
3401
goto exit;
3402
}}}}
3403
{paramname} = ival;
3404
}}}}
3405
""".format(argname=argname, paramname=self.parser_name)
3406
return super().parse_arg(argname, displayname)
3407
3408
3409
class slice_index_converter(CConverter):
3410
type = 'Py_ssize_t'
3411
3412
def converter_init(self, *, accept: TypeSet = {int, NoneType}) -> None:
3413
if accept == {int}:
3414
self.converter = '_PyEval_SliceIndexNotNone'
3415
elif accept == {int, NoneType}:
3416
self.converter = '_PyEval_SliceIndex'
3417
else:
3418
fail("slice_index_converter: illegal 'accept' argument " + repr(accept))
3419
3420
class size_t_converter(CConverter):
3421
type = 'size_t'
3422
converter = '_PyLong_Size_t_Converter'
3423
c_ignored_default = "0"
3424
3425
def parse_arg(self, argname: str, displayname: str) -> str:
3426
if self.format_unit == 'n':
3427
return """
3428
{paramname} = PyNumber_AsSsize_t({argname}, PyExc_OverflowError);
3429
if ({paramname} == -1 && PyErr_Occurred()) {{{{
3430
goto exit;
3431
}}}}
3432
""".format(argname=argname, paramname=self.parser_name)
3433
return super().parse_arg(argname, displayname)
3434
3435
3436
class fildes_converter(CConverter):
3437
type = 'int'
3438
converter = '_PyLong_FileDescriptor_Converter'
3439
3440
def _parse_arg(self, argname: str, displayname: str) -> str:
3441
return """
3442
{paramname} = PyObject_AsFileDescriptor({argname});
3443
if ({paramname} == -1) {{{{
3444
goto exit;
3445
}}}}
3446
""".format(argname=argname, paramname=self.name)
3447
3448
3449
class float_converter(CConverter):
3450
type = 'float'
3451
default_type = float
3452
format_unit = 'f'
3453
c_ignored_default = "0.0"
3454
3455
def parse_arg(self, argname: str, displayname: str) -> str:
3456
if self.format_unit == 'f':
3457
return """
3458
if (PyFloat_CheckExact({argname})) {{{{
3459
{paramname} = (float) (PyFloat_AS_DOUBLE({argname}));
3460
}}}}
3461
else
3462
{{{{
3463
{paramname} = (float) PyFloat_AsDouble({argname});
3464
if ({paramname} == -1.0 && PyErr_Occurred()) {{{{
3465
goto exit;
3466
}}}}
3467
}}}}
3468
""".format(argname=argname, paramname=self.parser_name)
3469
return super().parse_arg(argname, displayname)
3470
3471
class double_converter(CConverter):
3472
type = 'double'
3473
default_type = float
3474
format_unit = 'd'
3475
c_ignored_default = "0.0"
3476
3477
def parse_arg(self, argname: str, displayname: str) -> str:
3478
if self.format_unit == 'd':
3479
return """
3480
if (PyFloat_CheckExact({argname})) {{{{
3481
{paramname} = PyFloat_AS_DOUBLE({argname});
3482
}}}}
3483
else
3484
{{{{
3485
{paramname} = PyFloat_AsDouble({argname});
3486
if ({paramname} == -1.0 && PyErr_Occurred()) {{{{
3487
goto exit;
3488
}}}}
3489
}}}}
3490
""".format(argname=argname, paramname=self.parser_name)
3491
return super().parse_arg(argname, displayname)
3492
3493
3494
class Py_complex_converter(CConverter):
3495
type = 'Py_complex'
3496
default_type = complex
3497
format_unit = 'D'
3498
c_ignored_default = "{0.0, 0.0}"
3499
3500
def parse_arg(self, argname: str, displayname: str) -> str:
3501
if self.format_unit == 'D':
3502
return """
3503
{paramname} = PyComplex_AsCComplex({argname});
3504
if (PyErr_Occurred()) {{{{
3505
goto exit;
3506
}}}}
3507
""".format(argname=argname, paramname=self.parser_name)
3508
return super().parse_arg(argname, displayname)
3509
3510
3511
class object_converter(CConverter):
3512
type = 'PyObject *'
3513
format_unit = 'O'
3514
3515
def converter_init(
3516
self, *,
3517
converter=None,
3518
type=None,
3519
subclass_of=None
3520
) -> None:
3521
if converter:
3522
if subclass_of:
3523
fail("object: Cannot pass in both 'converter' and 'subclass_of'")
3524
self.format_unit = 'O&'
3525
self.converter = converter
3526
elif subclass_of:
3527
self.format_unit = 'O!'
3528
self.subclass_of = subclass_of
3529
3530
if type is not None:
3531
self.type = type
3532
3533
3534
#
3535
# We define three conventions for buffer types in the 'accept' argument:
3536
#
3537
# buffer : any object supporting the buffer interface
3538
# rwbuffer: any object supporting the buffer interface, but must be writeable
3539
# robuffer: any object supporting the buffer interface, but must not be writeable
3540
#
3541
3542
class buffer: pass
3543
class rwbuffer: pass
3544
class robuffer: pass
3545
3546
def str_converter_key(types, encoding, zeroes):
3547
return (frozenset(types), bool(encoding), bool(zeroes))
3548
3549
str_converter_argument_map: dict[str, str] = {}
3550
3551
class str_converter(CConverter):
3552
type = 'const char *'
3553
default_type = (str, Null, NoneType)
3554
format_unit = 's'
3555
3556
def converter_init(
3557
self,
3558
*,
3559
accept: TypeSet = {str},
3560
encoding: str | None = None,
3561
zeroes: bool = False
3562
) -> None:
3563
3564
key = str_converter_key(accept, encoding, zeroes)
3565
format_unit = str_converter_argument_map.get(key)
3566
if not format_unit:
3567
fail("str_converter: illegal combination of arguments", key)
3568
3569
self.format_unit = format_unit
3570
self.length = bool(zeroes)
3571
if encoding:
3572
if self.default not in (Null, None, unspecified):
3573
fail("str_converter: Argument Clinic doesn't support default values for encoded strings")
3574
self.encoding = encoding
3575
self.type = 'char *'
3576
# sorry, clinic can't support preallocated buffers
3577
# for es# and et#
3578
self.c_default = "NULL"
3579
if NoneType in accept and self.c_default == "Py_None":
3580
self.c_default = "NULL"
3581
3582
def post_parsing(self):
3583
if self.encoding:
3584
name = self.name
3585
return f"PyMem_FREE({name});\n"
3586
3587
def parse_arg(self, argname: str, displayname: str) -> str:
3588
if self.format_unit == 's':
3589
return """
3590
if (!PyUnicode_Check({argname})) {{{{
3591
_PyArg_BadArgument("{{name}}", {displayname}, "str", {argname});
3592
goto exit;
3593
}}}}
3594
Py_ssize_t {paramname}_length;
3595
{paramname} = PyUnicode_AsUTF8AndSize({argname}, &{paramname}_length);
3596
if ({paramname} == NULL) {{{{
3597
goto exit;
3598
}}}}
3599
if (strlen({paramname}) != (size_t){paramname}_length) {{{{
3600
PyErr_SetString(PyExc_ValueError, "embedded null character");
3601
goto exit;
3602
}}}}
3603
""".format(argname=argname, paramname=self.parser_name,
3604
displayname=displayname)
3605
if self.format_unit == 'z':
3606
return """
3607
if ({argname} == Py_None) {{{{
3608
{paramname} = NULL;
3609
}}}}
3610
else if (PyUnicode_Check({argname})) {{{{
3611
Py_ssize_t {paramname}_length;
3612
{paramname} = PyUnicode_AsUTF8AndSize({argname}, &{paramname}_length);
3613
if ({paramname} == NULL) {{{{
3614
goto exit;
3615
}}}}
3616
if (strlen({paramname}) != (size_t){paramname}_length) {{{{
3617
PyErr_SetString(PyExc_ValueError, "embedded null character");
3618
goto exit;
3619
}}}}
3620
}}}}
3621
else {{{{
3622
_PyArg_BadArgument("{{name}}", {displayname}, "str or None", {argname});
3623
goto exit;
3624
}}}}
3625
""".format(argname=argname, paramname=self.parser_name,
3626
displayname=displayname)
3627
return super().parse_arg(argname, displayname)
3628
3629
#
3630
# This is the fourth or fifth rewrite of registering all the
3631
# string converter format units. Previous approaches hid
3632
# bugs--generally mismatches between the semantics of the format
3633
# unit and the arguments necessary to represent those semantics
3634
# properly. Hopefully with this approach we'll get it 100% right.
3635
#
3636
# The r() function (short for "register") both registers the
3637
# mapping from arguments to format unit *and* registers the
3638
# legacy C converter for that format unit.
3639
#
3640
ConverterKeywordDict = dict[str, TypeSet | bool]
3641
3642
def r(format_unit: str,
3643
*,
3644
accept: TypeSet,
3645
encoding: bool = False,
3646
zeroes: bool = False
3647
) -> None:
3648
if not encoding and format_unit != 's':
3649
# add the legacy c converters here too.
3650
#
3651
# note: add_legacy_c_converter can't work for
3652
# es, es#, et, or et#
3653
# because of their extra encoding argument
3654
#
3655
# also don't add the converter for 's' because
3656
# the metaclass for CConverter adds it for us.
3657
kwargs: ConverterKeywordDict = {}
3658
if accept != {str}:
3659
kwargs['accept'] = accept
3660
if zeroes:
3661
kwargs['zeroes'] = True
3662
added_f = functools.partial(str_converter, **kwargs)
3663
legacy_converters[format_unit] = added_f
3664
3665
d = str_converter_argument_map
3666
key = str_converter_key(accept, encoding, zeroes)
3667
if key in d:
3668
sys.exit("Duplicate keys specified for str_converter_argument_map!")
3669
d[key] = format_unit
3670
3671
r('es', encoding=True, accept={str})
3672
r('es#', encoding=True, zeroes=True, accept={str})
3673
r('et', encoding=True, accept={bytes, bytearray, str})
3674
r('et#', encoding=True, zeroes=True, accept={bytes, bytearray, str})
3675
r('s', accept={str})
3676
r('s#', zeroes=True, accept={robuffer, str})
3677
r('y', accept={robuffer})
3678
r('y#', zeroes=True, accept={robuffer})
3679
r('z', accept={str, NoneType})
3680
r('z#', zeroes=True, accept={robuffer, str, NoneType})
3681
del r
3682
3683
3684
class PyBytesObject_converter(CConverter):
3685
type = 'PyBytesObject *'
3686
format_unit = 'S'
3687
# accept = {bytes}
3688
3689
def parse_arg(self, argname: str, displayname: str) -> str:
3690
if self.format_unit == 'S':
3691
return """
3692
if (!PyBytes_Check({argname})) {{{{
3693
_PyArg_BadArgument("{{name}}", {displayname}, "bytes", {argname});
3694
goto exit;
3695
}}}}
3696
{paramname} = ({type}){argname};
3697
""".format(argname=argname, paramname=self.parser_name,
3698
type=self.type, displayname=displayname)
3699
return super().parse_arg(argname, displayname)
3700
3701
class PyByteArrayObject_converter(CConverter):
3702
type = 'PyByteArrayObject *'
3703
format_unit = 'Y'
3704
# accept = {bytearray}
3705
3706
def parse_arg(self, argname: str, displayname: str) -> str:
3707
if self.format_unit == 'Y':
3708
return """
3709
if (!PyByteArray_Check({argname})) {{{{
3710
_PyArg_BadArgument("{{name}}", {displayname}, "bytearray", {argname});
3711
goto exit;
3712
}}}}
3713
{paramname} = ({type}){argname};
3714
""".format(argname=argname, paramname=self.parser_name,
3715
type=self.type, displayname=displayname)
3716
return super().parse_arg(argname, displayname)
3717
3718
class unicode_converter(CConverter):
3719
type = 'PyObject *'
3720
default_type = (str, Null, NoneType)
3721
format_unit = 'U'
3722
3723
def parse_arg(self, argname: str, displayname: str) -> str:
3724
if self.format_unit == 'U':
3725
return """
3726
if (!PyUnicode_Check({argname})) {{{{
3727
_PyArg_BadArgument("{{name}}", {displayname}, "str", {argname});
3728
goto exit;
3729
}}}}
3730
{paramname} = {argname};
3731
""".format(argname=argname, paramname=self.parser_name,
3732
displayname=displayname)
3733
return super().parse_arg(argname, displayname)
3734
3735
@add_legacy_c_converter('u')
3736
@add_legacy_c_converter('u#', zeroes=True)
3737
@add_legacy_c_converter('Z', accept={str, NoneType})
3738
@add_legacy_c_converter('Z#', accept={str, NoneType}, zeroes=True)
3739
class Py_UNICODE_converter(CConverter):
3740
type = 'const wchar_t *'
3741
default_type = (str, Null, NoneType)
3742
3743
def converter_init(
3744
self, *,
3745
accept: TypeSet = {str},
3746
zeroes: bool = False
3747
) -> None:
3748
format_unit = 'Z' if accept=={str, NoneType} else 'u'
3749
if zeroes:
3750
format_unit += '#'
3751
self.length = True
3752
self.format_unit = format_unit
3753
else:
3754
self.accept = accept
3755
if accept == {str}:
3756
self.converter = '_PyUnicode_WideCharString_Converter'
3757
elif accept == {str, NoneType}:
3758
self.converter = '_PyUnicode_WideCharString_Opt_Converter'
3759
else:
3760
fail("Py_UNICODE_converter: illegal 'accept' argument " + repr(accept))
3761
self.c_default = "NULL"
3762
3763
def cleanup(self):
3764
if not self.length:
3765
return """\
3766
PyMem_Free((void *){name});
3767
""".format(name=self.name)
3768
3769
def parse_arg(self, argname: str, argnum: str) -> str:
3770
if not self.length:
3771
if self.accept == {str}:
3772
return """
3773
if (!PyUnicode_Check({argname})) {{{{
3774
_PyArg_BadArgument("{{name}}", {argnum}, "str", {argname});
3775
goto exit;
3776
}}}}
3777
{paramname} = PyUnicode_AsWideCharString({argname}, NULL);
3778
if ({paramname} == NULL) {{{{
3779
goto exit;
3780
}}}}
3781
""".format(argname=argname, paramname=self.name, argnum=argnum)
3782
elif self.accept == {str, NoneType}:
3783
return """
3784
if ({argname} == Py_None) {{{{
3785
{paramname} = NULL;
3786
}}}}
3787
else if (PyUnicode_Check({argname})) {{{{
3788
{paramname} = PyUnicode_AsWideCharString({argname}, NULL);
3789
if ({paramname} == NULL) {{{{
3790
goto exit;
3791
}}}}
3792
}}}}
3793
else {{{{
3794
_PyArg_BadArgument("{{name}}", {argnum}, "str or None", {argname});
3795
goto exit;
3796
}}}}
3797
""".format(argname=argname, paramname=self.name, argnum=argnum)
3798
return super().parse_arg(argname, argnum)
3799
3800
@add_legacy_c_converter('s*', accept={str, buffer})
3801
@add_legacy_c_converter('z*', accept={str, buffer, NoneType})
3802
@add_legacy_c_converter('w*', accept={rwbuffer})
3803
class Py_buffer_converter(CConverter):
3804
type = 'Py_buffer'
3805
format_unit = 'y*'
3806
impl_by_reference = True
3807
c_ignored_default = "{NULL, NULL}"
3808
3809
def converter_init(self, *, accept: TypeSet = {buffer}) -> None:
3810
if self.default not in (unspecified, None):
3811
fail("The only legal default value for Py_buffer is None.")
3812
3813
self.c_default = self.c_ignored_default
3814
3815
if accept == {str, buffer, NoneType}:
3816
format_unit = 'z*'
3817
elif accept == {str, buffer}:
3818
format_unit = 's*'
3819
elif accept == {buffer}:
3820
format_unit = 'y*'
3821
elif accept == {rwbuffer}:
3822
format_unit = 'w*'
3823
else:
3824
fail("Py_buffer_converter: illegal combination of arguments")
3825
3826
self.format_unit = format_unit
3827
3828
def cleanup(self):
3829
name = self.name
3830
return "".join(["if (", name, ".obj) {\n PyBuffer_Release(&", name, ");\n}\n"])
3831
3832
def parse_arg(self, argname: str, displayname: str) -> str:
3833
if self.format_unit == 'y*':
3834
return """
3835
if (PyObject_GetBuffer({argname}, &{paramname}, PyBUF_SIMPLE) != 0) {{{{
3836
goto exit;
3837
}}}}
3838
if (!PyBuffer_IsContiguous(&{paramname}, 'C')) {{{{
3839
_PyArg_BadArgument("{{name}}", {displayname}, "contiguous buffer", {argname});
3840
goto exit;
3841
}}}}
3842
""".format(argname=argname, paramname=self.parser_name,
3843
displayname=displayname)
3844
elif self.format_unit == 's*':
3845
return """
3846
if (PyUnicode_Check({argname})) {{{{
3847
Py_ssize_t len;
3848
const char *ptr = PyUnicode_AsUTF8AndSize({argname}, &len);
3849
if (ptr == NULL) {{{{
3850
goto exit;
3851
}}}}
3852
PyBuffer_FillInfo(&{paramname}, {argname}, (void *)ptr, len, 1, 0);
3853
}}}}
3854
else {{{{ /* any bytes-like object */
3855
if (PyObject_GetBuffer({argname}, &{paramname}, PyBUF_SIMPLE) != 0) {{{{
3856
goto exit;
3857
}}}}
3858
if (!PyBuffer_IsContiguous(&{paramname}, 'C')) {{{{
3859
_PyArg_BadArgument("{{name}}", {displayname}, "contiguous buffer", {argname});
3860
goto exit;
3861
}}}}
3862
}}}}
3863
""".format(argname=argname, paramname=self.parser_name,
3864
displayname=displayname)
3865
elif self.format_unit == 'w*':
3866
return """
3867
if (PyObject_GetBuffer({argname}, &{paramname}, PyBUF_WRITABLE) < 0) {{{{
3868
PyErr_Clear();
3869
_PyArg_BadArgument("{{name}}", {displayname}, "read-write bytes-like object", {argname});
3870
goto exit;
3871
}}}}
3872
if (!PyBuffer_IsContiguous(&{paramname}, 'C')) {{{{
3873
_PyArg_BadArgument("{{name}}", {displayname}, "contiguous buffer", {argname});
3874
goto exit;
3875
}}}}
3876
""".format(argname=argname, paramname=self.parser_name,
3877
displayname=displayname)
3878
return super().parse_arg(argname, displayname)
3879
3880
3881
def correct_name_for_self(f) -> tuple[str, str]:
3882
if f.kind in (CALLABLE, METHOD_INIT):
3883
if f.cls:
3884
return "PyObject *", "self"
3885
return "PyObject *", "module"
3886
if f.kind == STATIC_METHOD:
3887
return "void *", "null"
3888
if f.kind in (CLASS_METHOD, METHOD_NEW):
3889
return "PyTypeObject *", "type"
3890
raise RuntimeError("Unhandled type of function f: " + repr(f.kind))
3891
3892
def required_type_for_self_for_parser(f):
3893
type, _ = correct_name_for_self(f)
3894
if f.kind in (METHOD_INIT, METHOD_NEW, STATIC_METHOD, CLASS_METHOD):
3895
return type
3896
return None
3897
3898
3899
class self_converter(CConverter):
3900
"""
3901
A special-case converter:
3902
this is the default converter used for "self".
3903
"""
3904
type = None
3905
format_unit = ''
3906
3907
def converter_init(self, *, type=None) -> None:
3908
self.specified_type = type
3909
3910
def pre_render(self):
3911
f = self.function
3912
default_type, default_name = correct_name_for_self(f)
3913
self.signature_name = default_name
3914
self.type = self.specified_type or self.type or default_type
3915
3916
kind = self.function.kind
3917
new_or_init = kind in (METHOD_NEW, METHOD_INIT)
3918
3919
if (kind == STATIC_METHOD) or new_or_init:
3920
self.show_in_signature = False
3921
3922
# tp_new (METHOD_NEW) functions are of type newfunc:
3923
# typedef PyObject *(*newfunc)(PyTypeObject *, PyObject *, PyObject *);
3924
#
3925
# tp_init (METHOD_INIT) functions are of type initproc:
3926
# typedef int (*initproc)(PyObject *, PyObject *, PyObject *);
3927
#
3928
# All other functions generated by Argument Clinic are stored in
3929
# PyMethodDef structures, in the ml_meth slot, which is of type PyCFunction:
3930
# typedef PyObject *(*PyCFunction)(PyObject *, PyObject *);
3931
# However! We habitually cast these functions to PyCFunction,
3932
# since functions that accept keyword arguments don't fit this signature
3933
# but are stored there anyway. So strict type equality isn't important
3934
# for these functions.
3935
#
3936
# So:
3937
#
3938
# * The name of the first parameter to the impl and the parsing function will always
3939
# be self.name.
3940
#
3941
# * The type of the first parameter to the impl will always be of self.type.
3942
#
3943
# * If the function is neither tp_new (METHOD_NEW) nor tp_init (METHOD_INIT):
3944
# * The type of the first parameter to the parsing function is also self.type.
3945
# This means that if you step into the parsing function, your "self" parameter
3946
# is of the correct type, which may make debugging more pleasant.
3947
#
3948
# * Else if the function is tp_new (METHOD_NEW):
3949
# * The type of the first parameter to the parsing function is "PyTypeObject *",
3950
# so the type signature of the function call is an exact match.
3951
# * If self.type != "PyTypeObject *", we cast the first parameter to self.type
3952
# in the impl call.
3953
#
3954
# * Else if the function is tp_init (METHOD_INIT):
3955
# * The type of the first parameter to the parsing function is "PyObject *",
3956
# so the type signature of the function call is an exact match.
3957
# * If self.type != "PyObject *", we cast the first parameter to self.type
3958
# in the impl call.
3959
3960
@property
3961
def parser_type(self):
3962
return required_type_for_self_for_parser(self.function) or self.type
3963
3964
def render(self, parameter, data):
3965
"""
3966
parameter is a clinic.Parameter instance.
3967
data is a CRenderData instance.
3968
"""
3969
if self.function.kind == STATIC_METHOD:
3970
return
3971
3972
self._render_self(parameter, data)
3973
3974
if self.type != self.parser_type:
3975
# insert cast to impl_argument[0], aka self.
3976
# we know we're in the first slot in all the CRenderData lists,
3977
# because we render parameters in order, and self is always first.
3978
assert len(data.impl_arguments) == 1
3979
assert data.impl_arguments[0] == self.name
3980
data.impl_arguments[0] = '(' + self.type + ")" + data.impl_arguments[0]
3981
3982
def set_template_dict(self, template_dict):
3983
template_dict['self_name'] = self.name
3984
template_dict['self_type'] = self.parser_type
3985
kind = self.function.kind
3986
cls = self.function.cls
3987
3988
if ((kind in (METHOD_NEW, METHOD_INIT)) and cls and cls.typedef):
3989
if kind == METHOD_NEW:
3990
type_check = (
3991
'({0} == base_tp || {0}->tp_init == base_tp->tp_init)'
3992
).format(self.name)
3993
else:
3994
type_check = ('(Py_IS_TYPE({0}, base_tp) ||\n '
3995
' Py_TYPE({0})->tp_new == base_tp->tp_new)'
3996
).format(self.name)
3997
3998
line = f'{type_check} &&\n '
3999
template_dict['self_type_check'] = line
4000
4001
type_object = self.function.cls.type_object
4002
type_ptr = f'PyTypeObject *base_tp = {type_object};'
4003
template_dict['base_type_ptr'] = type_ptr
4004
4005
4006
def add_c_return_converter(
4007
f: ReturnConverterType,
4008
name: str | None = None
4009
) -> ReturnConverterType:
4010
if not name:
4011
name = f.__name__
4012
if not name.endswith('_return_converter'):
4013
return f
4014
name = name.removesuffix('_return_converter')
4015
return_converters[name] = f
4016
return f
4017
4018
4019
class CReturnConverterAutoRegister(type):
4020
def __init__(
4021
cls: ReturnConverterType,
4022
name: str,
4023
bases: tuple[type, ...],
4024
classdict: dict[str, Any]
4025
) -> None:
4026
add_c_return_converter(cls)
4027
4028
4029
class CReturnConverter(metaclass=CReturnConverterAutoRegister):
4030
4031
# The C type to use for this variable.
4032
# 'type' should be a Python string specifying the type, e.g. "int".
4033
# If this is a pointer type, the type string should end with ' *'.
4034
type = 'PyObject *'
4035
4036
# The Python default value for this parameter, as a Python value.
4037
# Or the magic value "unspecified" if there is no default.
4038
default: object = None
4039
4040
def __init__(
4041
self,
4042
*,
4043
py_default: str | None = None,
4044
**kwargs
4045
) -> None:
4046
self.py_default = py_default
4047
try:
4048
self.return_converter_init(**kwargs)
4049
except TypeError as e:
4050
s = ', '.join(name + '=' + repr(value) for name, value in kwargs.items())
4051
sys.exit(self.__class__.__name__ + '(' + s + ')\n' + str(e))
4052
4053
def return_converter_init(self) -> None: ...
4054
4055
def declare(self, data: CRenderData) -> None:
4056
line: list[str] = []
4057
add = line.append
4058
add(self.type)
4059
if not self.type.endswith('*'):
4060
add(' ')
4061
add(data.converter_retval + ';')
4062
data.declarations.append(''.join(line))
4063
data.return_value = data.converter_retval
4064
4065
def err_occurred_if(
4066
self,
4067
expr: str,
4068
data: CRenderData
4069
) -> None:
4070
line = f'if (({expr}) && PyErr_Occurred()) {{\n goto exit;\n}}\n'
4071
data.return_conversion.append(line)
4072
4073
def err_occurred_if_null_pointer(
4074
self,
4075
variable: str,
4076
data: CRenderData
4077
) -> None:
4078
line = f'if ({variable} == NULL) {{\n goto exit;\n}}\n'
4079
data.return_conversion.append(line)
4080
4081
def render(
4082
self,
4083
function: Function,
4084
data: CRenderData
4085
) -> None: ...
4086
4087
4088
add_c_return_converter(CReturnConverter, 'object')
4089
4090
4091
class bool_return_converter(CReturnConverter):
4092
type = 'int'
4093
4094
def render(
4095
self,
4096
function: Function,
4097
data: CRenderData
4098
) -> None:
4099
self.declare(data)
4100
self.err_occurred_if(f"{data.converter_retval} == -1", data)
4101
data.return_conversion.append(
4102
f'return_value = PyBool_FromLong((long){data.converter_retval});\n'
4103
)
4104
4105
4106
class long_return_converter(CReturnConverter):
4107
type = 'long'
4108
conversion_fn = 'PyLong_FromLong'
4109
cast = ''
4110
unsigned_cast = ''
4111
4112
def render(
4113
self,
4114
function: Function,
4115
data: CRenderData
4116
) -> None:
4117
self.declare(data)
4118
self.err_occurred_if(f"{data.converter_retval} == {self.unsigned_cast}-1", data)
4119
data.return_conversion.append(
4120
f'return_value = {self.conversion_fn}({self.cast}{data.converter_retval});\n'
4121
)
4122
4123
4124
class int_return_converter(long_return_converter):
4125
type = 'int'
4126
cast = '(long)'
4127
4128
4129
class init_return_converter(long_return_converter):
4130
"""
4131
Special return converter for __init__ functions.
4132
"""
4133
type = 'int'
4134
cast = '(long)'
4135
4136
def render(
4137
self,
4138
function: Function,
4139
data: CRenderData
4140
) -> None: ...
4141
4142
4143
class unsigned_long_return_converter(long_return_converter):
4144
type = 'unsigned long'
4145
conversion_fn = 'PyLong_FromUnsignedLong'
4146
unsigned_cast = '(unsigned long)'
4147
4148
4149
class unsigned_int_return_converter(unsigned_long_return_converter):
4150
type = 'unsigned int'
4151
cast = '(unsigned long)'
4152
unsigned_cast = '(unsigned int)'
4153
4154
4155
class Py_ssize_t_return_converter(long_return_converter):
4156
type = 'Py_ssize_t'
4157
conversion_fn = 'PyLong_FromSsize_t'
4158
4159
4160
class size_t_return_converter(long_return_converter):
4161
type = 'size_t'
4162
conversion_fn = 'PyLong_FromSize_t'
4163
unsigned_cast = '(size_t)'
4164
4165
4166
class double_return_converter(CReturnConverter):
4167
type = 'double'
4168
cast = ''
4169
4170
def render(
4171
self,
4172
function: Function,
4173
data: CRenderData
4174
) -> None:
4175
self.declare(data)
4176
self.err_occurred_if(f"{data.converter_retval} == -1.0", data)
4177
data.return_conversion.append(
4178
f'return_value = PyFloat_FromDouble({self.cast}{data.converter_retval});\n'
4179
)
4180
4181
4182
class float_return_converter(double_return_converter):
4183
type = 'float'
4184
cast = '(double)'
4185
4186
4187
def eval_ast_expr(node, globals, *, filename='-'):
4188
"""
4189
Takes an ast.Expr node. Compiles and evaluates it.
4190
Returns the result of the expression.
4191
4192
globals represents the globals dict the expression
4193
should see. (There's no equivalent for "locals" here.)
4194
"""
4195
4196
if isinstance(node, ast.Expr):
4197
node = node.value
4198
4199
node = ast.Expression(node)
4200
co = compile(node, filename, 'eval')
4201
fn = FunctionType(co, globals)
4202
return fn()
4203
4204
4205
class IndentStack:
4206
def __init__(self):
4207
self.indents = []
4208
self.margin = None
4209
4210
def _ensure(self):
4211
if not self.indents:
4212
fail('IndentStack expected indents, but none are defined.')
4213
4214
def measure(self, line):
4215
"""
4216
Returns the length of the line's margin.
4217
"""
4218
if '\t' in line:
4219
fail('Tab characters are illegal in the Argument Clinic DSL.')
4220
stripped = line.lstrip()
4221
if not len(stripped):
4222
# we can't tell anything from an empty line
4223
# so just pretend it's indented like our current indent
4224
self._ensure()
4225
return self.indents[-1]
4226
return len(line) - len(stripped)
4227
4228
def infer(self, line):
4229
"""
4230
Infer what is now the current margin based on this line.
4231
Returns:
4232
1 if we have indented (or this is the first margin)
4233
0 if the margin has not changed
4234
-N if we have dedented N times
4235
"""
4236
indent = self.measure(line)
4237
margin = ' ' * indent
4238
if not self.indents:
4239
self.indents.append(indent)
4240
self.margin = margin
4241
return 1
4242
current = self.indents[-1]
4243
if indent == current:
4244
return 0
4245
if indent > current:
4246
self.indents.append(indent)
4247
self.margin = margin
4248
return 1
4249
# indent < current
4250
if indent not in self.indents:
4251
fail("Illegal outdent.")
4252
outdent_count = 0
4253
while indent != current:
4254
self.indents.pop()
4255
current = self.indents[-1]
4256
outdent_count -= 1
4257
self.margin = margin
4258
return outdent_count
4259
4260
@property
4261
def depth(self):
4262
"""
4263
Returns how many margins are currently defined.
4264
"""
4265
return len(self.indents)
4266
4267
def indent(self, line):
4268
"""
4269
Indents a line by the currently defined margin.
4270
"""
4271
return self.margin + line
4272
4273
def dedent(self, line):
4274
"""
4275
Dedents a line by the currently defined margin.
4276
(The inverse of 'indent'.)
4277
"""
4278
margin = self.margin
4279
indent = self.indents[-1]
4280
if not line.startswith(margin):
4281
fail('Cannot dedent, line does not start with the previous margin:')
4282
return line[indent:]
4283
4284
4285
StateKeeper = Callable[[str | None], None]
4286
4287
class DSLParser:
4288
def __init__(self, clinic: Clinic) -> None:
4289
self.clinic = clinic
4290
4291
self.directives = {}
4292
for name in dir(self):
4293
# functions that start with directive_ are added to directives
4294
_, s, key = name.partition("directive_")
4295
if s:
4296
self.directives[key] = getattr(self, name)
4297
4298
# functions that start with at_ are too, with an @ in front
4299
_, s, key = name.partition("at_")
4300
if s:
4301
self.directives['@' + key] = getattr(self, name)
4302
4303
self.reset()
4304
4305
def reset(self) -> None:
4306
self.function = None
4307
self.state: StateKeeper = self.state_dsl_start
4308
self.parameter_indent = None
4309
self.keyword_only = False
4310
self.positional_only = False
4311
self.group = 0
4312
self.parameter_state = self.ps_start
4313
self.seen_positional_with_default = False
4314
self.indent = IndentStack()
4315
self.kind = CALLABLE
4316
self.coexist = False
4317
self.parameter_continuation = ''
4318
self.preserve_output = False
4319
4320
def directive_version(self, required: str) -> None:
4321
global version
4322
if version_comparitor(version, required) < 0:
4323
fail("Insufficient Clinic version!\n Version: " + version + "\n Required: " + required)
4324
4325
def directive_module(self, name: str) -> None:
4326
fields = name.split('.')[:-1]
4327
module, cls = self.clinic._module_and_class(fields)
4328
if cls:
4329
fail("Can't nest a module inside a class!")
4330
4331
if name in module.classes:
4332
fail("Already defined module " + repr(name) + "!")
4333
4334
m = Module(name, module)
4335
module.modules[name] = m
4336
self.block.signatures.append(m)
4337
4338
def directive_class(
4339
self,
4340
name: str,
4341
typedef: str,
4342
type_object: str
4343
) -> None:
4344
fields = name.split('.')
4345
name = fields.pop()
4346
module, cls = self.clinic._module_and_class(fields)
4347
4348
parent = cls or module
4349
if name in parent.classes:
4350
fail("Already defined class " + repr(name) + "!")
4351
4352
c = Class(name, module, cls, typedef, type_object)
4353
parent.classes[name] = c
4354
self.block.signatures.append(c)
4355
4356
def directive_set(self, name: str, value: str) -> None:
4357
if name not in ("line_prefix", "line_suffix"):
4358
fail("unknown variable", repr(name))
4359
4360
value = value.format_map({
4361
'block comment start': '/*',
4362
'block comment end': '*/',
4363
})
4364
4365
self.clinic.__dict__[name] = value
4366
4367
def directive_destination(
4368
self,
4369
name: str,
4370
command: str,
4371
*args
4372
) -> None:
4373
if command == 'new':
4374
self.clinic.add_destination(name, *args)
4375
return
4376
4377
if command == 'clear':
4378
self.clinic.get_destination(name).clear()
4379
fail("unknown destination command", repr(command))
4380
4381
4382
def directive_output(
4383
self,
4384
command_or_name: str,
4385
destination: str = ''
4386
) -> None:
4387
fd = self.clinic.destination_buffers
4388
4389
if command_or_name == "preset":
4390
preset = self.clinic.presets.get(destination)
4391
if not preset:
4392
fail("Unknown preset " + repr(destination) + "!")
4393
fd.update(preset)
4394
return
4395
4396
if command_or_name == "push":
4397
self.clinic.destination_buffers_stack.append(fd.copy())
4398
return
4399
4400
if command_or_name == "pop":
4401
if not self.clinic.destination_buffers_stack:
4402
fail("Can't 'output pop', stack is empty!")
4403
previous_fd = self.clinic.destination_buffers_stack.pop()
4404
fd.update(previous_fd)
4405
return
4406
4407
# secret command for debugging!
4408
if command_or_name == "print":
4409
self.block.output.append(pprint.pformat(fd))
4410
self.block.output.append('\n')
4411
return
4412
4413
d = self.clinic.get_destination_buffer(destination)
4414
4415
if command_or_name == "everything":
4416
for name in list(fd):
4417
fd[name] = d
4418
return
4419
4420
if command_or_name not in fd:
4421
fail("Invalid command / destination name " + repr(command_or_name) + ", must be one of:\n preset push pop print everything " + " ".join(fd))
4422
fd[command_or_name] = d
4423
4424
def directive_dump(self, name: str) -> None:
4425
self.block.output.append(self.clinic.get_destination(name).dump())
4426
4427
def directive_printout(self, *args: str) -> None:
4428
self.block.output.append(' '.join(args))
4429
self.block.output.append('\n')
4430
4431
def directive_preserve(self) -> None:
4432
if self.preserve_output:
4433
fail("Can't have preserve twice in one block!")
4434
self.preserve_output = True
4435
4436
def at_classmethod(self) -> None:
4437
if self.kind is not CALLABLE:
4438
fail("Can't set @classmethod, function is not a normal callable")
4439
self.kind = CLASS_METHOD
4440
4441
def at_staticmethod(self) -> None:
4442
if self.kind is not CALLABLE:
4443
fail("Can't set @staticmethod, function is not a normal callable")
4444
self.kind = STATIC_METHOD
4445
4446
def at_coexist(self) -> None:
4447
if self.coexist:
4448
fail("Called @coexist twice!")
4449
self.coexist = True
4450
4451
def parse(self, block: Block) -> None:
4452
self.reset()
4453
self.block = block
4454
self.saved_output = self.block.output
4455
block.output = []
4456
block_start = self.clinic.block_parser.line_number
4457
lines = block.input.split('\n')
4458
for line_number, line in enumerate(lines, self.clinic.block_parser.block_start_line_number):
4459
if '\t' in line:
4460
fail('Tab characters are illegal in the Clinic DSL.\n\t' + repr(line), line_number=block_start)
4461
self.state(line)
4462
4463
self.next(self.state_terminal)
4464
self.state(None)
4465
4466
block.output.extend(self.clinic.language.render(clinic, block.signatures))
4467
4468
if self.preserve_output:
4469
if block.output:
4470
fail("'preserve' only works for blocks that don't produce any output!")
4471
block.output = self.saved_output
4472
4473
@staticmethod
4474
def ignore_line(line):
4475
# ignore comment-only lines
4476
if line.lstrip().startswith('#'):
4477
return True
4478
4479
# Ignore empty lines too
4480
# (but not in docstring sections!)
4481
if not line.strip():
4482
return True
4483
4484
return False
4485
4486
@staticmethod
4487
def calculate_indent(line: str) -> int:
4488
return len(line) - len(line.strip())
4489
4490
def next(
4491
self,
4492
state: StateKeeper,
4493
line: str | None = None
4494
) -> None:
4495
# real_print(self.state.__name__, "->", state.__name__, ", line=", line)
4496
self.state = state
4497
if line is not None:
4498
self.state(line)
4499
4500
def state_dsl_start(self, line):
4501
# self.block = self.ClinicOutputBlock(self)
4502
if self.ignore_line(line):
4503
return
4504
4505
# is it a directive?
4506
fields = shlex.split(line)
4507
directive_name = fields[0]
4508
directive = self.directives.get(directive_name, None)
4509
if directive:
4510
try:
4511
directive(*fields[1:])
4512
except TypeError as e:
4513
fail(str(e))
4514
return
4515
4516
self.next(self.state_modulename_name, line)
4517
4518
def state_modulename_name(self, line):
4519
# looking for declaration, which establishes the leftmost column
4520
# line should be
4521
# modulename.fnname [as c_basename] [-> return annotation]
4522
# square brackets denote optional syntax.
4523
#
4524
# alternatively:
4525
# modulename.fnname [as c_basename] = modulename.existing_fn_name
4526
# clones the parameters and return converter from that
4527
# function. you can't modify them. you must enter a
4528
# new docstring.
4529
#
4530
# (but we might find a directive first!)
4531
#
4532
# this line is permitted to start with whitespace.
4533
# we'll call this number of spaces F (for "function").
4534
4535
if not line.strip():
4536
return
4537
4538
self.indent.infer(line)
4539
4540
# are we cloning?
4541
before, equals, existing = line.rpartition('=')
4542
if equals:
4543
full_name, _, c_basename = before.partition(' as ')
4544
full_name = full_name.strip()
4545
c_basename = c_basename.strip()
4546
existing = existing.strip()
4547
if (is_legal_py_identifier(full_name) and
4548
(not c_basename or is_legal_c_identifier(c_basename)) and
4549
is_legal_py_identifier(existing)):
4550
# we're cloning!
4551
fields = [x.strip() for x in existing.split('.')]
4552
function_name = fields.pop()
4553
module, cls = self.clinic._module_and_class(fields)
4554
4555
for existing_function in (cls or module).functions:
4556
if existing_function.name == function_name:
4557
break
4558
else:
4559
existing_function = None
4560
if not existing_function:
4561
print("class", cls, "module", module, "existing", existing)
4562
print("cls. functions", cls.functions)
4563
fail("Couldn't find existing function " + repr(existing) + "!")
4564
4565
fields = [x.strip() for x in full_name.split('.')]
4566
function_name = fields.pop()
4567
module, cls = self.clinic._module_and_class(fields)
4568
4569
if not (existing_function.kind == self.kind and existing_function.coexist == self.coexist):
4570
fail("'kind' of function and cloned function don't match! (@classmethod/@staticmethod/@coexist)")
4571
self.function = existing_function.copy(name=function_name, full_name=full_name, module=module, cls=cls, c_basename=c_basename, docstring='')
4572
4573
self.block.signatures.append(self.function)
4574
(cls or module).functions.append(self.function)
4575
self.next(self.state_function_docstring)
4576
return
4577
4578
line, _, returns = line.partition('->')
4579
4580
full_name, _, c_basename = line.partition(' as ')
4581
full_name = full_name.strip()
4582
c_basename = c_basename.strip() or None
4583
4584
if not is_legal_py_identifier(full_name):
4585
fail("Illegal function name:", full_name)
4586
if c_basename and not is_legal_c_identifier(c_basename):
4587
fail("Illegal C basename:", c_basename)
4588
4589
return_converter = None
4590
if returns:
4591
ast_input = f"def x() -> {returns}: pass"
4592
module = None
4593
try:
4594
module = ast.parse(ast_input)
4595
except SyntaxError:
4596
pass
4597
if not module:
4598
fail("Badly-formed annotation for " + full_name + ": " + returns)
4599
try:
4600
name, legacy, kwargs = self.parse_converter(module.body[0].returns)
4601
if legacy:
4602
fail("Legacy converter {!r} not allowed as a return converter"
4603
.format(name))
4604
if name not in return_converters:
4605
fail("No available return converter called " + repr(name))
4606
return_converter = return_converters[name](**kwargs)
4607
except ValueError:
4608
fail("Badly-formed annotation for " + full_name + ": " + returns)
4609
4610
fields = [x.strip() for x in full_name.split('.')]
4611
function_name = fields.pop()
4612
module, cls = self.clinic._module_and_class(fields)
4613
4614
fields = full_name.split('.')
4615
if fields[-1] in unsupported_special_methods:
4616
fail(f"{fields[-1]} is a special method and cannot be converted to Argument Clinic! (Yet.)")
4617
4618
if fields[-1] == '__new__':
4619
if (self.kind != CLASS_METHOD) or (not cls):
4620
fail("__new__ must be a class method!")
4621
self.kind = METHOD_NEW
4622
elif fields[-1] == '__init__':
4623
if (self.kind != CALLABLE) or (not cls):
4624
fail("__init__ must be a normal method, not a class or static method!")
4625
self.kind = METHOD_INIT
4626
if not return_converter:
4627
return_converter = init_return_converter()
4628
4629
if not return_converter:
4630
return_converter = CReturnConverter()
4631
4632
if not module:
4633
fail("Undefined module used in declaration of " + repr(full_name.strip()) + ".")
4634
self.function = Function(name=function_name, full_name=full_name, module=module, cls=cls, c_basename=c_basename,
4635
return_converter=return_converter, kind=self.kind, coexist=self.coexist)
4636
self.block.signatures.append(self.function)
4637
4638
# insert a self converter automatically
4639
type, name = correct_name_for_self(self.function)
4640
kwargs = {}
4641
if cls and type == "PyObject *":
4642
kwargs['type'] = cls.typedef
4643
sc = self.function.self_converter = self_converter(name, name, self.function, **kwargs)
4644
p_self = Parameter(sc.name, inspect.Parameter.POSITIONAL_ONLY, function=self.function, converter=sc)
4645
self.function.parameters[sc.name] = p_self
4646
4647
(cls or module).functions.append(self.function)
4648
self.next(self.state_parameters_start)
4649
4650
# Now entering the parameters section. The rules, formally stated:
4651
#
4652
# * All lines must be indented with spaces only.
4653
# * The first line must be a parameter declaration.
4654
# * The first line must be indented.
4655
# * This first line establishes the indent for parameters.
4656
# * We'll call this number of spaces P (for "parameter").
4657
# * Thenceforth:
4658
# * Lines indented with P spaces specify a parameter.
4659
# * Lines indented with > P spaces are docstrings for the previous
4660
# parameter.
4661
# * We'll call this number of spaces D (for "docstring").
4662
# * All subsequent lines indented with >= D spaces are stored as
4663
# part of the per-parameter docstring.
4664
# * All lines will have the first D spaces of the indent stripped
4665
# before they are stored.
4666
# * It's illegal to have a line starting with a number of spaces X
4667
# such that P < X < D.
4668
# * A line with < P spaces is the first line of the function
4669
# docstring, which ends processing for parameters and per-parameter
4670
# docstrings.
4671
# * The first line of the function docstring must be at the same
4672
# indent as the function declaration.
4673
# * It's illegal to have any line in the parameters section starting
4674
# with X spaces such that F < X < P. (As before, F is the indent
4675
# of the function declaration.)
4676
#
4677
# Also, currently Argument Clinic places the following restrictions on groups:
4678
# * Each group must contain at least one parameter.
4679
# * Each group may contain at most one group, which must be the furthest
4680
# thing in the group from the required parameters. (The nested group
4681
# must be the first in the group when it's before the required
4682
# parameters, and the last thing in the group when after the required
4683
# parameters.)
4684
# * There may be at most one (top-level) group to the left or right of
4685
# the required parameters.
4686
# * You must specify a slash, and it must be after all parameters.
4687
# (In other words: either all parameters are positional-only,
4688
# or none are.)
4689
#
4690
# Said another way:
4691
# * Each group must contain at least one parameter.
4692
# * All left square brackets before the required parameters must be
4693
# consecutive. (You can't have a left square bracket followed
4694
# by a parameter, then another left square bracket. You can't
4695
# have a left square bracket, a parameter, a right square bracket,
4696
# and then a left square bracket.)
4697
# * All right square brackets after the required parameters must be
4698
# consecutive.
4699
#
4700
# These rules are enforced with a single state variable:
4701
# "parameter_state". (Previously the code was a miasma of ifs and
4702
# separate boolean state variables.) The states are:
4703
#
4704
# [ [ a, b, ] c, ] d, e, f=3, [ g, h, [ i ] ] <- line
4705
# 01 2 3 4 5 6 <- state transitions
4706
#
4707
# 0: ps_start. before we've seen anything. legal transitions are to 1 or 3.
4708
# 1: ps_left_square_before. left square brackets before required parameters.
4709
# 2: ps_group_before. in a group, before required parameters.
4710
# 3: ps_required. required parameters, positional-or-keyword or positional-only
4711
# (we don't know yet). (renumber left groups!)
4712
# 4: ps_optional. positional-or-keyword or positional-only parameters that
4713
# now must have default values.
4714
# 5: ps_group_after. in a group, after required parameters.
4715
# 6: ps_right_square_after. right square brackets after required parameters.
4716
ps_start, ps_left_square_before, ps_group_before, ps_required, \
4717
ps_optional, ps_group_after, ps_right_square_after = range(7)
4718
4719
def state_parameters_start(self, line):
4720
if self.ignore_line(line):
4721
return
4722
4723
# if this line is not indented, we have no parameters
4724
if not self.indent.infer(line):
4725
return self.next(self.state_function_docstring, line)
4726
4727
self.parameter_continuation = ''
4728
return self.next(self.state_parameter, line)
4729
4730
4731
def to_required(self):
4732
"""
4733
Transition to the "required" parameter state.
4734
"""
4735
if self.parameter_state != self.ps_required:
4736
self.parameter_state = self.ps_required
4737
for p in self.function.parameters.values():
4738
p.group = -p.group
4739
4740
def state_parameter(self, line):
4741
if self.parameter_continuation:
4742
line = self.parameter_continuation + ' ' + line.lstrip()
4743
self.parameter_continuation = ''
4744
4745
if self.ignore_line(line):
4746
return
4747
4748
assert self.indent.depth == 2
4749
indent = self.indent.infer(line)
4750
if indent == -1:
4751
# we outdented, must be to definition column
4752
return self.next(self.state_function_docstring, line)
4753
4754
if indent == 1:
4755
# we indented, must be to new parameter docstring column
4756
return self.next(self.state_parameter_docstring_start, line)
4757
4758
line = line.rstrip()
4759
if line.endswith('\\'):
4760
self.parameter_continuation = line[:-1]
4761
return
4762
4763
line = line.lstrip()
4764
4765
if line in ('*', '/', '[', ']'):
4766
self.parse_special_symbol(line)
4767
return
4768
4769
if self.parameter_state in (self.ps_start, self.ps_required):
4770
self.to_required()
4771
elif self.parameter_state == self.ps_left_square_before:
4772
self.parameter_state = self.ps_group_before
4773
elif self.parameter_state == self.ps_group_before:
4774
if not self.group:
4775
self.to_required()
4776
elif self.parameter_state in (self.ps_group_after, self.ps_optional):
4777
pass
4778
else:
4779
fail("Function " + self.function.name + " has an unsupported group configuration. (Unexpected state " + str(self.parameter_state) + ".a)")
4780
4781
# handle "as" for parameters too
4782
c_name = None
4783
name, have_as_token, trailing = line.partition(' as ')
4784
if have_as_token:
4785
name = name.strip()
4786
if ' ' not in name:
4787
fields = trailing.strip().split(' ')
4788
if not fields:
4789
fail("Invalid 'as' clause!")
4790
c_name = fields[0]
4791
if c_name.endswith(':'):
4792
name += ':'
4793
c_name = c_name[:-1]
4794
fields[0] = name
4795
line = ' '.join(fields)
4796
4797
base, equals, default = line.rpartition('=')
4798
if not equals:
4799
base = default
4800
default = None
4801
4802
module = None
4803
try:
4804
ast_input = f"def x({base}): pass"
4805
module = ast.parse(ast_input)
4806
except SyntaxError:
4807
try:
4808
# the last = was probably inside a function call, like
4809
# c: int(accept={str})
4810
# so assume there was no actual default value.
4811
default = None
4812
ast_input = f"def x({line}): pass"
4813
module = ast.parse(ast_input)
4814
except SyntaxError:
4815
pass
4816
if not module:
4817
fail("Function " + self.function.name + " has an invalid parameter declaration:\n\t" + line)
4818
4819
function_args = module.body[0].args
4820
4821
if len(function_args.args) > 1:
4822
fail("Function " + self.function.name + " has an invalid parameter declaration (comma?):\n\t" + line)
4823
if function_args.defaults or function_args.kw_defaults:
4824
fail("Function " + self.function.name + " has an invalid parameter declaration (default value?):\n\t" + line)
4825
if function_args.kwarg:
4826
fail("Function " + self.function.name + " has an invalid parameter declaration (**kwargs?):\n\t" + line)
4827
4828
if function_args.vararg:
4829
is_vararg = True
4830
parameter = function_args.vararg
4831
else:
4832
is_vararg = False
4833
parameter = function_args.args[0]
4834
4835
parameter_name = parameter.arg
4836
name, legacy, kwargs = self.parse_converter(parameter.annotation)
4837
4838
if not default:
4839
if self.parameter_state == self.ps_optional:
4840
fail("Can't have a parameter without a default (" + repr(parameter_name) + ")\nafter a parameter with a default!")
4841
if is_vararg:
4842
value = NULL
4843
kwargs.setdefault('c_default', "NULL")
4844
else:
4845
value = unspecified
4846
if 'py_default' in kwargs:
4847
fail("You can't specify py_default without specifying a default value!")
4848
else:
4849
if is_vararg:
4850
fail("Vararg can't take a default value!")
4851
4852
if self.parameter_state == self.ps_required:
4853
self.parameter_state = self.ps_optional
4854
default = default.strip()
4855
bad = False
4856
ast_input = f"x = {default}"
4857
try:
4858
module = ast.parse(ast_input)
4859
4860
if 'c_default' not in kwargs:
4861
# we can only represent very simple data values in C.
4862
# detect whether default is okay, via a denylist
4863
# of disallowed ast nodes.
4864
class DetectBadNodes(ast.NodeVisitor):
4865
bad = False
4866
def bad_node(self, node):
4867
self.bad = True
4868
4869
# inline function call
4870
visit_Call = bad_node
4871
# inline if statement ("x = 3 if y else z")
4872
visit_IfExp = bad_node
4873
4874
# comprehensions and generator expressions
4875
visit_ListComp = visit_SetComp = bad_node
4876
visit_DictComp = visit_GeneratorExp = bad_node
4877
4878
# literals for advanced types
4879
visit_Dict = visit_Set = bad_node
4880
visit_List = visit_Tuple = bad_node
4881
4882
# "starred": "a = [1, 2, 3]; *a"
4883
visit_Starred = bad_node
4884
4885
denylist = DetectBadNodes()
4886
denylist.visit(module)
4887
bad = denylist.bad
4888
else:
4889
# if they specify a c_default, we can be more lenient about the default value.
4890
# but at least make an attempt at ensuring it's a valid expression.
4891
try:
4892
value = eval(default)
4893
if value is unspecified:
4894
fail("'unspecified' is not a legal default value!")
4895
except NameError:
4896
pass # probably a named constant
4897
except Exception as e:
4898
fail("Malformed expression given as default value\n"
4899
"{!r} caused {!r}".format(default, e))
4900
if bad:
4901
fail("Unsupported expression as default value: " + repr(default))
4902
4903
expr = module.body[0].value
4904
# mild hack: explicitly support NULL as a default value
4905
if isinstance(expr, ast.Name) and expr.id == 'NULL':
4906
value = NULL
4907
py_default = '<unrepresentable>'
4908
c_default = "NULL"
4909
elif (isinstance(expr, ast.BinOp) or
4910
(isinstance(expr, ast.UnaryOp) and
4911
not (isinstance(expr.operand, ast.Constant) and
4912
type(expr.operand.value) in {int, float, complex})
4913
)):
4914
c_default = kwargs.get("c_default")
4915
if not (isinstance(c_default, str) and c_default):
4916
fail("When you specify an expression (" + repr(default) + ") as your default value,\nyou MUST specify a valid c_default." + ast.dump(expr))
4917
py_default = default
4918
value = unknown
4919
elif isinstance(expr, ast.Attribute):
4920
a = []
4921
n = expr
4922
while isinstance(n, ast.Attribute):
4923
a.append(n.attr)
4924
n = n.value
4925
if not isinstance(n, ast.Name):
4926
fail("Unsupported default value " + repr(default) + " (looked like a Python constant)")
4927
a.append(n.id)
4928
py_default = ".".join(reversed(a))
4929
4930
c_default = kwargs.get("c_default")
4931
if not (isinstance(c_default, str) and c_default):
4932
fail("When you specify a named constant (" + repr(py_default) + ") as your default value,\nyou MUST specify a valid c_default.")
4933
4934
try:
4935
value = eval(py_default)
4936
except NameError:
4937
value = unknown
4938
else:
4939
value = ast.literal_eval(expr)
4940
py_default = repr(value)
4941
if isinstance(value, (bool, None.__class__)):
4942
c_default = "Py_" + py_default
4943
elif isinstance(value, str):
4944
c_default = c_repr(value)
4945
else:
4946
c_default = py_default
4947
4948
except SyntaxError as e:
4949
fail("Syntax error: " + repr(e.text))
4950
except (ValueError, AttributeError):
4951
value = unknown
4952
c_default = kwargs.get("c_default")
4953
py_default = default
4954
if not (isinstance(c_default, str) and c_default):
4955
fail("When you specify a named constant (" + repr(py_default) + ") as your default value,\nyou MUST specify a valid c_default.")
4956
4957
kwargs.setdefault('c_default', c_default)
4958
kwargs.setdefault('py_default', py_default)
4959
4960
dict = legacy_converters if legacy else converters
4961
legacy_str = "legacy " if legacy else ""
4962
if name not in dict:
4963
fail(f'{name} is not a valid {legacy_str}converter')
4964
# if you use a c_name for the parameter, we just give that name to the converter
4965
# but the parameter object gets the python name
4966
converter = dict[name](c_name or parameter_name, parameter_name, self.function, value, **kwargs)
4967
4968
if is_vararg:
4969
kind = inspect.Parameter.VAR_POSITIONAL
4970
elif self.keyword_only:
4971
kind = inspect.Parameter.KEYWORD_ONLY
4972
else:
4973
kind = inspect.Parameter.POSITIONAL_OR_KEYWORD
4974
4975
if isinstance(converter, self_converter):
4976
if len(self.function.parameters) == 1:
4977
if (self.parameter_state != self.ps_required):
4978
fail("A 'self' parameter cannot be marked optional.")
4979
if value is not unspecified:
4980
fail("A 'self' parameter cannot have a default value.")
4981
if self.group:
4982
fail("A 'self' parameter cannot be in an optional group.")
4983
kind = inspect.Parameter.POSITIONAL_ONLY
4984
self.parameter_state = self.ps_start
4985
self.function.parameters.clear()
4986
else:
4987
fail("A 'self' parameter, if specified, must be the very first thing in the parameter block.")
4988
4989
if isinstance(converter, defining_class_converter):
4990
_lp = len(self.function.parameters)
4991
if _lp == 1:
4992
if (self.parameter_state != self.ps_required):
4993
fail("A 'defining_class' parameter cannot be marked optional.")
4994
if value is not unspecified:
4995
fail("A 'defining_class' parameter cannot have a default value.")
4996
if self.group:
4997
fail("A 'defining_class' parameter cannot be in an optional group.")
4998
else:
4999
fail("A 'defining_class' parameter, if specified, must either be the first thing in the parameter block, or come just after 'self'.")
5000
5001
5002
p = Parameter(parameter_name, kind, function=self.function, converter=converter, default=value, group=self.group)
5003
5004
names = [k.name for k in self.function.parameters.values()]
5005
if parameter_name in names[1:]:
5006
fail("You can't have two parameters named " + repr(parameter_name) + "!")
5007
elif names and parameter_name == names[0] and c_name is None:
5008
fail(f"Parameter '{parameter_name}' requires a custom C name")
5009
5010
key = f"{parameter_name}_as_{c_name}" if c_name else parameter_name
5011
self.function.parameters[key] = p
5012
5013
KwargDict = dict[str | None, Any]
5014
5015
@staticmethod
5016
def parse_converter(annotation: ast.expr | None) -> tuple[str, bool, KwargDict]:
5017
match annotation:
5018
case ast.Constant(value=str() as value):
5019
return value, True, {}
5020
case ast.Name(name):
5021
return name, False, {}
5022
case ast.Call(func=ast.Name(name)):
5023
symbols = globals()
5024
kwargs = {
5025
node.arg: eval_ast_expr(node.value, symbols)
5026
for node in annotation.keywords
5027
}
5028
return name, False, kwargs
5029
case _:
5030
fail(
5031
"Annotations must be either a name, a function call, or a string."
5032
)
5033
5034
def parse_special_symbol(self, symbol):
5035
if symbol == '*':
5036
if self.keyword_only:
5037
fail("Function " + self.function.name + " uses '*' more than once.")
5038
self.keyword_only = True
5039
elif symbol == '[':
5040
if self.parameter_state in (self.ps_start, self.ps_left_square_before):
5041
self.parameter_state = self.ps_left_square_before
5042
elif self.parameter_state in (self.ps_required, self.ps_group_after):
5043
self.parameter_state = self.ps_group_after
5044
else:
5045
fail("Function " + self.function.name + " has an unsupported group configuration. (Unexpected state " + str(self.parameter_state) + ".b)")
5046
self.group += 1
5047
self.function.docstring_only = True
5048
elif symbol == ']':
5049
if not self.group:
5050
fail("Function " + self.function.name + " has a ] without a matching [.")
5051
if not any(p.group == self.group for p in self.function.parameters.values()):
5052
fail("Function " + self.function.name + " has an empty group.\nAll groups must contain at least one parameter.")
5053
self.group -= 1
5054
if self.parameter_state in (self.ps_left_square_before, self.ps_group_before):
5055
self.parameter_state = self.ps_group_before
5056
elif self.parameter_state in (self.ps_group_after, self.ps_right_square_after):
5057
self.parameter_state = self.ps_right_square_after
5058
else:
5059
fail("Function " + self.function.name + " has an unsupported group configuration. (Unexpected state " + str(self.parameter_state) + ".c)")
5060
elif symbol == '/':
5061
if self.positional_only:
5062
fail("Function " + self.function.name + " uses '/' more than once.")
5063
self.positional_only = True
5064
# ps_required and ps_optional are allowed here, that allows positional-only without option groups
5065
# to work (and have default values!)
5066
if (self.parameter_state not in (self.ps_required, self.ps_optional, self.ps_right_square_after, self.ps_group_before)) or self.group:
5067
fail("Function " + self.function.name + " has an unsupported group configuration. (Unexpected state " + str(self.parameter_state) + ".d)")
5068
if self.keyword_only:
5069
fail("Function " + self.function.name + " mixes keyword-only and positional-only parameters, which is unsupported.")
5070
# fixup preceding parameters
5071
for p in self.function.parameters.values():
5072
if p.is_vararg():
5073
continue
5074
if (p.kind != inspect.Parameter.POSITIONAL_OR_KEYWORD and not isinstance(p.converter, self_converter)):
5075
fail("Function " + self.function.name + " mixes keyword-only and positional-only parameters, which is unsupported.")
5076
p.kind = inspect.Parameter.POSITIONAL_ONLY
5077
5078
def state_parameter_docstring_start(self, line):
5079
self.parameter_docstring_indent = len(self.indent.margin)
5080
assert self.indent.depth == 3
5081
return self.next(self.state_parameter_docstring, line)
5082
5083
# every line of the docstring must start with at least F spaces,
5084
# where F > P.
5085
# these F spaces will be stripped.
5086
def state_parameter_docstring(self, line):
5087
stripped = line.strip()
5088
if stripped.startswith('#'):
5089
return
5090
5091
indent = self.indent.measure(line)
5092
if indent < self.parameter_docstring_indent:
5093
self.indent.infer(line)
5094
assert self.indent.depth < 3
5095
if self.indent.depth == 2:
5096
# back to a parameter
5097
return self.next(self.state_parameter, line)
5098
assert self.indent.depth == 1
5099
return self.next(self.state_function_docstring, line)
5100
5101
assert self.function.parameters
5102
last_parameter = next(reversed(list(self.function.parameters.values())))
5103
5104
new_docstring = last_parameter.docstring
5105
5106
if new_docstring:
5107
new_docstring += '\n'
5108
if stripped:
5109
new_docstring += self.indent.dedent(line)
5110
5111
last_parameter.docstring = new_docstring
5112
5113
# the final stanza of the DSL is the docstring.
5114
def state_function_docstring(self, line):
5115
if self.group:
5116
fail("Function " + self.function.name + " has a ] without a matching [.")
5117
5118
stripped = line.strip()
5119
if stripped.startswith('#'):
5120
return
5121
5122
new_docstring = self.function.docstring
5123
if new_docstring:
5124
new_docstring += "\n"
5125
if stripped:
5126
line = self.indent.dedent(line).rstrip()
5127
else:
5128
line = ''
5129
new_docstring += line
5130
self.function.docstring = new_docstring
5131
5132
def format_docstring(self):
5133
f = self.function
5134
5135
new_or_init = f.kind in (METHOD_NEW, METHOD_INIT)
5136
if new_or_init and not f.docstring:
5137
# don't render a docstring at all, no signature, nothing.
5138
return f.docstring
5139
5140
text, add, output = _text_accumulator()
5141
parameters = f.render_parameters
5142
5143
##
5144
## docstring first line
5145
##
5146
5147
if new_or_init:
5148
# classes get *just* the name of the class
5149
# not __new__, not __init__, and not module.classname
5150
assert f.cls
5151
add(f.cls.name)
5152
else:
5153
add(f.name)
5154
add('(')
5155
5156
# populate "right_bracket_count" field for every parameter
5157
assert parameters, "We should always have a self parameter. " + repr(f)
5158
assert isinstance(parameters[0].converter, self_converter)
5159
# self is always positional-only.
5160
assert parameters[0].is_positional_only()
5161
parameters[0].right_bracket_count = 0
5162
positional_only = True
5163
for p in parameters[1:]:
5164
if not p.is_positional_only():
5165
positional_only = False
5166
else:
5167
assert positional_only
5168
if positional_only:
5169
p.right_bracket_count = abs(p.group)
5170
else:
5171
# don't put any right brackets around non-positional-only parameters, ever.
5172
p.right_bracket_count = 0
5173
5174
right_bracket_count = 0
5175
5176
def fix_right_bracket_count(desired):
5177
nonlocal right_bracket_count
5178
s = ''
5179
while right_bracket_count < desired:
5180
s += '['
5181
right_bracket_count += 1
5182
while right_bracket_count > desired:
5183
s += ']'
5184
right_bracket_count -= 1
5185
return s
5186
5187
need_slash = False
5188
added_slash = False
5189
need_a_trailing_slash = False
5190
5191
# we only need a trailing slash:
5192
# * if this is not a "docstring_only" signature
5193
# * and if the last *shown* parameter is
5194
# positional only
5195
if not f.docstring_only:
5196
for p in reversed(parameters):
5197
if not p.converter.show_in_signature:
5198
continue
5199
if p.is_positional_only():
5200
need_a_trailing_slash = True
5201
break
5202
5203
5204
added_star = False
5205
5206
first_parameter = True
5207
last_p = parameters[-1]
5208
line_length = len(''.join(text))
5209
indent = " " * line_length
5210
def add_parameter(text):
5211
nonlocal line_length
5212
nonlocal first_parameter
5213
if first_parameter:
5214
s = text
5215
first_parameter = False
5216
else:
5217
s = ' ' + text
5218
if line_length + len(s) >= 72:
5219
add('\n')
5220
add(indent)
5221
line_length = len(indent)
5222
s = text
5223
line_length += len(s)
5224
add(s)
5225
5226
for p in parameters:
5227
if not p.converter.show_in_signature:
5228
continue
5229
assert p.name
5230
5231
is_self = isinstance(p.converter, self_converter)
5232
if is_self and f.docstring_only:
5233
# this isn't a real machine-parsable signature,
5234
# so let's not print the "self" parameter
5235
continue
5236
5237
if p.is_positional_only():
5238
need_slash = not f.docstring_only
5239
elif need_slash and not (added_slash or p.is_positional_only()):
5240
added_slash = True
5241
add_parameter('/,')
5242
5243
if p.is_keyword_only() and not added_star:
5244
added_star = True
5245
add_parameter('*,')
5246
5247
p_add, p_output = text_accumulator()
5248
p_add(fix_right_bracket_count(p.right_bracket_count))
5249
5250
if isinstance(p.converter, self_converter):
5251
# annotate first parameter as being a "self".
5252
#
5253
# if inspect.Signature gets this function,
5254
# and it's already bound, the self parameter
5255
# will be stripped off.
5256
#
5257
# if it's not bound, it should be marked
5258
# as positional-only.
5259
#
5260
# note: we don't print "self" for __init__,
5261
# because this isn't actually the signature
5262
# for __init__. (it can't be, __init__ doesn't
5263
# have a docstring.) if this is an __init__
5264
# (or __new__), then this signature is for
5265
# calling the class to construct a new instance.
5266
p_add('$')
5267
5268
if p.is_vararg():
5269
p_add("*")
5270
5271
name = p.converter.signature_name or p.name
5272
p_add(name)
5273
5274
if not p.is_vararg() and p.converter.is_optional():
5275
p_add('=')
5276
value = p.converter.py_default
5277
if not value:
5278
value = repr(p.converter.default)
5279
p_add(value)
5280
5281
if (p != last_p) or need_a_trailing_slash:
5282
p_add(',')
5283
5284
add_parameter(p_output())
5285
5286
add(fix_right_bracket_count(0))
5287
if need_a_trailing_slash:
5288
add_parameter('/')
5289
add(')')
5290
5291
# PEP 8 says:
5292
#
5293
# The Python standard library will not use function annotations
5294
# as that would result in a premature commitment to a particular
5295
# annotation style. Instead, the annotations are left for users
5296
# to discover and experiment with useful annotation styles.
5297
#
5298
# therefore this is commented out:
5299
#
5300
# if f.return_converter.py_default:
5301
# add(' -> ')
5302
# add(f.return_converter.py_default)
5303
5304
if not f.docstring_only:
5305
add("\n" + sig_end_marker + "\n")
5306
5307
docstring_first_line = output()
5308
5309
# now fix up the places where the brackets look wrong
5310
docstring_first_line = docstring_first_line.replace(', ]', ',] ')
5311
5312
# okay. now we're officially building the "parameters" section.
5313
# create substitution text for {parameters}
5314
spacer_line = False
5315
for p in parameters:
5316
if not p.docstring.strip():
5317
continue
5318
if spacer_line:
5319
add('\n')
5320
else:
5321
spacer_line = True
5322
add(" ")
5323
add(p.name)
5324
add('\n')
5325
add(textwrap.indent(rstrip_lines(p.docstring.rstrip()), " "))
5326
parameters = output()
5327
if parameters:
5328
parameters += '\n'
5329
5330
##
5331
## docstring body
5332
##
5333
5334
docstring = f.docstring.rstrip()
5335
lines = [line.rstrip() for line in docstring.split('\n')]
5336
5337
# Enforce the summary line!
5338
# The first line of a docstring should be a summary of the function.
5339
# It should fit on one line (80 columns? 79 maybe?) and be a paragraph
5340
# by itself.
5341
#
5342
# Argument Clinic enforces the following rule:
5343
# * either the docstring is empty,
5344
# * or it must have a summary line.
5345
#
5346
# Guido said Clinic should enforce this:
5347
# http://mail.python.org/pipermail/python-dev/2013-June/127110.html
5348
5349
if len(lines) >= 2:
5350
if lines[1]:
5351
fail("Docstring for " + f.full_name + " does not have a summary line!\n" +
5352
"Every non-blank function docstring must start with\n" +
5353
"a single line summary followed by an empty line.")
5354
elif len(lines) == 1:
5355
# the docstring is only one line right now--the summary line.
5356
# add an empty line after the summary line so we have space
5357
# between it and the {parameters} we're about to add.
5358
lines.append('')
5359
5360
parameters_marker_count = len(docstring.split('{parameters}')) - 1
5361
if parameters_marker_count > 1:
5362
fail('You may not specify {parameters} more than once in a docstring!')
5363
5364
if not parameters_marker_count:
5365
# insert after summary line
5366
lines.insert(2, '{parameters}')
5367
5368
# insert at front of docstring
5369
lines.insert(0, docstring_first_line)
5370
5371
docstring = "\n".join(lines)
5372
5373
add(docstring)
5374
docstring = output()
5375
5376
docstring = linear_format(docstring, parameters=parameters)
5377
docstring = docstring.rstrip()
5378
5379
return docstring
5380
5381
def state_terminal(self, line):
5382
"""
5383
Called when processing the block is done.
5384
"""
5385
assert not line
5386
5387
if not self.function:
5388
return
5389
5390
if self.keyword_only:
5391
values = self.function.parameters.values()
5392
if not values:
5393
no_parameter_after_star = True
5394
else:
5395
last_parameter = next(reversed(list(values)))
5396
no_parameter_after_star = last_parameter.kind != inspect.Parameter.KEYWORD_ONLY
5397
if no_parameter_after_star:
5398
fail("Function " + self.function.name + " specifies '*' without any parameters afterwards.")
5399
5400
# remove trailing whitespace from all parameter docstrings
5401
for name, value in self.function.parameters.items():
5402
if not value:
5403
continue
5404
value.docstring = value.docstring.rstrip()
5405
5406
self.function.docstring = self.format_docstring()
5407
5408
5409
5410
5411
# maps strings to callables.
5412
# the callable should return an object
5413
# that implements the clinic parser
5414
# interface (__init__ and parse).
5415
#
5416
# example parsers:
5417
# "clinic", handles the Clinic DSL
5418
# "python", handles running Python code
5419
#
5420
parsers = {'clinic' : DSLParser, 'python': PythonParser}
5421
5422
5423
clinic = None
5424
5425
5426
def main(argv):
5427
import sys
5428
import argparse
5429
cmdline = argparse.ArgumentParser(
5430
description="""Preprocessor for CPython C files.
5431
5432
The purpose of the Argument Clinic is automating all the boilerplate involved
5433
with writing argument parsing code for builtins and providing introspection
5434
signatures ("docstrings") for CPython builtins.
5435
5436
For more information see https://docs.python.org/3/howto/clinic.html""")
5437
cmdline.add_argument("-f", "--force", action='store_true')
5438
cmdline.add_argument("-o", "--output", type=str)
5439
cmdline.add_argument("-v", "--verbose", action='store_true')
5440
cmdline.add_argument("--converters", action='store_true')
5441
cmdline.add_argument("--make", action='store_true',
5442
help="Walk --srcdir to run over all relevant files.")
5443
cmdline.add_argument("--srcdir", type=str, default=os.curdir,
5444
help="The directory tree to walk in --make mode.")
5445
cmdline.add_argument("filename", type=str, nargs="*")
5446
ns = cmdline.parse_args(argv)
5447
5448
if ns.converters:
5449
if ns.filename:
5450
print("Usage error: can't specify --converters and a filename at the same time.")
5451
print()
5452
cmdline.print_usage()
5453
sys.exit(-1)
5454
converters = []
5455
return_converters = []
5456
ignored = set("""
5457
add_c_converter
5458
add_c_return_converter
5459
add_default_legacy_c_converter
5460
add_legacy_c_converter
5461
""".strip().split())
5462
module = globals()
5463
for name in module:
5464
for suffix, ids in (
5465
("_return_converter", return_converters),
5466
("_converter", converters),
5467
):
5468
if name in ignored:
5469
continue
5470
if name.endswith(suffix):
5471
ids.append((name, name.removesuffix(suffix)))
5472
break
5473
print()
5474
5475
print("Legacy converters:")
5476
legacy = sorted(legacy_converters)
5477
print(' ' + ' '.join(c for c in legacy if c[0].isupper()))
5478
print(' ' + ' '.join(c for c in legacy if c[0].islower()))
5479
print()
5480
5481
for title, attribute, ids in (
5482
("Converters", 'converter_init', converters),
5483
("Return converters", 'return_converter_init', return_converters),
5484
):
5485
print(title + ":")
5486
longest = -1
5487
for name, short_name in ids:
5488
longest = max(longest, len(short_name))
5489
for name, short_name in sorted(ids, key=lambda x: x[1].lower()):
5490
cls = module[name]
5491
callable = getattr(cls, attribute, None)
5492
if not callable:
5493
continue
5494
signature = inspect.signature(callable)
5495
parameters = []
5496
for parameter_name, parameter in signature.parameters.items():
5497
if parameter.kind == inspect.Parameter.KEYWORD_ONLY:
5498
if parameter.default != inspect.Parameter.empty:
5499
s = f'{parameter_name}={parameter.default!r}'
5500
else:
5501
s = parameter_name
5502
parameters.append(s)
5503
print(' {}({})'.format(short_name, ', '.join(parameters)))
5504
print()
5505
print("All converters also accept (c_default=None, py_default=None, annotation=None).")
5506
print("All return converters also accept (py_default=None).")
5507
sys.exit(0)
5508
5509
if ns.make:
5510
if ns.output or ns.filename:
5511
print("Usage error: can't use -o or filenames with --make.")
5512
print()
5513
cmdline.print_usage()
5514
sys.exit(-1)
5515
if not ns.srcdir:
5516
print("Usage error: --srcdir must not be empty with --make.")
5517
print()
5518
cmdline.print_usage()
5519
sys.exit(-1)
5520
for root, dirs, files in os.walk(ns.srcdir):
5521
for rcs_dir in ('.svn', '.git', '.hg', 'build', 'externals'):
5522
if rcs_dir in dirs:
5523
dirs.remove(rcs_dir)
5524
for filename in files:
5525
# handle .c, .cpp and .h files
5526
if not filename.endswith(('.c', '.cpp', '.h')):
5527
continue
5528
path = os.path.join(root, filename)
5529
if ns.verbose:
5530
print(path)
5531
parse_file(path, verify=not ns.force)
5532
return
5533
5534
if not ns.filename:
5535
cmdline.print_usage()
5536
sys.exit(-1)
5537
5538
if ns.output and len(ns.filename) > 1:
5539
print("Usage error: can't use -o with multiple filenames.")
5540
print()
5541
cmdline.print_usage()
5542
sys.exit(-1)
5543
5544
for filename in ns.filename:
5545
if ns.verbose:
5546
print(filename)
5547
parse_file(filename, output=ns.output, verify=not ns.force)
5548
5549
5550
if __name__ == "__main__":
5551
sys.exit(main(sys.argv[1:]))
5552
5553