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