blob: ced3e675245a0d9c1142257ec3fcbec5325ec573 [file] [log] [blame]
Larry Hastings31826802013-10-19 00:09:25 -07001#!/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
8import abc
9import ast
10import atexit
11import clinic
12import collections
13import contextlib
14import functools
15import hashlib
16import inspect
17import io
18import itertools
19import os
20import re
21import shlex
22import sys
23import tempfile
24import textwrap
25
26
27# TODO:
28# converters for
29#
30# es
31# es#
32# et
33# et#
34# s#
35# u#
36# y#
37# z#
38# Z#
39#
40# soon:
41#
42# * allow mixing any two of {positional-only, positional-or-keyword,
43# keyword-only}
44# * dict constructor uses positional-only and keyword-only
45# * max and min use positional only with an optional group
46# and keyword-only
47#
48# * Generate forward slash for docstring first line
49# (if I get positional-only syntax pep accepted)
50#
51# * Add "version" directive, so we can complain if the file
52# is too new for us.
53#
54
55_empty = inspect._empty
56_void = inspect._void
57
58
59class Unspecified:
60 def __repr__(self):
61 return '<Unspecified>'
62
63unspecified = Unspecified()
64
65
66class Null:
67 def __repr__(self):
68 return '<Null>'
69
70NULL = Null()
71
72
73def _text_accumulator():
74 text = []
75 def output():
76 s = ''.join(text)
77 text.clear()
78 return s
79 return text, text.append, output
80
81
82def text_accumulator():
83 """
84 Creates a simple text accumulator / joiner.
85
86 Returns a pair of callables:
87 append, output
88 "append" appends a string to the accumulator.
89 "output" returns the contents of the accumulator
90 joined together (''.join(accumulator)) and
91 empties the accumulator.
92 """
93 text, append, output = _text_accumulator()
94 return append, output
95
96
97def fail(*args, filename=None, line_number=None):
98 joined = " ".join([str(a) for a in args])
99 add, output = text_accumulator()
100 add("Error")
101 if clinic:
102 if filename is None:
103 filename = clinic.filename
104 if clinic.block_parser and (line_number is None):
105 line_number = clinic.block_parser.line_number
106 if filename is not None:
107 add(' in file "' + filename + '"')
108 if line_number is not None:
109 add(" on line " + str(line_number))
110 add(':\n')
111 add(joined)
112 print(output())
113 sys.exit(-1)
114
115
116
117def quoted_for_c_string(s):
118 for old, new in (
119 ('"', '\\"'),
120 ("'", "\\'"),
121 ):
122 s = s.replace(old, new)
123 return s
124
125# added "self", "cls", and "null" just to be safe
126# (clinic will generate variables with these names)
127c_keywords = set("""
128asm auto break case char cls const continue default do double
129else enum extern float for goto if inline int long null register
130return self short signed sizeof static struct switch typedef
131typeof union unsigned void volatile while
132""".strip().split())
133
134def legal_c_identifier(s):
135 # if we picked a C keyword, pick something else
136 if s in c_keywords:
137 return s + "_value"
138 return s
139
140def rstrip_lines(s):
141 text, add, output = _text_accumulator()
142 for line in s.split('\n'):
143 add(line.rstrip())
144 add('\n')
145 text.pop()
146 return output()
147
148def linear_format(s, **kwargs):
149 """
150 Perform str.format-like substitution, except:
151 * The strings substituted must be on lines by
152 themselves. (This line is the "source line".)
153 * If the substitution text is empty, the source line
154 is removed in the output.
155 * If the substitution text is not empty:
156 * Each line of the substituted text is indented
157 by the indent of the source line.
158 * A newline will be added to the end.
159 """
160
161 add, output = text_accumulator()
162 for line in s.split('\n'):
163 indent, curly, trailing = line.partition('{')
164 if not curly:
165 add(line)
166 add('\n')
167 continue
168
169 name, curl, trailing = trailing.partition('}')
170 if not curly or name not in kwargs:
171 add(line)
172 add('\n')
173 continue
174
175 if trailing:
176 fail("Text found after {" + name + "} block marker! It must be on a line by itself.")
177 if indent.strip():
178 fail("Non-whitespace characters found before {" + name + "} block marker! It must be on a line by itself.")
179
180 value = kwargs[name]
181 if not value:
182 continue
183
184 value = textwrap.indent(rstrip_lines(value), indent)
185 add(value)
186 add('\n')
187
188 return output()[:-1]
189
190
191class CRenderData:
192 def __init__(self):
193
194 # The C statements to declare variables.
195 # Should be full lines with \n eol characters.
196 self.declarations = []
197
198 # The C statements required to initialize the variables before the parse call.
199 # Should be full lines with \n eol characters.
200 self.initializers = []
201
202 # The entries for the "keywords" array for PyArg_ParseTuple.
203 # Should be individual strings representing the names.
204 self.keywords = []
205
206 # The "format units" for PyArg_ParseTuple.
207 # Should be individual strings that will get
208 self.format_units = []
209
210 # The varargs arguments for PyArg_ParseTuple.
211 self.parse_arguments = []
212
213 # The parameter declarations for the impl function.
214 self.impl_parameters = []
215
216 # The arguments to the impl function at the time it's called.
217 self.impl_arguments = []
218
219 # For return converters: the name of the variable that
220 # should receive the value returned by the impl.
221 self.return_value = "return_value"
222
223 # For return converters: the code to convert the return
224 # value from the parse function. This is also where
225 # you should check the _return_value for errors, and
226 # "goto exit" if there are any.
227 self.return_conversion = []
228
229 # The C statements required to clean up after the impl call.
230 self.cleanup = []
231
232
233class Language(metaclass=abc.ABCMeta):
234
235 start_line = ""
236 body_prefix = ""
237 stop_line = ""
238 checksum_line = ""
239
240 @abc.abstractmethod
241 def render(self, block):
242 pass
243
244 def validate(self):
245 def assert_only_one(field, token='dsl_name'):
246 line = getattr(self, field)
247 token = '{' + token + '}'
248 if len(line.split(token)) != 2:
249 fail(self.__class__.__name__ + " " + field + " must contain " + token + " exactly once!")
250 assert_only_one('start_line')
251 assert_only_one('stop_line')
252 assert_only_one('checksum_line')
253 assert_only_one('checksum_line', 'checksum')
254
255 if len(self.body_prefix.split('{dsl_name}')) >= 3:
256 fail(self.__class__.__name__ + " body_prefix may contain " + token + " once at most!")
257
258
259
260class PythonLanguage(Language):
261
262 language = 'Python'
263 start_line = "#/*[{dsl_name}]"
264 body_prefix = "#"
265 stop_line = "#[{dsl_name}]*/"
266 checksum_line = "#/*[{dsl_name} checksum: {checksum}]*/"
267
268
269def permute_left_option_groups(l):
270 """
271 Given [1, 2, 3], should yield:
272 ()
273 (3,)
274 (2, 3)
275 (1, 2, 3)
276 """
277 yield tuple()
278 accumulator = []
279 for group in reversed(l):
280 accumulator = list(group) + accumulator
281 yield tuple(accumulator)
282
283
284def permute_right_option_groups(l):
285 """
286 Given [1, 2, 3], should yield:
287 ()
288 (1,)
289 (1, 2)
290 (1, 2, 3)
291 """
292 yield tuple()
293 accumulator = []
294 for group in l:
295 accumulator.extend(group)
296 yield tuple(accumulator)
297
298
299def permute_optional_groups(left, required, right):
300 """
301 Generator function that computes the set of acceptable
302 argument lists for the provided iterables of
303 argument groups. (Actually it generates a tuple of tuples.)
304
305 Algorithm: prefer left options over right options.
306
307 If required is empty, left must also be empty.
308 """
309 required = tuple(required)
310 result = []
311
312 if not required:
313 assert not left
314
315 accumulator = []
316 counts = set()
317 for r in permute_right_option_groups(right):
318 for l in permute_left_option_groups(left):
319 t = l + required + r
320 if len(t) in counts:
321 continue
322 counts.add(len(t))
323 accumulator.append(t)
324
325 accumulator.sort(key=len)
326 return tuple(accumulator)
327
328
329class CLanguage(Language):
330
331 language = 'C'
332 start_line = "/*[{dsl_name}]"
333 body_prefix = ""
334 stop_line = "[{dsl_name}]*/"
335 checksum_line = "/*[{dsl_name} checksum: {checksum}]*/"
336
337 def render(self, signatures):
338 function = None
339 for o in signatures:
340 if isinstance(o, Function):
341 if function:
342 fail("You may specify at most one function per block.\nFound a block containing at least two:\n\t" + repr(function) + " and " + repr(o))
343 function = o
344 return self.render_function(function)
345
346 def docstring_for_c_string(self, f):
347 text, add, output = _text_accumulator()
348 # turn docstring into a properly quoted C string
349 for line in f.docstring.split('\n'):
350 add('"')
351 add(quoted_for_c_string(line))
352 add('\\n"\n')
353
354 text.pop()
355 add('"')
356 return ''.join(text)
357
358 impl_prototype_template = "{c_basename}_impl({impl_parameters})"
359
360 @staticmethod
361 def template_base(*args):
362 flags = '|'.join(f for f in args if f)
363 return """
364PyDoc_STRVAR({c_basename}__doc__,
365{docstring});
366
367#define {methoddef_name} \\
368 {{"{name}", (PyCFunction){c_basename}, {meth_flags}, {c_basename}__doc__}},
369""".replace('{meth_flags}', flags)
370
371 def meth_noargs_pyobject_template(self, meth_flags=""):
372 return self.template_base("METH_NOARGS", meth_flags) + """
373static PyObject *
374{c_basename}(PyObject *{self_name})
375"""
376
377 def meth_noargs_template(self, meth_flags=""):
378 return self.template_base("METH_NOARGS", meth_flags) + """
379static {impl_return_type}
380{impl_prototype};
381
382static PyObject *
383{c_basename}(PyObject *{self_name})
384{{
385 PyObject *return_value = NULL;
386 {declarations}
387 {initializers}
388
389 {return_value} = {c_basename}_impl({impl_arguments});
390 {return_conversion}
391
392{exit_label}
393 {cleanup}
394 return return_value;
395}}
396
397static {impl_return_type}
398{impl_prototype}
399"""
400
401 def meth_o_template(self, meth_flags=""):
402 return self.template_base("METH_O", meth_flags) + """
403static PyObject *
404{c_basename}({impl_parameters})
405"""
406
407 def meth_o_return_converter_template(self, meth_flags=""):
408 return self.template_base("METH_O", meth_flags) + """
409static {impl_return_type}
410{impl_prototype};
411
412static PyObject *
413{c_basename}({impl_parameters})
414{{
415 PyObject *return_value = NULL;
416 {declarations}
417 {initializers}
418 _return_value = {c_basename}_impl({impl_arguments});
419 {return_conversion}
420
421{exit_label}
422 {cleanup}
423 return return_value;
424}}
425
426static {impl_return_type}
427{impl_prototype}
428"""
429
430 def option_group_template(self, meth_flags=""):
431 return self.template_base("METH_VARARGS", meth_flags) + """
432static {impl_return_type}
433{impl_prototype};
434
435static PyObject *
436{c_basename}(PyObject *{self_name}, PyObject *args)
437{{
438 PyObject *return_value = NULL;
439 {declarations}
440 {initializers}
441
442 {option_group_parsing}
443 {return_value} = {c_basename}_impl({impl_arguments});
444 {return_conversion}
445
446{exit_label}
447 {cleanup}
448 return return_value;
449}}
450
451static {impl_return_type}
452{impl_prototype}
453"""
454
455 def keywords_template(self, meth_flags=""):
456 return self.template_base("METH_VARARGS|METH_KEYWORDS", meth_flags) + """
457static {impl_return_type}
458{impl_prototype};
459
460static PyObject *
461{c_basename}(PyObject *{self_name}, PyObject *args, PyObject *kwargs)
462{{
463 PyObject *return_value = NULL;
464 static char *_keywords[] = {{{keywords}, NULL}};
465 {declarations}
466 {initializers}
467
468 if (!PyArg_ParseTupleAndKeywords(args, kwargs,
469 "{format_units}:{name}", _keywords,
470 {parse_arguments}))
471 goto exit;
472 {return_value} = {c_basename}_impl({impl_arguments});
473 {return_conversion}
474
475{exit_label}
476 {cleanup}
477 return return_value;
478}}
479
480static {impl_return_type}
481{impl_prototype}
482"""
483
484 def positional_only_template(self, meth_flags=""):
485 return self.template_base("METH_VARARGS", meth_flags) + """
486static {impl_return_type}
487{impl_prototype};
488
489static PyObject *
490{c_basename}(PyObject *{self_name}, PyObject *args)
491{{
492 PyObject *return_value = NULL;
493 {declarations}
494 {initializers}
495
496 if (!PyArg_ParseTuple(args,
497 "{format_units}:{name}",
498 {parse_arguments}))
499 goto exit;
500 {return_value} = {c_basename}_impl({impl_arguments});
501 {return_conversion}
502
503{exit_label}
504 {cleanup}
505 return return_value;
506}}
507
508static {impl_return_type}
509{impl_prototype}
510"""
511
512 @staticmethod
513 def group_to_variable_name(group):
514 adjective = "left_" if group < 0 else "right_"
515 return "group_" + adjective + str(abs(group))
516
517 def render_option_group_parsing(self, f, template_dict):
518 # positional only, grouped, optional arguments!
519 # can be optional on the left or right.
520 # here's an example:
521 #
522 # [ [ [ A1 A2 ] B1 B2 B3 ] C1 C2 ] D1 D2 D3 [ E1 E2 E3 [ F1 F2 F3 ] ]
523 #
524 # Here group D are required, and all other groups are optional.
525 # (Group D's "group" is actually None.)
526 # We can figure out which sets of arguments we have based on
527 # how many arguments are in the tuple.
528 #
529 # Note that you need to count up on both sides. For example,
530 # you could have groups C+D, or C+D+E, or C+D+E+F.
531 #
532 # What if the number of arguments leads us to an ambiguous result?
533 # Clinic prefers groups on the left. So in the above example,
534 # five arguments would map to B+C, not C+D.
535
536 add, output = text_accumulator()
537 parameters = list(f.parameters.values())
538
539 groups = []
540 group = None
541 left = []
542 right = []
543 required = []
544 last = unspecified
545
546 for p in parameters:
547 group_id = p.group
548 if group_id != last:
549 last = group_id
550 group = []
551 if group_id < 0:
552 left.append(group)
553 elif group_id == 0:
554 group = required
555 else:
556 right.append(group)
557 group.append(p)
558
559 count_min = sys.maxsize
560 count_max = -1
561
562 add("switch (PyTuple_Size(args)) {{\n")
563 for subset in permute_optional_groups(left, required, right):
564 count = len(subset)
565 count_min = min(count_min, count)
566 count_max = max(count_max, count)
567
568 group_ids = {p.group for p in subset} # eliminate duplicates
569 d = {}
570 d['count'] = count
571 d['name'] = f.name
572 d['groups'] = sorted(group_ids)
573 d['format_units'] = "".join(p.converter.format_unit for p in subset)
574
575 parse_arguments = []
576 for p in subset:
577 p.converter.parse_argument(parse_arguments)
578 d['parse_arguments'] = ", ".join(parse_arguments)
579
580 group_ids.discard(0)
581 lines = [self.group_to_variable_name(g) + " = 1;" for g in group_ids]
582 lines = "\n".join(lines)
583
584 s = """
585 case {count}:
586 if (!PyArg_ParseTuple(args, "{format_units}:{name}", {parse_arguments}))
587 return NULL;
588 {group_booleans}
589 break;
590"""[1:]
591 s = linear_format(s, group_booleans=lines)
592 s = s.format_map(d)
593 add(s)
594
595 add(" default:\n")
596 s = ' PyErr_SetString(PyExc_TypeError, "{} requires {} to {} arguments");\n'
597 add(s.format(f.full_name, count_min, count_max))
598 add(' return NULL;\n')
599 add("}}")
600 template_dict['option_group_parsing'] = output()
601
602 def render_function(self, f):
603 if not f:
604 return ""
605
606 add, output = text_accumulator()
607 data = CRenderData()
608
609 if f.kind == STATIC_METHOD:
610 meth_flags = 'METH_STATIC'
611 self_name = "null"
612 else:
613 if f.kind == CALLABLE:
614 meth_flags = ''
615 self_name = "self"
616 elif f.kind == CLASS_METHOD:
617 meth_flags = 'METH_CLASS'
618 self_name = "cls"
619 else:
620 fail("Unrecognized 'kind' " + repr(f.kind) + " for function " + f.name)
621
622 data.impl_parameters.append("PyObject *" + self_name)
623 data.impl_arguments.append(self_name)
624
625 if f.coexist:
626 if meth_flags:
627 meth_flags += '|'
628 meth_flags += 'METH_COEXIST'
629
630 parameters = list(f.parameters.values())
631 converters = [p.converter for p in parameters]
632
633 template_dict = {}
634
635 full_name = f.full_name
636 template_dict['full_name'] = full_name
637
638 name = full_name.rpartition('.')[2]
639 template_dict['name'] = name
640
641 c_basename = f.c_basename or full_name.replace(".", "_")
642 template_dict['c_basename'] = c_basename
643
644 methoddef_name = "{}_METHODDEF".format(c_basename.upper())
645 template_dict['methoddef_name'] = methoddef_name
646
647 template_dict['docstring'] = self.docstring_for_c_string(f)
648
649 template_dict['self_name'] = self_name
650
651 positional = has_option_groups = False
652
653 if parameters:
654 last_group = 0
655
656 for p in parameters:
657 c = p.converter
658
659 # insert group variable
660 group = p.group
661 if last_group != group:
662 last_group = group
663 if group:
664 group_name = self.group_to_variable_name(group)
665 data.impl_arguments.append(group_name)
666 data.declarations.append("int " + group_name + " = 0;")
667 data.impl_parameters.append("int " + group_name)
668 has_option_groups = True
669 c.render(p, data)
670
671 positional = parameters[-1].kind == inspect.Parameter.POSITIONAL_ONLY
672 if has_option_groups:
673 assert positional
674
675 f.return_converter.render(f, data)
676 template_dict['impl_return_type'] = f.return_converter.type
677
678 template_dict['declarations'] = "\n".join(data.declarations)
679 template_dict['initializers'] = "\n\n".join(data.initializers)
680 template_dict['keywords'] = '"' + '", "'.join(data.keywords) + '"'
681 template_dict['format_units'] = ''.join(data.format_units)
682 template_dict['parse_arguments'] = ', '.join(data.parse_arguments)
683 template_dict['impl_parameters'] = ", ".join(data.impl_parameters)
684 template_dict['impl_arguments'] = ", ".join(data.impl_arguments)
685 template_dict['return_conversion'] = "".join(data.return_conversion).rstrip()
686 template_dict['cleanup'] = "".join(data.cleanup)
687 template_dict['return_value'] = data.return_value
688
689 template_dict['impl_prototype'] = self.impl_prototype_template.format_map(template_dict)
690
691 default_return_converter = (not f.return_converter or
692 f.return_converter.type == 'PyObject *')
693
694 if not parameters:
695 if default_return_converter:
696 template = self.meth_noargs_pyobject_template(meth_flags)
697 else:
698 template = self.meth_noargs_template(meth_flags)
699 elif (len(parameters) == 1 and
700 parameters[0].kind == inspect.Parameter.POSITIONAL_ONLY and
701 not converters[0].is_optional() and
702 isinstance(converters[0], object_converter) and
703 converters[0].format_unit == 'O'):
704 if default_return_converter:
705 template = self.meth_o_template(meth_flags)
706 else:
707 # HACK
708 # we're using "impl_parameters" for the
709 # non-impl function, because that works
710 # better for METH_O. but that means we
711 # must surpress actually declaring the
712 # impl's parameters as variables in the
713 # non-impl. but since it's METH_O, we
714 # only have one anyway, and it's the first one.
715 declarations_copy = list(data.declarations)
716 before, pyobject, after = declarations_copy[0].partition('PyObject *')
717 assert not before, "hack failed, see comment"
718 assert pyobject, "hack failed, see comment"
719 assert after and after[0].isalpha(), "hack failed, see comment"
720 del declarations_copy[0]
721 template_dict['declarations'] = "\n".join(declarations_copy)
722 template = self.meth_o_return_converter_template(meth_flags)
723 elif has_option_groups:
724 self.render_option_group_parsing(f, template_dict)
725 template = self.option_group_template(meth_flags)
726 template = linear_format(template,
727 option_group_parsing=template_dict['option_group_parsing'])
728 elif positional:
729 template = self.positional_only_template(meth_flags)
730 else:
731 template = self.keywords_template(meth_flags)
732
733 template = linear_format(template,
734 declarations=template_dict['declarations'],
735 return_conversion=template_dict['return_conversion'],
736 initializers=template_dict['initializers'],
737 cleanup=template_dict['cleanup'],
738 )
739
740 # Only generate the "exit:" label
741 # if we have any gotos
742 need_exit_label = "goto exit;" in template
743 template = linear_format(template,
744 exit_label="exit:" if need_exit_label else ''
745 )
746
747 return template.format_map(template_dict)
748
749
750@contextlib.contextmanager
751def OverrideStdioWith(stdout):
752 saved_stdout = sys.stdout
753 sys.stdout = stdout
754 try:
755 yield
756 finally:
757 assert sys.stdout is stdout
758 sys.stdout = saved_stdout
759
760
761def create_regex(before, after):
762 """Create an re object for matching marker lines."""
763 pattern = r'^{}(\w+){}$'
764 return re.compile(pattern.format(re.escape(before), re.escape(after)))
765
766
767class Block:
768 r"""
769 Represents a single block of text embedded in
770 another file. If dsl_name is None, the block represents
771 verbatim text, raw original text from the file, in
772 which case "input" will be the only non-false member.
773 If dsl_name is not None, the block represents a Clinic
774 block.
775
776 input is always str, with embedded \n characters.
777 input represents the original text from the file;
778 if it's a Clinic block, it is the original text with
779 the body_prefix and redundant leading whitespace removed.
780
781 dsl_name is either str or None. If str, it's the text
782 found on the start line of the block between the square
783 brackets.
784
785 signatures is either list or None. If it's a list,
786 it may only contain clinic.Module, clinic.Class, and
787 clinic.Function objects. At the moment it should
788 contain at most one of each.
789
790 output is either str or None. If str, it's the output
791 from this block, with embedded '\n' characters.
792
793 indent is either str or None. It's the leading whitespace
794 that was found on every line of input. (If body_prefix is
795 not empty, this is the indent *after* removing the
796 body_prefix.)
797
798 preindent is either str or None. It's the whitespace that
799 was found in front of every line of input *before* the
800 "body_prefix" (see the Language object). If body_prefix
801 is empty, preindent must always be empty too.
802
803 To illustrate indent and preindent: Assume that '_'
804 represents whitespace. If the block processed was in a
805 Python file, and looked like this:
806 ____#/*[python]
807 ____#__for a in range(20):
808 ____#____print(a)
809 ____#[python]*/
810 "preindent" would be "____" and "indent" would be "__".
811
812 """
813 def __init__(self, input, dsl_name=None, signatures=None, output=None, indent='', preindent=''):
814 assert isinstance(input, str)
815 self.input = input
816 self.dsl_name = dsl_name
817 self.signatures = signatures or []
818 self.output = output
819 self.indent = indent
820 self.preindent = preindent
821
822
823class BlockParser:
824 """
825 Block-oriented parser for Argument Clinic.
826 Iterator, yields Block objects.
827 """
828
829 def __init__(self, input, language, *, verify=True):
830 """
831 "input" should be a str object
832 with embedded \n characters.
833
834 "language" should be a Language object.
835 """
836 language.validate()
837
838 self.input = collections.deque(reversed(input.splitlines(keepends=True)))
839 self.block_start_line_number = self.line_number = 0
840
841 self.language = language
842 before, _, after = language.start_line.partition('{dsl_name}')
843 assert _ == '{dsl_name}'
844 self.start_re = create_regex(before, after)
845 self.verify = verify
846 self.last_checksum_re = None
847 self.last_dsl_name = None
848 self.dsl_name = None
849
850 def __iter__(self):
851 return self
852
853 def __next__(self):
854 if not self.input:
855 raise StopIteration
856
857 if self.dsl_name:
858 return_value = self.parse_clinic_block(self.dsl_name)
859 self.dsl_name = None
860 return return_value
861 return self.parse_verbatim_block()
862
863 def is_start_line(self, line):
864 match = self.start_re.match(line.lstrip())
865 return match.group(1) if match else None
866
867 def _line(self):
868 self.line_number += 1
869 return self.input.pop()
870
871 def parse_verbatim_block(self):
872 add, output = text_accumulator()
873 self.block_start_line_number = self.line_number
874
875 while self.input:
876 line = self._line()
877 dsl_name = self.is_start_line(line)
878 if dsl_name:
879 self.dsl_name = dsl_name
880 break
881 add(line)
882
883 return Block(output())
884
885 def parse_clinic_block(self, dsl_name):
886 input_add, input_output = text_accumulator()
887 self.block_start_line_number = self.line_number + 1
888 stop_line = self.language.stop_line.format(dsl_name=dsl_name) + '\n'
889 body_prefix = self.language.body_prefix.format(dsl_name=dsl_name)
890
891 # consume body of program
892 while self.input:
893 line = self._line()
894 if line == stop_line or self.is_start_line(line):
895 break
896 if body_prefix:
897 line = line.lstrip()
898 assert line.startswith(body_prefix)
899 line = line[len(body_prefix):]
900 input_add(line)
901
902 # consume output and checksum line, if present.
903 if self.last_dsl_name == dsl_name:
904 checksum_re = self.last_checksum_re
905 else:
906 before, _, after = self.language.checksum_line.format(dsl_name=dsl_name, checksum='{checksum}').partition('{checksum}')
907 assert _ == '{checksum}'
908 checksum_re = create_regex(before, after)
909 self.last_dsl_name = dsl_name
910 self.last_checksum_re = checksum_re
911
912 # scan forward for checksum line
913 output_add, output_output = text_accumulator()
914 checksum = None
915 while self.input:
916 line = self._line()
917 match = checksum_re.match(line.lstrip())
918 checksum = match.group(1) if match else None
919 if checksum:
920 break
921 output_add(line)
922 if self.is_start_line(line):
923 break
924
Larry Hastingsef3b1fb2013-10-22 23:26:23 -0700925 output = output_output()
Larry Hastings31826802013-10-19 00:09:25 -0700926 if checksum:
Larry Hastings31826802013-10-19 00:09:25 -0700927 if self.verify:
928 computed = compute_checksum(output)
929 if checksum != computed:
930 fail("Checksum mismatch!\nExpected: {}\nComputed: {}".format(checksum, computed))
931 else:
932 # put back output
933 self.input.extend(reversed(output.splitlines(keepends=True)))
934 self.line_number -= len(output)
935 output = None
936
937 return Block(input_output(), dsl_name, output=output)
938
939
940class BlockPrinter:
941
942 def __init__(self, language, f=None):
943 self.language = language
944 self.f = f or io.StringIO()
945
946 def print_block(self, block):
947 input = block.input
948 output = block.output
949 dsl_name = block.dsl_name
950 write = self.f.write
951
952 assert (not input) or (input.endswith('\n'))
953 assert not ((dsl_name == None) ^ (output == None)), "you must specify dsl_name and output together, dsl_name " + repr(dsl_name)
954
955 if not dsl_name:
956 write(input)
957 return
958
959 write(self.language.start_line.format(dsl_name=dsl_name))
960 write("\n")
961
962 body_prefix = self.language.body_prefix.format(dsl_name=dsl_name)
963 if not body_prefix:
964 write(input)
965 else:
966 for line in input.split('\n'):
967 write(body_prefix)
968 write(line)
969 write("\n")
970
971 write(self.language.stop_line.format(dsl_name=dsl_name))
972 write("\n")
973
974 output = block.output
975 if output:
976 write(output)
977 if not output.endswith('\n'):
978 write('\n')
979
980 write(self.language.checksum_line.format(dsl_name=dsl_name, checksum=compute_checksum(output)))
981 write("\n")
982
983
984# maps strings to Language objects.
985# "languages" maps the name of the language ("C", "Python").
986# "extensions" maps the file extension ("c", "py").
987languages = { 'C': CLanguage, 'Python': PythonLanguage }
988extensions = { 'c': CLanguage, 'h': CLanguage, 'py': PythonLanguage }
989
990
991# maps strings to callables.
992# these callables must be of the form:
993# def foo(name, default, *, ...)
994# The callable may have any number of keyword-only parameters.
995# The callable must return a CConverter object.
996# The callable should not call builtins.print.
997converters = {}
998
999# maps strings to callables.
1000# these callables follow the same rules as those for "converters" above.
1001# note however that they will never be called with keyword-only parameters.
1002legacy_converters = {}
1003
1004
1005# maps strings to callables.
1006# these callables must be of the form:
1007# def foo(*, ...)
1008# The callable may have any number of keyword-only parameters.
1009# The callable must return a CConverter object.
1010# The callable should not call builtins.print.
1011return_converters = {}
1012
1013class Clinic:
1014 def __init__(self, language, printer=None, *, verify=True, filename=None):
1015 # maps strings to Parser objects.
1016 # (instantiated from the "parsers" global.)
1017 self.parsers = {}
1018 self.language = language
1019 self.printer = printer or BlockPrinter(language)
1020 self.verify = verify
1021 self.filename = filename
1022 self.modules = collections.OrderedDict()
1023
1024 global clinic
1025 clinic = self
1026
1027 def parse(self, input):
1028 printer = self.printer
1029 self.block_parser = BlockParser(input, self.language, verify=self.verify)
1030 for block in self.block_parser:
1031 dsl_name = block.dsl_name
1032 if dsl_name:
1033 if dsl_name not in self.parsers:
1034 assert dsl_name in parsers, "No parser to handle {!r} block.".format(dsl_name)
1035 self.parsers[dsl_name] = parsers[dsl_name](self)
1036 parser = self.parsers[dsl_name]
1037 parser.parse(block)
1038 printer.print_block(block)
1039 return printer.f.getvalue()
1040
1041 def _module_and_class(self, fields):
1042 """
1043 fields should be an iterable of field names.
1044 returns a tuple of (module, class).
1045 the module object could actually be self (a clinic object).
1046 this function is only ever used to find the parent of where
1047 a new class/module should go.
1048 """
1049 in_classes = False
1050 parent = module = self
1051 cls = None
1052 so_far = []
1053
1054 for field in fields:
1055 so_far.append(field)
1056 if not in_classes:
1057 child = parent.modules.get(field)
1058 if child:
1059 module = child
1060 continue
1061 in_classes = True
1062 if not hasattr(parent, 'classes'):
1063 return module, cls
1064 child = parent.classes.get(field)
1065 if not child:
1066 fail('Parent class or module ' + '.'.join(so_far) + " does not exist.")
1067 cls = parent = child
1068
1069 return module, cls
1070
1071
1072def parse_file(filename, *, verify=True, output=None, encoding='utf-8'):
1073 extension = os.path.splitext(filename)[1][1:]
1074 if not extension:
1075 fail("Can't extract file type for file " + repr(filename))
1076
1077 try:
1078 language = extensions[extension]()
1079 except KeyError:
1080 fail("Can't identify file type for file " + repr(filename))
1081
1082 clinic = Clinic(language, verify=verify, filename=filename)
1083
1084 with open(filename, 'r', encoding=encoding) as f:
1085 text = clinic.parse(f.read())
1086
1087 directory = os.path.dirname(filename) or '.'
1088
1089 with tempfile.TemporaryDirectory(prefix="clinic", dir=directory) as tmpdir:
1090 bytes = text.encode(encoding)
1091 tmpfilename = os.path.join(tmpdir, os.path.basename(filename))
1092 with open(tmpfilename, "wb") as f:
1093 f.write(bytes)
1094 os.replace(tmpfilename, output or filename)
1095
1096
1097def compute_checksum(input):
1098 input = input or ''
1099 return hashlib.sha1(input.encode('utf-8')).hexdigest()
1100
1101
1102
1103
1104class PythonParser:
1105 def __init__(self, clinic):
1106 pass
1107
1108 def parse(self, block):
1109 s = io.StringIO()
1110 with OverrideStdioWith(s):
1111 exec(block.input)
1112 block.output = s.getvalue()
1113
1114
1115class Module:
1116 def __init__(self, name, module=None):
1117 self.name = name
1118 self.module = self.parent = module
1119
1120 self.modules = collections.OrderedDict()
1121 self.classes = collections.OrderedDict()
1122 self.functions = []
1123
1124class Class:
1125 def __init__(self, name, module=None, cls=None):
1126 self.name = name
1127 self.module = module
1128 self.cls = cls
1129 self.parent = cls or module
1130
1131 self.classes = collections.OrderedDict()
1132 self.functions = []
1133
1134DATA, CALLABLE, METHOD, STATIC_METHOD, CLASS_METHOD = range(5)
1135
1136class Function:
1137 """
1138 Mutable duck type for inspect.Function.
1139
1140 docstring - a str containing
1141 * embedded line breaks
1142 * text outdented to the left margin
1143 * no trailing whitespace.
1144 It will always be true that
1145 (not docstring) or ((not docstring[0].isspace()) and (docstring.rstrip() == docstring))
1146 """
1147
1148 def __init__(self, parameters=None, *, name,
1149 module, cls=None, c_basename=None,
1150 full_name=None,
1151 return_converter, return_annotation=_empty,
1152 docstring=None, kind=CALLABLE, coexist=False):
1153 self.parameters = parameters or collections.OrderedDict()
1154 self.return_annotation = return_annotation
1155 self.name = name
1156 self.full_name = full_name
1157 self.module = module
1158 self.cls = cls
1159 self.parent = cls or module
1160 self.c_basename = c_basename
1161 self.return_converter = return_converter
1162 self.docstring = docstring or ''
1163 self.kind = kind
1164 self.coexist = coexist
1165
1166 def __repr__(self):
1167 return '<clinic.Function ' + self.name + '>'
1168
1169
1170class Parameter:
1171 """
1172 Mutable duck type of inspect.Parameter.
1173 """
1174
1175 def __init__(self, name, kind, *, default=_empty,
1176 function, converter, annotation=_empty,
1177 docstring=None, group=0):
1178 self.name = name
1179 self.kind = kind
1180 self.default = default
1181 self.function = function
1182 self.converter = converter
1183 self.annotation = annotation
1184 self.docstring = docstring or ''
1185 self.group = group
1186
1187 def __repr__(self):
1188 return '<clinic.Parameter ' + self.name + '>'
1189
1190 def is_keyword_only(self):
1191 return self.kind == inspect.Parameter.KEYWORD_ONLY
1192
1193py_special_values = {
1194 NULL: "None",
1195}
1196
1197def py_repr(o):
1198 special = py_special_values.get(o)
1199 if special:
1200 return special
1201 return repr(o)
1202
1203
1204c_special_values = {
1205 NULL: "NULL",
1206 None: "Py_None",
1207}
1208
1209def c_repr(o):
1210 special = c_special_values.get(o)
1211 if special:
1212 return special
1213 if isinstance(o, str):
1214 return '"' + quoted_for_c_string(o) + '"'
1215 return repr(o)
1216
1217def add_c_converter(f, name=None):
1218 if not name:
1219 name = f.__name__
1220 if not name.endswith('_converter'):
1221 return f
1222 name = name[:-len('_converter')]
1223 converters[name] = f
1224 return f
1225
1226def add_default_legacy_c_converter(cls):
1227 # automatically add converter for default format unit
1228 # (but without stomping on the existing one if it's already
1229 # set, in case you subclass)
1230 if ((cls.format_unit != 'O&') and
1231 (cls.format_unit not in legacy_converters)):
1232 legacy_converters[cls.format_unit] = cls
1233 return cls
1234
1235def add_legacy_c_converter(format_unit, **kwargs):
1236 """
1237 Adds a legacy converter.
1238 """
1239 def closure(f):
1240 if not kwargs:
1241 added_f = f
1242 else:
1243 added_f = functools.partial(f, **kwargs)
1244 legacy_converters[format_unit] = added_f
1245 return f
1246 return closure
1247
1248class CConverterAutoRegister(type):
1249 def __init__(cls, name, bases, classdict):
1250 add_c_converter(cls)
1251 add_default_legacy_c_converter(cls)
1252
1253class CConverter(metaclass=CConverterAutoRegister):
1254 """
1255 For the init function, self, name, function, and default
1256 must be keyword-or-positional parameters. All other
1257 parameters (including "required" and "doc_default")
1258 must be keyword-only.
1259 """
1260
1261 type = None
1262 format_unit = 'O&'
1263
1264 # The Python default value for this parameter, as a Python value.
1265 # Or "unspecified" if there is no default.
1266 default = unspecified
1267
1268 # "default" converted into a str for rendering into Python code.
1269 py_default = None
1270
1271 # "default" as it should appear in the documentation, as a string.
1272 # Or None if there is no default.
1273 doc_default = None
1274
1275 # "default" converted into a C value, as a string.
1276 # Or None if there is no default.
1277 c_default = None
1278
1279 # The C converter *function* to be used, if any.
1280 # (If this is not None, format_unit must be 'O&'.)
1281 converter = None
1282 encoding = None
1283 impl_by_reference = False
1284 parse_by_reference = True
1285 length = False
1286
1287 def __init__(self, name, function, default=unspecified, *, doc_default=None, required=False, annotation=unspecified, **kwargs):
1288 self.function = function
1289 self.name = name
1290
1291 if default is not unspecified:
1292 self.default = default
1293 self.py_default = py_repr(default)
1294 self.doc_default = doc_default if doc_default is not None else self.py_default
1295 self.c_default = c_repr(default)
1296 elif doc_default is not None:
1297 fail(function.fullname + " argument " + name + " specified a 'doc_default' without having a 'default'")
1298 if annotation != unspecified:
1299 fail("The 'annotation' parameter is not currently permitted.")
1300 self.required = required
1301 self.converter_init(**kwargs)
1302
1303 def converter_init(self):
1304 pass
1305
1306 def is_optional(self):
1307 return (self.default is not unspecified) and (not self.required)
1308
1309 def render(self, parameter, data):
1310 """
1311 parameter is a clinic.Parameter instance.
1312 data is a CRenderData instance.
1313 """
1314 name = legal_c_identifier(self.name)
1315
1316 # declarations
1317 d = self.declaration()
1318 data.declarations.append(d)
1319
1320 # initializers
1321 initializers = self.initialize()
1322 if initializers:
1323 data.initializers.append('/* initializers for ' + name + ' */\n' + initializers.rstrip())
1324
1325 # impl_arguments
1326 s = ("&" if self.impl_by_reference else "") + name
1327 data.impl_arguments.append(s)
1328
1329 # keywords
1330 data.keywords.append(name)
1331
1332 # format_units
1333 if self.is_optional() and '|' not in data.format_units:
1334 data.format_units.append('|')
1335 if parameter.is_keyword_only() and '$' not in data.format_units:
1336 data.format_units.append('$')
1337 data.format_units.append(self.format_unit)
1338
1339 # parse_arguments
1340 self.parse_argument(data.parse_arguments)
1341
1342 # impl_parameters
1343 data.impl_parameters.append(self.simple_declaration(by_reference=self.impl_by_reference))
1344
1345 # cleanup
1346 cleanup = self.cleanup()
1347 if cleanup:
1348 data.cleanup.append('/* Cleanup for ' + name + ' */\n' + cleanup.rstrip() + "\n")
1349
1350 # Why is this one broken out separately?
1351 # For "positional-only" function parsing,
1352 # which generates a bunch of PyArg_ParseTuple calls.
1353 def parse_argument(self, list):
1354 assert not (self.converter and self.encoding)
1355 if self.format_unit == 'O&':
1356 assert self.converter
1357 list.append(self.converter)
1358
1359 if self.encoding:
1360 list.append(self.encoding)
1361
1362 s = ("&" if self.parse_by_reference else "") + legal_c_identifier(self.name)
1363 list.append(s)
1364
1365 #
1366 # All the functions after here are intended as extension points.
1367 #
1368
1369 def simple_declaration(self, by_reference=False):
1370 """
1371 Computes the basic declaration of the variable.
1372 Used in computing the prototype declaration and the
1373 variable declaration.
1374 """
1375 prototype = [self.type]
1376 if by_reference or not self.type.endswith('*'):
1377 prototype.append(" ")
1378 if by_reference:
1379 prototype.append('*')
1380 prototype.append(legal_c_identifier(self.name))
1381 return "".join(prototype)
1382
1383 def declaration(self):
1384 """
1385 The C statement to declare this variable.
1386 """
1387 declaration = [self.simple_declaration()]
1388 if self.c_default:
1389 declaration.append(" = ")
1390 declaration.append(self.c_default)
1391 declaration.append(";")
1392 return "".join(declaration)
1393
1394 def initialize(self):
1395 """
1396 The C statements required to set up this variable before parsing.
1397 Returns a string containing this code indented at column 0.
1398 If no initialization is necessary, returns an empty string.
1399 """
1400 return ""
1401
1402 def cleanup(self):
1403 """
1404 The C statements required to clean up after this variable.
1405 Returns a string containing this code indented at column 0.
1406 If no cleanup is necessary, returns an empty string.
1407 """
1408 return ""
1409
1410
1411class bool_converter(CConverter):
1412 type = 'int'
1413 format_unit = 'p'
1414
1415 def converter_init(self):
1416 self.default = bool(self.default)
1417 self.c_default = str(int(self.default))
1418
1419class char_converter(CConverter):
1420 type = 'char'
1421 format_unit = 'c'
1422
1423@add_legacy_c_converter('B', bitwise=True)
1424class byte_converter(CConverter):
1425 type = 'byte'
1426 format_unit = 'b'
1427
1428 def converter_init(self, *, bitwise=False):
1429 if bitwise:
1430 format_unit = 'B'
1431
1432class short_converter(CConverter):
1433 type = 'short'
1434 format_unit = 'h'
1435
1436class unsigned_short_converter(CConverter):
1437 type = 'unsigned short'
1438 format_unit = 'H'
1439
1440 def converter_init(self, *, bitwise=False):
1441 if not bitwise:
1442 fail("Unsigned shorts must be bitwise (for now).")
1443
1444@add_legacy_c_converter('C', from_str=True)
1445class int_converter(CConverter):
1446 type = 'int'
1447 format_unit = 'i'
1448
1449 def converter_init(self, *, from_str=False):
1450 if from_str:
1451 format_unit = 'C'
1452
1453class unsigned_int_converter(CConverter):
1454 type = 'unsigned int'
1455 format_unit = 'I'
1456
1457 def converter_init(self, *, bitwise=False):
1458 if not bitwise:
1459 fail("Unsigned ints must be bitwise (for now).")
1460
1461class long_converter(CConverter):
1462 type = 'long'
1463 format_unit = 'l'
1464
1465class unsigned_long_converter(CConverter):
1466 type = 'unsigned long'
1467 format_unit = 'k'
1468
1469 def converter_init(self, *, bitwise=False):
1470 if not bitwise:
1471 fail("Unsigned longs must be bitwise (for now).")
1472
1473class PY_LONG_LONG_converter(CConverter):
1474 type = 'PY_LONG_LONG'
1475 format_unit = 'L'
1476
1477class unsigned_PY_LONG_LONG_converter(CConverter):
1478 type = 'unsigned PY_LONG_LONG'
1479 format_unit = 'K'
1480
1481 def converter_init(self, *, bitwise=False):
1482 if not bitwise:
1483 fail("Unsigned PY_LONG_LONGs must be bitwise (for now).")
1484
1485class Py_ssize_t_converter(CConverter):
1486 type = 'Py_ssize_t'
1487 format_unit = 'n'
1488
1489
1490class float_converter(CConverter):
1491 type = 'float'
1492 format_unit = 'f'
1493
1494class double_converter(CConverter):
1495 type = 'double'
1496 format_unit = 'd'
1497
1498
1499class Py_complex_converter(CConverter):
1500 type = 'Py_complex'
1501 format_unit = 'D'
1502
1503
1504class object_converter(CConverter):
1505 type = 'PyObject *'
1506 format_unit = 'O'
1507
1508 def converter_init(self, *, type=None):
1509 if type:
1510 assert isinstance(type, str)
1511 assert type.isidentifier()
1512 try:
1513 type = eval(type)
1514 # need more of these!
1515 type = {
1516 str: '&PyUnicode_Type',
1517 }[type]
1518 except NameError:
1519 type = type
1520 self.format_unit = 'O!'
1521 self.encoding = type
1522
1523
1524@add_legacy_c_converter('y', from_bytes=True)
1525@add_legacy_c_converter('z', nullable=True)
1526class str_converter(CConverter):
1527 type = 'const char *'
1528 format_unit = 's'
1529
1530 def converter_init(self, *, nullable=False, from_bytes=False):
1531 if from_bytes:
1532 assert not nullable
1533 format_unit = 'y'
1534 if nullable:
1535 format_unit = 'z'
1536
1537
1538class PyBytesObject_converter(CConverter):
1539 type = 'PyBytesObject *'
1540 format_unit = 'S'
1541
1542class PyByteArrayObject_converter(CConverter):
1543 type = 'PyByteArrayObject *'
1544 format_unit = 'Y'
1545
1546class unicode_converter(CConverter):
1547 type = 'PyObject *'
1548 format_unit = 'U'
1549
1550@add_legacy_c_converter('Z', nullable=True)
1551class Py_UNICODE_converter(CConverter):
1552 type = 'Py_UNICODE *'
1553 format_unit = 'u'
1554
1555 def converter_init(self, *, nullable=False):
1556 if nullable:
1557 format_unit = 'Z'
1558
1559@add_legacy_c_converter('s*', zeroes=True)
1560@add_legacy_c_converter('w*', read_write=True)
1561@add_legacy_c_converter('z*', zeroes=True, nullable=True)
1562class Py_buffer_converter(CConverter):
1563 type = 'Py_buffer'
1564 format_unit = 'y*'
1565 impl_by_reference = True
1566
1567 def converter_init(self, *, str=False, zeroes=False, nullable=False, read_write=False):
1568 if not str:
1569 assert not (zeroes or nullable or read_write)
1570 elif read_write:
1571 assert not (zeroes or nullable)
1572 self.format_unit = 'w*'
1573 else:
1574 assert zeroes
1575 self.format_unit = 'z*' if nullable else 's*'
1576
1577 def cleanup(self):
1578 return "PyBuffer_Release(&" + legal_c_identifier(self.name) + ");\n"
1579
1580
1581def add_c_return_converter(f, name=None):
1582 if not name:
1583 name = f.__name__
1584 if not name.endswith('_return_converter'):
1585 return f
1586 name = name[:-len('_return_converter')]
1587 return_converters[name] = f
1588 return f
1589
1590
1591class CReturnConverterAutoRegister(type):
1592 def __init__(cls, name, bases, classdict):
1593 add_c_return_converter(cls)
1594
1595class CReturnConverter(metaclass=CReturnConverterAutoRegister):
1596
1597 type = 'PyObject *'
1598 default = None
1599
1600 def __init__(self, *, doc_default=None, **kwargs):
1601 self.doc_default = doc_default
1602 try:
1603 self.return_converter_init(**kwargs)
1604 except TypeError as e:
1605 s = ', '.join(name + '=' + repr(value) for name, value in kwargs.items())
1606 sys.exit(self.__class__.__name__ + '(' + s + ')\n' + str(e))
1607
1608 def return_converter_init(self):
1609 pass
1610
1611 def declare(self, data, name="_return_value"):
1612 line = []
1613 add = line.append
1614 add(self.type)
1615 if not self.type.endswith('*'):
1616 add(' ')
1617 add(name + ';')
1618 data.declarations.append(''.join(line))
1619 data.return_value = name
1620
1621 def err_occurred_if(self, expr, data):
1622 data.return_conversion.append('if (({}) && PyErr_Occurred())\n goto exit;\n'.format(expr))
1623
1624 def err_occurred_if_null_pointer(self, variable, data):
1625 data.return_conversion.append('if ({} == NULL)\n goto exit;\n'.format(variable))
1626
1627 def render(self, function, data):
1628 """
1629 function is a clinic.Function instance.
1630 data is a CRenderData instance.
1631 """
1632 pass
1633
1634add_c_return_converter(CReturnConverter, 'object')
1635
1636class int_return_converter(CReturnConverter):
1637 type = 'int'
1638
1639 def render(self, function, data):
1640 self.declare(data)
1641 self.err_occurred_if("_return_value == -1", data)
1642 data.return_conversion.append(
1643 'return_value = PyLong_FromLong((long)_return_value);\n')
1644
1645
1646class long_return_converter(CReturnConverter):
1647 type = 'long'
1648
1649 def render(self, function, data):
1650 self.declare(data)
1651 self.err_occurred_if("_return_value == -1", data)
1652 data.return_conversion.append(
1653 'return_value = PyLong_FromLong(_return_value);\n')
1654
1655
1656class Py_ssize_t_return_converter(CReturnConverter):
1657 type = 'Py_ssize_t'
1658
1659 def render(self, function, data):
1660 self.declare(data)
1661 self.err_occurred_if("_return_value == -1", data)
1662 data.return_conversion.append(
1663 'return_value = PyLong_FromSsize_t(_return_value);\n')
1664
1665
1666class DecodeFSDefault_return_converter(CReturnConverter):
1667 type = 'char *'
1668
1669 def render(self, function, data):
1670 self.declare(data)
1671 self.err_occurred_if_null_pointer("_return_value", data)
1672 data.return_conversion.append(
1673 'return_value = PyUnicode_DecodeFSDefault(_return_value);\n')
1674
1675
1676class IndentStack:
1677 def __init__(self):
1678 self.indents = []
1679 self.margin = None
1680
1681 def _ensure(self):
1682 if not self.indents:
1683 fail('IndentStack expected indents, but none are defined.')
1684
1685 def measure(self, line):
1686 """
1687 Returns the length of the line's margin.
1688 """
1689 if '\t' in line:
1690 fail('Tab characters are illegal in the Clinic DSL.')
1691 stripped = line.lstrip()
1692 if not len(stripped):
1693 # we can't tell anything from an empty line
1694 # so just pretend it's indented like our current indent
1695 self._ensure()
1696 return self.indents[-1]
1697 return len(line) - len(stripped)
1698
1699 def infer(self, line):
1700 """
1701 Infer what is now the current margin based on this line.
1702 Returns:
1703 1 if we have indented (or this is the first margin)
1704 0 if the margin has not changed
1705 -N if we have dedented N times
1706 """
1707 indent = self.measure(line)
1708 margin = ' ' * indent
1709 if not self.indents:
1710 self.indents.append(indent)
1711 self.margin = margin
1712 return 1
1713 current = self.indents[-1]
1714 if indent == current:
1715 return 0
1716 if indent > current:
1717 self.indents.append(indent)
1718 self.margin = margin
1719 return 1
1720 # indent < current
1721 if indent not in self.indents:
1722 fail("Illegal outdent.")
1723 outdent_count = 0
1724 while indent != current:
1725 self.indents.pop()
1726 current = self.indents[-1]
1727 outdent_count -= 1
1728 self.margin = margin
1729 return outdent_count
1730
1731 @property
1732 def depth(self):
1733 """
1734 Returns how many margins are currently defined.
1735 """
1736 return len(self.indents)
1737
1738 def indent(self, line):
1739 """
1740 Indents a line by the currently defined margin.
1741 """
1742 return self.margin + line
1743
1744 def dedent(self, line):
1745 """
1746 Dedents a line by the currently defined margin.
1747 (The inverse of 'indent'.)
1748 """
1749 margin = self.margin
1750 indent = self.indents[-1]
1751 if not line.startswith(margin):
1752 fail('Cannot dedent, line does not start with the previous margin:')
1753 return line[indent:]
1754
1755
1756class DSLParser:
1757 def __init__(self, clinic):
1758 self.clinic = clinic
1759
1760 self.directives = {}
1761 for name in dir(self):
1762 # functions that start with directive_ are added to directives
1763 _, s, key = name.partition("directive_")
1764 if s:
1765 self.directives[key] = getattr(self, name)
1766
1767 # functions that start with at_ are too, with an @ in front
1768 _, s, key = name.partition("at_")
1769 if s:
1770 self.directives['@' + key] = getattr(self, name)
1771
1772 self.reset()
1773
1774 def reset(self):
1775 self.function = None
1776 self.state = self.state_dsl_start
1777 self.parameter_indent = None
1778 self.keyword_only = False
1779 self.group = 0
1780 self.parameter_state = self.ps_start
1781 self.indent = IndentStack()
1782 self.kind = CALLABLE
1783 self.coexist = False
1784
1785 def directive_module(self, name):
1786 fields = name.split('.')
1787 new = fields.pop()
1788 module, cls = self.clinic._module_and_class(fields)
1789 if cls:
1790 fail("Can't nest a module inside a class!")
1791 m = Module(name, module)
1792 module.modules[name] = m
1793 self.block.signatures.append(m)
1794
1795 def directive_class(self, name):
1796 fields = name.split('.')
1797 in_classes = False
1798 parent = self
1799 name = fields.pop()
1800 so_far = []
1801 module, cls = self.clinic._module_and_class(fields)
1802
1803 if not module:
1804 fail("You must explicitly specify the module for the class.")
1805
1806 c = Class(name, module, cls)
1807 module.classes[name] = c
1808 if cls:
1809 cls.classes[name] = c
1810 self.block.signatures.append(c)
1811
1812 def at_classmethod(self):
1813 assert self.kind is CALLABLE
1814 self.kind = CLASS_METHOD
1815
1816 def at_staticmethod(self):
1817 assert self.kind is CALLABLE
1818 self.kind = STATIC_METHOD
1819
1820 def at_coexist(self):
1821 assert self.coexist == False
1822 self.coexist = True
1823
1824 def parse(self, block):
1825 self.reset()
1826 self.block = block
1827 block_start = self.clinic.block_parser.line_number
1828 lines = block.input.split('\n')
1829 for line_number, line in enumerate(lines, self.clinic.block_parser.block_start_line_number):
1830 if '\t' in line:
1831 fail('Tab characters are illegal in the Clinic DSL.\n\t' + repr(line), line_number=block_start)
1832 self.state(line)
1833
1834 self.next(self.state_terminal)
1835 self.state(None)
1836
1837 block.output = self.clinic.language.render(block.signatures)
1838
1839 @staticmethod
1840 def ignore_line(line):
1841 # ignore comment-only lines
1842 if line.lstrip().startswith('#'):
1843 return True
1844
1845 # Ignore empty lines too
1846 # (but not in docstring sections!)
1847 if not line.strip():
1848 return True
1849
1850 return False
1851
1852 @staticmethod
1853 def calculate_indent(line):
1854 return len(line) - len(line.strip())
1855
1856 def next(self, state, line=None):
1857 # real_print(self.state.__name__, "->", state.__name__, ", line=", line)
1858 self.state = state
1859 if line is not None:
1860 self.state(line)
1861
1862 def state_dsl_start(self, line):
1863 # self.block = self.ClinicOutputBlock(self)
1864 if self.ignore_line(line):
1865 return
1866 self.next(self.state_modulename_name, line)
1867
1868 def state_modulename_name(self, line):
1869 # looking for declaration, which establishes the leftmost column
1870 # line should be
1871 # modulename.fnname [as c_basename] [-> return annotation]
1872 # square brackets denote optional syntax.
1873 #
1874 # (but we might find a directive first!)
1875 #
1876 # this line is permitted to start with whitespace.
1877 # we'll call this number of spaces F (for "function").
1878
1879 if not line.strip():
1880 return
1881
1882 self.indent.infer(line)
1883
1884 # is it a directive?
1885 fields = shlex.split(line)
1886 directive_name = fields[0]
1887 directive = self.directives.get(directive_name, None)
1888 if directive:
1889 directive(*fields[1:])
1890 return
1891
1892 line, _, returns = line.partition('->')
1893
1894 full_name, _, c_basename = line.partition(' as ')
1895 full_name = full_name.strip()
1896 c_basename = c_basename.strip() or None
1897
1898 if not returns:
1899 return_converter = CReturnConverter()
1900 else:
1901 ast_input = "def x() -> {}: pass".format(returns)
1902 module = None
1903 try:
1904 module = ast.parse(ast_input)
1905 except SyntaxError:
1906 pass
1907 if not module:
1908 fail("Badly-formed annotation for " + full_name + ": " + returns)
1909 try:
1910 name, legacy, kwargs = self.parse_converter(module.body[0].returns)
1911 assert not legacy
1912 if name not in return_converters:
1913 fail("Error: No available return converter called " + repr(name))
1914 return_converter = return_converters[name](**kwargs)
1915 except ValueError:
1916 fail("Badly-formed annotation for " + full_name + ": " + returns)
1917
1918 fields = [x.strip() for x in full_name.split('.')]
1919 function_name = fields.pop()
1920 module, cls = self.clinic._module_and_class(fields)
1921
1922 if not module:
1923 fail("Undefined module used in declaration of " + repr(full_name.strip()) + ".")
1924 self.function = Function(name=function_name, full_name=full_name, module=module, cls=cls, c_basename=c_basename,
1925 return_converter=return_converter, kind=self.kind, coexist=self.coexist)
1926 self.block.signatures.append(self.function)
1927 self.next(self.state_parameters_start)
1928
1929 # Now entering the parameters section. The rules, formally stated:
1930 #
1931 # * All lines must be indented with spaces only.
1932 # * The first line must be a parameter declaration.
1933 # * The first line must be indented.
1934 # * This first line establishes the indent for parameters.
1935 # * We'll call this number of spaces P (for "parameter").
1936 # * Thenceforth:
1937 # * Lines indented with P spaces specify a parameter.
1938 # * Lines indented with > P spaces are docstrings for the previous
1939 # parameter.
1940 # * We'll call this number of spaces D (for "docstring").
1941 # * All subsequent lines indented with >= D spaces are stored as
1942 # part of the per-parameter docstring.
1943 # * All lines will have the first D spaces of the indent stripped
1944 # before they are stored.
1945 # * It's illegal to have a line starting with a number of spaces X
1946 # such that P < X < D.
1947 # * A line with < P spaces is the first line of the function
1948 # docstring, which ends processing for parameters and per-parameter
1949 # docstrings.
1950 # * The first line of the function docstring must be at the same
1951 # indent as the function declaration.
1952 # * It's illegal to have any line in the parameters section starting
1953 # with X spaces such that F < X < P. (As before, F is the indent
1954 # of the function declaration.)
1955 #
1956 ##############
1957 #
1958 # Also, currently Argument Clinic places the following restrictions on groups:
1959 # * Each group must contain at least one parameter.
1960 # * Each group may contain at most one group, which must be the furthest
1961 # thing in the group from the required parameters. (The nested group
1962 # must be the first in the group when it's before the required
1963 # parameters, and the last thing in the group when after the required
1964 # parameters.)
1965 # * There may be at most one (top-level) group to the left or right of
1966 # the required parameters.
1967 # * You must specify a slash, and it must be after all parameters.
1968 # (In other words: either all parameters are positional-only,
1969 # or none are.)
1970 #
1971 # Said another way:
1972 # * Each group must contain at least one parameter.
1973 # * All left square brackets before the required parameters must be
1974 # consecutive. (You can't have a left square bracket followed
1975 # by a parameter, then another left square bracket. You can't
1976 # have a left square bracket, a parameter, a right square bracket,
1977 # and then a left square bracket.)
1978 # * All right square brackets after the required parameters must be
1979 # consecutive.
1980 #
1981 # These rules are enforced with a single state variable:
1982 # "parameter_state". (Previously the code was a miasma of ifs and
1983 # separate boolean state variables.) The states are:
1984 #
1985 # [ [ a, b, ] c, ] d, e, f, [ g, h, [ i ] ] / <- line
1986 # 01 2 3 4 5 6 <- state transitions
1987 #
1988 # 0: ps_start. before we've seen anything. legal transitions are to 1 or 3.
1989 # 1: ps_left_square_before. left square brackets before required parameters.
1990 # 2: ps_group_before. in a group, before required parameters.
1991 # 3: ps_required. required parameters. (renumber left groups!)
1992 # 4: ps_group_after. in a group, after required parameters.
1993 # 5: ps_right_square_after. right square brackets after required parameters.
1994 # 6: ps_seen_slash. seen slash.
1995 ps_start, ps_left_square_before, ps_group_before, ps_required, \
1996 ps_group_after, ps_right_square_after, ps_seen_slash = range(7)
1997
1998 def state_parameters_start(self, line):
1999 if self.ignore_line(line):
2000 return
2001
2002 # if this line is not indented, we have no parameters
2003 if not self.indent.infer(line):
2004 return self.next(self.state_function_docstring, line)
2005
2006 return self.next(self.state_parameter, line)
2007
2008
2009 def to_required(self):
2010 """
2011 Transition to the "required" parameter state.
2012 """
2013 if self.parameter_state != self.ps_required:
2014 self.parameter_state = self.ps_required
2015 for p in self.function.parameters.values():
2016 p.group = -p.group
2017
2018 def state_parameter(self, line):
2019 if self.ignore_line(line):
2020 return
2021
2022 assert self.indent.depth == 2
2023 indent = self.indent.infer(line)
2024 if indent == -1:
2025 # we outdented, must be to definition column
2026 return self.next(self.state_function_docstring, line)
2027
2028 if indent == 1:
2029 # we indented, must be to new parameter docstring column
2030 return self.next(self.state_parameter_docstring_start, line)
2031
2032 line = line.lstrip()
2033
2034 if line in ('*', '/', '[', ']'):
2035 self.parse_special_symbol(line)
2036 return
2037
2038 if self.parameter_state in (self.ps_start, self.ps_required):
2039 self.to_required()
2040 elif self.parameter_state == self.ps_left_square_before:
2041 self.parameter_state = self.ps_group_before
2042 elif self.parameter_state == self.ps_group_before:
2043 if not self.group:
2044 self.to_required()
2045 elif self.parameter_state == self.ps_group_after:
2046 pass
2047 else:
2048 fail("Function " + self.function.name + " has an unsupported group configuration. (Unexpected state " + str(self.parameter_state) + ")")
2049
2050 ast_input = "def x({}): pass".format(line)
2051 module = None
2052 try:
2053 module = ast.parse(ast_input)
2054 except SyntaxError:
2055 pass
2056 if not module:
Larry Hastingsef3b1fb2013-10-22 23:26:23 -07002057 fail("Function " + self.function.name + " has an invalid parameter declaration:\n\t" + line)
Larry Hastings31826802013-10-19 00:09:25 -07002058
2059 function_args = module.body[0].args
2060 parameter = function_args.args[0]
2061
2062 if function_args.defaults:
2063 expr = function_args.defaults[0]
2064 # mild hack: explicitly support NULL as a default value
2065 if isinstance(expr, ast.Name) and expr.id == 'NULL':
2066 value = NULL
2067 else:
2068 value = ast.literal_eval(expr)
2069 else:
2070 value = unspecified
2071
2072 parameter_name = parameter.arg
2073 name, legacy, kwargs = self.parse_converter(parameter.annotation)
2074 dict = legacy_converters if legacy else converters
2075 legacy_str = "legacy " if legacy else ""
2076 if name not in dict:
2077 fail('{} is not a valid {}converter'.format(name, legacy_str))
2078 converter = dict[name](parameter_name, self.function, value, **kwargs)
2079
2080 kind = inspect.Parameter.KEYWORD_ONLY if self.keyword_only else inspect.Parameter.POSITIONAL_OR_KEYWORD
2081 p = Parameter(parameter_name, kind, function=self.function, converter=converter, default=value, group=self.group)
2082 self.function.parameters[parameter_name] = p
2083
2084 def parse_converter(self, annotation):
2085 if isinstance(annotation, ast.Str):
2086 return annotation.s, True, {}
2087
2088 if isinstance(annotation, ast.Name):
2089 return annotation.id, False, {}
2090
2091 assert isinstance(annotation, ast.Call)
2092
2093 name = annotation.func.id
2094 kwargs = {node.arg: ast.literal_eval(node.value) for node in annotation.keywords}
2095 return name, False, kwargs
2096
2097 def parse_special_symbol(self, symbol):
2098 if self.parameter_state == self.ps_seen_slash:
2099 fail("Function " + self.function.name + " specifies " + symbol + " after /, which is unsupported.")
2100
2101 if symbol == '*':
2102 if self.keyword_only:
2103 fail("Function " + self.function.name + " uses '*' more than once.")
2104 self.keyword_only = True
2105 elif symbol == '[':
2106 if self.parameter_state in (self.ps_start, self.ps_left_square_before):
2107 self.parameter_state = self.ps_left_square_before
2108 elif self.parameter_state in (self.ps_required, self.ps_group_after):
2109 self.parameter_state = self.ps_group_after
2110 else:
2111 fail("Function " + self.function.name + " has an unsupported group configuration. (Unexpected state " + str(self.parameter_state) + ")")
2112 self.group += 1
2113 elif symbol == ']':
2114 if not self.group:
2115 fail("Function " + self.function.name + " has a ] without a matching [.")
2116 if not any(p.group == self.group for p in self.function.parameters.values()):
2117 fail("Function " + self.function.name + " has an empty group.\nAll groups must contain at least one parameter.")
2118 self.group -= 1
2119 if self.parameter_state in (self.ps_left_square_before, self.ps_group_before):
2120 self.parameter_state = self.ps_group_before
2121 elif self.parameter_state in (self.ps_group_after, self.ps_right_square_after):
2122 self.parameter_state = self.ps_right_square_after
2123 else:
2124 fail("Function " + self.function.name + " has an unsupported group configuration. (Unexpected state " + str(self.parameter_state) + ")")
2125 elif symbol == '/':
2126 # ps_required is allowed here, that allows positional-only without option groups
2127 # to work (and have default values!)
2128 if (self.parameter_state not in (self.ps_required, self.ps_right_square_after, self.ps_group_before)) or self.group:
2129 fail("Function " + self.function.name + " has an unsupported group configuration. (Unexpected state " + str(self.parameter_state) + ")")
2130 if self.keyword_only:
2131 fail("Function " + self.function.name + " mixes keyword-only and positional-only parameters, which is unsupported.")
2132 self.parameter_state = self.ps_seen_slash
2133 # fixup preceeding parameters
2134 for p in self.function.parameters.values():
2135 if p.kind != inspect.Parameter.POSITIONAL_OR_KEYWORD:
2136 fail("Function " + self.function.name + " mixes keyword-only and positional-only parameters, which is unsupported.")
2137 p.kind = inspect.Parameter.POSITIONAL_ONLY
2138
2139 def state_parameter_docstring_start(self, line):
2140 self.parameter_docstring_indent = len(self.indent.margin)
2141 assert self.indent.depth == 3
2142 return self.next(self.state_parameter_docstring, line)
2143
2144 # every line of the docstring must start with at least F spaces,
2145 # where F > P.
2146 # these F spaces will be stripped.
2147 def state_parameter_docstring(self, line):
2148 stripped = line.strip()
2149 if stripped.startswith('#'):
2150 return
2151
2152 indent = self.indent.measure(line)
2153 if indent < self.parameter_docstring_indent:
2154 self.indent.infer(line)
2155 assert self.indent.depth < 3
2156 if self.indent.depth == 2:
2157 # back to a parameter
2158 return self.next(self.state_parameter, line)
2159 assert self.indent.depth == 1
2160 return self.next(self.state_function_docstring, line)
2161
2162 assert self.function.parameters
2163 last_parameter = next(reversed(list(self.function.parameters.values())))
2164
2165 new_docstring = last_parameter.docstring
2166
2167 if new_docstring:
2168 new_docstring += '\n'
2169 if stripped:
2170 new_docstring += self.indent.dedent(line)
2171
2172 last_parameter.docstring = new_docstring
2173
2174 # the final stanza of the DSL is the docstring.
2175 def state_function_docstring(self, line):
2176 if self.group:
2177 fail("Function " + self.function.name + " has a ] without a matching [.")
2178
2179 stripped = line.strip()
2180 if stripped.startswith('#'):
2181 return
2182
2183 new_docstring = self.function.docstring
2184 if new_docstring:
2185 new_docstring += "\n"
2186 if stripped:
2187 line = self.indent.dedent(line).rstrip()
2188 else:
2189 line = ''
2190 new_docstring += line
2191 self.function.docstring = new_docstring
2192
2193 def format_docstring(self):
2194 f = self.function
2195
2196 add, output = text_accumulator()
2197 parameters = list(f.parameters.values())
2198
2199 ##
2200 ## docstring first line
2201 ##
2202
2203 add(f.full_name)
2204 add('(')
2205
2206 # populate "right_bracket_count" field for every parameter
2207 if parameters:
2208 # for now, the only way Clinic supports positional-only parameters
2209 # is if all of them are positional-only.
2210 positional_only_parameters = [p.kind == inspect.Parameter.POSITIONAL_ONLY for p in parameters]
2211 if parameters[0].kind == inspect.Parameter.POSITIONAL_ONLY:
2212 assert all(positional_only_parameters)
2213 for p in parameters:
2214 p.right_bracket_count = abs(p.group)
2215 else:
2216 # don't put any right brackets around non-positional-only parameters, ever.
2217 for p in parameters:
2218 p.right_bracket_count = 0
2219
2220 right_bracket_count = 0
2221
2222 def fix_right_bracket_count(desired):
2223 nonlocal right_bracket_count
2224 s = ''
2225 while right_bracket_count < desired:
2226 s += '['
2227 right_bracket_count += 1
2228 while right_bracket_count > desired:
2229 s += ']'
2230 right_bracket_count -= 1
2231 return s
2232
2233 added_star = False
2234 add_comma = False
2235
2236 for p in parameters:
2237 assert p.name
2238
2239 if p.is_keyword_only() and not added_star:
2240 added_star = True
2241 if add_comma:
2242 add(', ')
2243 add('*')
2244
2245 a = [p.name]
2246 if p.converter.is_optional():
2247 a.append('=')
2248 value = p.converter.default
2249 a.append(p.converter.doc_default)
2250 s = fix_right_bracket_count(p.right_bracket_count)
2251 s += "".join(a)
2252 if add_comma:
2253 add(', ')
2254 add(s)
2255 add_comma = True
2256
2257 add(fix_right_bracket_count(0))
2258 add(')')
2259
2260 if f.return_converter.doc_default:
2261 add(' -> ')
2262 add(f.return_converter.doc_default)
2263
2264 docstring_first_line = output()
2265
2266 # now fix up the places where the brackets look wrong
2267 docstring_first_line = docstring_first_line.replace(', ]', ',] ')
2268
2269 # okay. now we're officially building the
2270 # "prototype" section.
2271 add(docstring_first_line)
2272
2273 # create substitution text for {parameters}
2274 for p in parameters:
2275 if not p.docstring.strip():
2276 continue
2277 add('\n')
2278 add(" ")
2279 add(p.name)
2280 add('\n')
2281 add(textwrap.indent(rstrip_lines(p.docstring.rstrip()), " "))
2282 prototype = output()
2283
2284 ##
2285 ## docstring body
2286 ##
2287
2288 docstring = f.docstring.rstrip()
2289 lines = [line.rstrip() for line in docstring.split('\n')]
2290
2291 # Enforce the summary line!
2292 # The first line of a docstring should be a summary of the function.
2293 # It should fit on one line (80 columns? 79 maybe?) and be a paragraph
2294 # by itself.
2295 #
2296 # Argument Clinic enforces the following rule:
2297 # * either the docstring is empty,
2298 # * or it must have a summary line.
2299 #
2300 # Guido said Clinic should enforce this:
2301 # http://mail.python.org/pipermail/python-dev/2013-June/127110.html
2302
2303 if len(lines) >= 2:
2304 if lines[1]:
2305 fail("Docstring for " + f.full_name + " does not have a summary line!\n" +
2306 "Every non-blank function docstring must start with\n" +
2307 "a single line summary followed by an empty line.")
2308 elif len(lines) == 1:
2309 # the docstring is only one line right now--the summary line.
2310 # add an empty line after the summary line so we have space
2311 # between it and the {prototype} we're about to add.
2312 lines.append('')
2313
2314 prototype_marker_count = len(docstring.split('{prototype}')) - 1
2315 if prototype_marker_count:
2316 fail('You may not specify {prototype} in a docstring!')
2317 # insert *after* the summary line
2318 lines.insert(2, '{prototype}\n')
2319
2320 docstring = "\n".join(lines)
2321
2322 add(docstring)
2323 docstring = output()
2324
2325 docstring = linear_format(docstring, prototype=prototype)
2326 docstring = docstring.rstrip()
2327
2328 return docstring
2329
2330 def state_terminal(self, line):
2331 """
2332 Called when processing the block is done.
2333 """
2334 assert not line
2335
2336 if not self.function:
2337 return
2338
2339 if self.keyword_only:
2340 values = self.function.parameters.values()
2341 if not values:
2342 no_parameter_after_star = True
2343 else:
2344 last_parameter = next(reversed(list(values)))
2345 no_parameter_after_star = last_parameter.kind != inspect.Parameter.KEYWORD_ONLY
2346 if no_parameter_after_star:
2347 fail("Function " + self.function.name + " specifies '*' without any parameters afterwards.")
2348
2349 # remove trailing whitespace from all parameter docstrings
2350 for name, value in self.function.parameters.items():
2351 if not value:
2352 continue
2353 value.docstring = value.docstring.rstrip()
2354
2355 self.function.docstring = self.format_docstring()
2356
2357
2358# maps strings to callables.
2359# the callable should return an object
2360# that implements the clinic parser
2361# interface (__init__ and parse).
2362#
2363# example parsers:
2364# "clinic", handles the Clinic DSL
2365# "python", handles running Python code
2366#
2367parsers = {'clinic' : DSLParser, 'python': PythonParser}
2368
2369
2370clinic = None
2371
2372
2373def main(argv):
2374 import sys
2375
2376 if sys.version_info.major < 3 or sys.version_info.minor < 3:
2377 sys.exit("Error: clinic.py requires Python 3.3 or greater.")
2378
2379 import argparse
2380 cmdline = argparse.ArgumentParser()
2381 cmdline.add_argument("-f", "--force", action='store_true')
2382 cmdline.add_argument("-o", "--output", type=str)
2383 cmdline.add_argument("--converters", action='store_true')
2384 cmdline.add_argument("filename", type=str, nargs="*")
2385 ns = cmdline.parse_args(argv)
2386
2387 if ns.converters:
2388 if ns.filename:
2389 print("Usage error: can't specify --converters and a filename at the same time.")
2390 print()
2391 cmdline.print_usage()
2392 sys.exit(-1)
2393 converters = []
2394 return_converters = []
2395 ignored = set("""
2396 add_c_converter
2397 add_c_return_converter
2398 add_default_legacy_c_converter
2399 add_legacy_c_converter
2400 """.strip().split())
2401 module = globals()
2402 for name in module:
2403 for suffix, ids in (
2404 ("_return_converter", return_converters),
2405 ("_converter", converters),
2406 ):
2407 if name in ignored:
2408 continue
2409 if name.endswith(suffix):
2410 ids.append((name, name[:-len(suffix)]))
2411 break
2412 print()
2413
2414 print("Legacy converters:")
2415 legacy = sorted(legacy_converters)
2416 print(' ' + ' '.join(c for c in legacy if c[0].isupper()))
2417 print(' ' + ' '.join(c for c in legacy if c[0].islower()))
2418 print()
2419
2420 for title, attribute, ids in (
2421 ("Converters", 'converter_init', converters),
2422 ("Return converters", 'return_converter_init', return_converters),
2423 ):
2424 print(title + ":")
2425 longest = -1
2426 for name, short_name in ids:
2427 longest = max(longest, len(short_name))
2428 for name, short_name in sorted(ids, key=lambda x: x[1].lower()):
2429 cls = module[name]
2430 callable = getattr(cls, attribute, None)
2431 if not callable:
2432 continue
2433 signature = inspect.signature(callable)
2434 parameters = []
2435 for parameter_name, parameter in signature.parameters.items():
2436 if parameter.kind == inspect.Parameter.KEYWORD_ONLY:
2437 if parameter.default != inspect.Parameter.empty:
2438 s = '{}={!r}'.format(parameter_name, parameter.default)
2439 else:
2440 s = parameter_name
2441 parameters.append(s)
2442 print(' {}({})'.format(short_name, ', '.join(parameters)))
2443 # add_comma = False
2444 # for parameter_name, parameter in signature.parameters.items():
2445 # if parameter.kind == inspect.Parameter.KEYWORD_ONLY:
2446 # if add_comma:
2447 # parameters.append(', ')
2448 # else:
2449 # add_comma = True
2450 # s = parameter_name
2451 # if parameter.default != inspect.Parameter.empty:
2452 # s += '=' + repr(parameter.default)
2453 # parameters.append(s)
2454 # parameters.append(')')
2455
2456 # print(" ", short_name + "".join(parameters))
2457 print()
2458 print("All converters also accept (doc_default=None, required=False).")
2459 print("All return converters also accept (doc_default=None).")
2460 sys.exit(0)
2461
2462 if not ns.filename:
2463 cmdline.print_usage()
2464 sys.exit(-1)
2465
2466 if ns.output and len(ns.filename) > 1:
2467 print("Usage error: can't use -o with multiple filenames.")
2468 print()
2469 cmdline.print_usage()
2470 sys.exit(-1)
2471
2472 for filename in ns.filename:
2473 parse_file(filename, output=ns.output, verify=not ns.force)
2474
2475
2476if __name__ == "__main__":
2477 sys.exit(main(sys.argv[1:]))