Commits

larry committed a918d95

User-extension API barely works!

Comments (0)

Files changed (2)

Modules/posixmodule.c

 
 /*[python]
 
-class Clinic:
-  pass
-
-clinic = Clinic()
-
-clinic.write_impl_file()
+# clinic.write_impl_file()
 
 [python]*/
 
+/*[python end:adc83b19e793491b1c6ea0fd8b46cd9f32e592fc]*/
+
 #define PY_SSIZE_T_CLEAN
 
 #include "Python.h"
     #define OS_STAT_DIR_FD_CONVERTER dir_fd_unavailable
 #endif
 
+
 /*[python]
-
-class Clinic:
-  def register_type(self, t):
-    def registerer(cls):
-      return cls
-    return registerer
-
-clinic = Clinic()
-
-# register path type
-
-class ClinicExtension:
-  pass
-
-@clinic.register_type('path_t')
-class ClinicPath(ClinicExtension):
-
-  format_unit = "O&"
-
-  def __init__(self, name):
-    super().__init__(name)
-    self.flags['converter'] = path_converter
-
-  def default(self, s):
-    self.allow_fds = int(default)
-    return "".join([
-        "PATH_T_INITIALIZE(",
-        repr(self.function.name),
-        ", ",
-        "1" if self.nullable else "0",
-        ", ",
-        str(self.allow_fds),
-        ")"
-        ])
-
-  def cleanup(self):
-    return "path_cleanup(&" + self.name + ");"
+@type_map_register
+class CVariable_path_t(CVariable):
+
+    type = "path_t"
+
+    format_unit_map = {
+        frozenset() : "O&",
+        }
+
+    def __init__(self, name, default=unspecified):
+        super().__init__(name, default)
+        self.flags['converter'] = "path_converter"
+
+    def default(self, s):
+        assert s is unspecified
+        allow_fd = str(int(bool(self.flags.get('allow_fd'))))
+        nullable = str(int(bool(self.flags.get('nullable'))))
+
+        return "".join([
+            "PATH_T_INITIALIZE(",
+            '"',
+            clinic.name,
+            '", ',
+            nullable,
+            ", ",
+            allow_fd,
+            ")"
+            ])
+
+    def cleanup(self):
+        return ["path_cleanup(&" + self.name + ");",]
 
 [python]*/
 
 /*[clinic]
 os.stat -> stat result
 
-    path_t path = PATH_T_INITIALIZE("stat", 0, 1);
-    required
-    converter=path_converter
+    path_t path;
+    allow_fd
         Path to be examined; can be string, bytes, or open-file-descriptor int.
 
     int dir_fd = DEFAULT_DIR_FD;
 
 Perform a stat system call on the given path.
 
-  {arguments}
+{arguments}
 
 dir_fd and follow_symlinks may not be implemented
   on your platform.  If they are unavailable, using them will raise a
 "Perform a stat system call on the given path.\n"
 "\n"
 "  path\n"
-"        Path to be examined; can be string, bytes, or open-file-descriptor int.\n"
+"      Path to be examined; can be string, bytes, or open-file-descriptor int.\n"
 "\n"
 "  dir_fd\n"
-"        If not None, it should be a file descriptor open to a directory,\n"
-"        and path should be a relative string; path will then be relative to\n"
-"        that directory.\n"
+"      If not None, it should be a file descriptor open to a directory,\n"
+"      and path should be a relative string; path will then be relative to\n"
+"      that directory.\n"
 "\n"
 "  follow_symlinks\n"
-"        If False, and the last element of the path is a symbolic link,\n"
-"        stat will examine the symbolic link itself instead of the file\n"
-"        the link points to.\n"
+"      If False, and the last element of the path is a symbolic link,\n"
+"      stat will examine the symbolic link itself instead of the file\n"
+"      the link points to.\n"
 "\n"
 "dir_fd and follow_symlinks may not be implemented\n"
 "  on your platform.  If they are unavailable, using them will raise a\n"
 static PyObject *
 os_stat(PyObject *self, PyObject *args, PyObject *kwargs)
 {
+    PyObject *_return_value;
     path_t path = PATH_T_INITIALIZE("stat", 0, 1);
     int dir_fd = DEFAULT_DIR_FD;
     int follow_symlinks = 1;
         path_converter, &path, OS_STAT_DIR_FD_CONVERTER, &dir_fd, &follow_symlinks))
         return NULL;
 
-    return os_stat_impl(self, path, dir_fd, follow_symlinks);
+    _return_value = os_stat_impl(self, path, dir_fd, follow_symlinks);
+    path_cleanup(&path);
+    return _return_value;
 }
 
 static PyObject *
 os_stat_impl(PyObject *self, path_t path, int dir_fd, int follow_symlinks)
-/*[clinic end:f3e6b328245207c240825782d908d3ff3a9c11c0]*/
+/*[clinic end:1c5d983e1320d541d0ad6f62dd99a33d93ba25f2]*/
 {
     PyObject *return_value = posix_do_stat("stat", &path, dir_fd, follow_symlinks);
-    path_cleanup(&path);
     return return_value;
 }
 
 /*[clinic]
 os.access
 
-    path_t path = PATH_T_INITIALIZE("access", 0, 1);
-    required
-    converter=path_converter
+    path_t path;
+    allow_fd
         Path to be tested; can be string, bytes, or open-file-descriptor int.
 
     int mode;
 Use the real uid/gid to test for access to a path.
 Returns True if granted, False otherwise.
 
-  {arguments}
+{arguments}
 
 dir_fd, effective_ids, and follow_symlinks may not be implemented
   on your platform.  If they are unavailable, using them will raise a
 "Returns True if granted, False otherwise.\n"
 "\n"
 "  path\n"
-"        Path to be tested; can be string, bytes, or open-file-descriptor int.\n"
+"      Path to be tested; can be string, bytes, or open-file-descriptor int.\n"
 "\n"
 "  mode\n"
-"        Operating-system mode bitfield.  Can be F_OK to test existence,\n"
-"        or the inclusive-OR of R_OK, W_OK, and X_OK.\n"
+"      Operating-system mode bitfield.  Can be F_OK to test existence,\n"
+"      or the inclusive-OR of R_OK, W_OK, and X_OK.\n"
 "\n"
 "  dir_fd\n"
-"        If not None, it should be a file descriptor open to a directory,\n"
-"        and path should be relative; path will then be relative to that\n"
-"        directory.\n"
+"      If not None, it should be a file descriptor open to a directory,\n"
+"      and path should be relative; path will then be relative to that\n"
+"      directory.\n"
 "\n"
 "  effective_ids\n"
-"        If True, access will use the effective uid/gid instead of\n"
-"        the real uid/gid.\n"
+"      If True, access will use the effective uid/gid instead of\n"
+"      the real uid/gid.\n"
 "\n"
 "  follow_symlinks\n"
-"        If False, and the last element of the path is a symbolic link,\n"
-"        access will examine the symbolic link itself instead of the file\n"
-"        the link points to.\n"
+"      If False, and the last element of the path is a symbolic link,\n"
+"      access will examine the symbolic link itself instead of the file\n"
+"      the link points to.\n"
 "\n"
 "dir_fd, effective_ids, and follow_symlinks may not be implemented\n"
 "  on your platform.  If they are unavailable, using them will raise a\n"
 static PyObject *
 os_access(PyObject *self, PyObject *args, PyObject *kwargs)
 {
+    PyObject *_return_value;
     path_t path = PATH_T_INITIALIZE("access", 0, 1);
     int mode;
     int dir_fd = DEFAULT_DIR_FD;
         path_converter, &path, &mode, OS_ACCESS_DIR_FD_CONVERTER, &dir_fd, &effective_ids, &follow_symlinks))
         return NULL;
 
-    return os_access_impl(self, path, mode, dir_fd, effective_ids, follow_symlinks);
+    _return_value = os_access_impl(self, path, mode, dir_fd, effective_ids, follow_symlinks);
+    path_cleanup(&path);
+    return _return_value;
 }
 
 static PyObject *
 os_access_impl(PyObject *self, path_t path, int mode, int dir_fd, int effective_ids, int follow_symlinks)
-/*[clinic end:fc0d31ac4162e46966d2493cae19efdea34f583e]*/
+/*[clinic end:962c425ca71c25349ecc9a44ea0c4ee32f984545]*/
 {
     PyObject *return_value = NULL;
 
 #ifndef HAVE_FACCESSAT
 exit:
 #endif
-    path_cleanup(&path);
     return return_value;
 }
 
 unspecified = Unspecified()
 
 
+type_map = {}
 
-# This "type_map" thing is just for the Clinic prototype.
-# It maps Clinic type declarations to PyArg_Parse format units.
-# I expect to replace it before this ever ships in a Python release,
-# hopefully something based on a new more granular
-# argument parsing library.
-#
-# Syntax of type_map:
-#   <C type> <options> = <format unit>
-# Declarations are one per line.
-#
-# <C type> is first and can contain spaces.  (In truth, anything the
-#	parser doesn't recognize is appended to the C type.)
-#
-# options are in the middle, and can be any / all / none of the following:
-#
-#		bitwise
-#       converter
-#		encoding
-#		immutable
-#		length
-#		nullable
-#		zeroes
-#		( one or more python types, between parentheses, separated by whitespace )
-#
-# with the exception of stuff inside parentheses, all these options
-# are boolean flags and default to false.
-# ("converter" and "encoding" just indicate whether or not the user
-#  specified a converter / encoding.  for the purposes of the type map
-#  we don't care what the converter or encoding actually is.)
-#
-# The line ends with
-#		= <format unit>
-#   which is the format unit used with PyArg_ParseTuple
-#
-# Note: for convenience's sakes, "const char *" by default
-#   maps to strings, not bytes.
-#
-#
-# Unimplemented so far:
-#   w*
+def type_map_register(cls):
+	type_map[cls.type] = cls
+	return cls
 
-type_map = """
-PyObject * = O
-void * converter = O&
-
-Py_UNICODE * = u
-Py_UNICODE * length = u#
-Py_UNICODE * nullable = Z
-Py_UNICODE * length nullable = Z#
-
-const char * = s
-const char * nullable = z
-
-char * encoding = es
-char * encoding (str bytes bytearray) = et
-char * encoding length = es#
-char * encoding length (str bytes bytearray) = et#
-
-const char * (bytes) = y
-Py_buffer (bytes bytearray buffer) = y*
-const char * length (bytes buffer) = y#
-
-Py_buffer zeroes (str bytes bytearray buffer) = s*
-Py_buffer nullable zeroes (str bytes bytearray buffer) = z*
-const char * length zeroes (str bytes buffer) = s#
-const char * length nullable zeroes (str bytes buffer) = z#
-
-PyUnicodeObject * = U
-
-PyBytesObject * = S
-
-PyBytesArrayObject * = Y
-
-unsigned char = b
-unsigned char bitwise = B
-short = h
-short int = h
-unsigned short bitwise = H
-unsigned short int bitwise = H
-int = i
-unsigned int bitwise = I
-long = l
-long int = l
-unsigned long bitwise = k
-unsigned long int bitwise = k
-PY_LONG_LONG = L
-unsigned PY_LONG_LONG bitwise = K
-Py_ssize_t = n
-char (bytes bytearray) = c
-char = C
-int (bool) = p
-
-float = f
-double = d
-Py_complex * = D
-
-"""
-
-format_unit_map = {}
-
-options = collections.OrderedDict()
-for key in "bitwise converter encoding immutable length nullable zeroes".split():
-	options[key] = False
-
-def get_format_unit(c_type,
-	bitwise=False, converter=False, encoding=False, immutable=False,
-	length=False, nullable=False, zeroes=False, py_types=()):
-	if converter:
-		c_type = "void *"
-	keys = (c_type, bitwise, converter, encoding, immutable, length, nullable, zeroes, py_types)
-	keystr = "c_type, bitwise, converter, encoding, immutable, length, nullable, zeroes, py_types".split()
-	d = format_unit_map
-	for key, name in zip(keys, keystr):
-		before_d = d
-		d = d.get(key, None)
-		if not d:
-			break
-	return d
-
-def set_format_unit(format_unit, c_type,
-	bitwise=False, converter=False, encoding=False, immutable=False,
-	length=False, nullable=False, zeroes=False, py_types=()):
-	if converter:
-		c_type = "void *"
-	keys, last_key = (c_type, bitwise, converter, encoding, immutable, length, nullable, zeroes), py_types
-	d = format_unit_map
-	for key in keys:
-		d = d.setdefault(key, {})
-	d[last_key] = format_unit
-
-# conversion:
-
-for line in type_map.split("\n"):
-	line = line.strip()
-	if not line:
-		continue
-	fields, _, format_unit = line.rpartition('=')
-	format_unit = format_unit.strip()
-	bitwise = converter = encoding = immutable = False
-	length = nullable = zeroes = False
-	c_type = []
-	types = []
-	in_types = False
-	line_options = options.copy()
-	for field in fields.split():
-		if not field:
-			continue
-
-		if in_types:
-			if ')' in field:
-				field, _, should_be_empty = field.partition(')')
-				assert not should_be_empty, "don't abuse the lame type_map parser"
-				in_types = False
-			if field:
-				types.append(field)
-			continue
-		if field in line_options:
-			if line_options[field]:
-				sys.exit("Error: Can't specify " + field + " twice.\n\t" + line)
-			line_options[field] = True
-			continue
-		if field.startswith('('):
-			assert not in_types
-			field = field[1:].strip()
-			if ')' in field:
-				field, _, should_be_empty = field.partition(')')
-				assert not should_be_empty, "don't abuse the lame type_map parser"
-			else:
-				in_types = True
-			if field:
-				types.append(field)
-			continue
-		c_type.append(field)
-	assert c_type, "no c_type specified for type_map initializer line"
-	c_type = " ".join(c_type)
-	types = tuple(sorted(types))
-	splat = tuple(line_options.values()) + (types,)
-	set_format_unit(format_unit, c_type, *splat)
-
-
-value_to_cvalue_map = {
-	NULL: "NULL",
-	None: "Py_None",
-}
-
-def value_to_cvalue(v):
-	return value_to_cvalue_map.get(v, v)
-
+def type_map_alias(type):
+	def decorator(cls):
+		type_map[type] = cls
+		return cls
+	return decorator
 
 
 class CVariable:
 	"""
 	An individual C variable required by an Argument.
 	(Some Arguments require more than one.)
+
+	When subclassing, the most relevant things to override are:
+		type
+		format_unit_map
+		format_unit_flags
+		default
+		cleanup
 	"""
-	name = ""
-	c_type = ""
-	default = unspecified
 
-	def __init__(self, c_type, name, default=unspecified):
-		self.c_type = c_type
+	type = None
+
+	def __init__(self, name, default=unspecified):
 		self.name = name
-		self.default = default
+		self.str_default = default
+		self.flags = {}
 
 	def impl_argument(self):
 		"""
 		The C code to pass this argument in to the impl function.
 		Returns an iterable.
 		"""
-		assert self.name and self.c_type
+		assert self.name and self.type
 		return (self.name,)
 
 	def keyword(self):
 		The value to add to the 'keywords' array for PyArg_ParseTupleAndKeywords.
 		Returns an iterable.
 		"""
-		assert self.name and self.c_type
+		assert self.name and self.type
 		return ('"' + self.name + '"',)
 
 	def parse_argument(self):
 		The C code to pass this argument in to PyArg_ParseTuple &c.
 		Returns an iterable.
 		"""
-		assert self.name and self.c_type
+		assert self.name and self.type
 		return ("&" + self.name,)
 
 	def prototype(self):
 		The C code to define this variable as an argument to a function.
 		Returns an iterable.
 		"""
-		assert self.name and self.c_type
-		prototype = [self.c_type]
-		if not self.c_type.endswith('*'):
+		assert self.name and self.type
+		prototype = [self.type]
+		if not self.type.endswith('*'):
 			prototype.append(" ")
 		prototype.append(self.name)
 		return ("".join(prototype),)
 
+	value_to_cvalue_map = {
+		NULL: "NULL",
+		None: "Py_None",
+	}
+
+	def default(self, s):
+		"""
+		Returns a string to be used as the initializer for this variable, or None if no initializer is needed.
+		s is a Python value.
+		If s is the special value 'unspecified', no default was specified.
+		"""
+		if s is unspecified:
+			return None
+		return self.value_to_cvalue_map.get(s, s)
+
 	def declaration(self):
 		"""
 		The C statement to declare this variable.
 		Returns an iterable.
 		"""
 		declaration = list(self.prototype())
-		if self.default != unspecified:
-			declaration.append(" = {}".format(self.default))
+		default = self.default(self.str_default)
+		if default:
+			declaration.append(" = ")
+			declaration.append(default)
 		declaration.append(";")
 		return ("".join(declaration),)
 
+	def cleanup(self):
+		"""
+		The C statements required to clean up after this variable.
+		Returns an iterable.
+		(If no cleanup is necessary, returns an empty iterable.)
+		"""
+		return ()
+
+	# should be a map of frozenset(flags) -> string
+	# the string represent "true" flags
+	# if there are also associated "types", then the key becomes a tuple
+	#   first element is frozenset(flags)
+	#   second element is frozenset(types)
+	format_unit_map = {}
+
+	# the set of flags get_format_unit() will pay attention
+	# to when computing the format unit.
+	format_unit_flags = frozenset("bitwise encoding immutable length nullable zeroes".split())
+
+	@property
+	def format_unit(self):
+		flags = dict(self.flags)
+		if 'types' in flags:
+			types = frozenset(flags['types'].split())
+			del flags['types']
+		else:
+			types = None
+
+		key = set()
+		for flag in self.format_unit_flags:
+			if flags.get(flag):
+				key.add(flag)
+		key = frozenset(key)
+		if types:
+			key = (key, types)
+		format_unit = self.format_unit_map.get(key)
+		if not format_unit:
+			sys.exit("No valid format unit for " + self.__class__.__name__ + " " + self.name + " (no match for flags " + repr(key) + ")")
+		return format_unit
+
+
+class CVariable_converter(CVariable):
+
+	format_unit_map = {
+		frozenset() : "O&",
+		}
+
+
+@type_map_register
+class CVariable_PyObject_star(CVariable):
+	"""
+	PyObject * = O
+	"""
+
+	type = "PyObject *"
+
+	format_unit_map = {
+		frozenset() : "O",
+		}
+
+
+@type_map_register
+class CVariable_Py_UNICODE_star(CVariable):
+	"""
+	Py_UNICODE * = u
+	Py_UNICODE * length = u#
+	Py_UNICODE * nullable = Z
+	Py_UNICODE * length nullable = Z#
+	"""
+
+	type = "Py_UNICODE *"
+
+	format_unit_map = {
+		frozenset() : "u",
+		frozenset(('length',)) : "u#",
+		frozenset(('nullable',)) : "Z",
+		frozenset(('length', 'nullable')) : "Z#",
+		}
+
+@type_map_register
+class CVariable_char_star(CVariable):
+	"""
+	char * encoding = es
+	char * encoding (str bytes bytearray) = et
+	char * encoding length = es#
+	char * encoding length (str bytes bytearray) = et#
+	"""
+
+	type = "char *"
+
+	str_types = frozenset(('str', 'bytes', 'bytearray'))
+
+	format_unit_map = {
+		frozenset(('encoding',)) : "es",
+		frozenset(('encoding', 'length')) : "es#",
+		(frozenset(('encoding',)), str_types) : "et",
+		(frozenset(('encoding', 'length')), str_types) : "et#",
+		}
+	
+@type_map_register
+class CVariable_const_char_star(CVariable):
+	"""
+	const char * = s
+	const char * nullable = z
+	const char * (bytes) = y
+	const char * length (bytes buffer) = y#
+	const char * length zeroes (str bytes buffer) = s#
+	const char * length nullable zeroes (str bytes buffer) = z#
+	"""
+
+	type = "const char *"
+
+	types_bytes = frozenset(('bytes,'))
+	types_bytes_buffer = frozenset(('buffer,')) | types_bytes
+	types_str_bytes_buffer = frozenset(('str,')) | types_bytes_buffer
+
+	format_unit_map = {
+		frozenset() : "s",
+		frozenset(('nullable',)) : "z",
+		(frozenset(), types_bytes) : "y",
+		(frozenset(('length',)), types_bytes_buffer) : "y#",
+
+		(frozenset(('length', 'zeroes')), types_str_bytes_buffer) : "s#",
+		(frozenset(('length', 'nullable', 'zeroes')), types_str_bytes_buffer) : "z#",
+		}
+
+@type_map_register
+class CVariable_Py_buffer(CVariable):
+	"""
+	Py_buffer (bytes bytearray buffer) = y*
+	Py_buffer zeroes (str bytes bytearray buffer) = s*
+	Py_buffer nullable zeroes (str bytes bytearray buffer) = z*
+	"""
+
+	type = 'Py_buffer'
+
+	bytes_types = frozenset(('bytes', 'bytearray', 'buffer'))
+	bytes_types_and_str = frozenset(('str',)) | bytes_types
+
+	format_unit_map = {
+		(frozenset(), bytes_types) : "y*",
+		(frozenset(('zeroes',)), bytes_types_and_str) : "s*",
+		(frozenset(('nullable', 'zeroes',)), bytes_types_and_str) : "z*",
+		}
+
+
+@type_map_register
+class CVariable_PyUnicodeObject_star(CVariable):
+	"""
+	PyUnicodeObject * = U
+	"""
+
+	type = 'PyUnicodeObject *'
+
+	format_unit_map = {
+		frozenset() : "U",
+		}
+
+
+@type_map_register
+class CVariable_PyBytesObject_star(CVariable):
+	"""
+	PyBytesObject * = S
+	"""
+
+	type = 'PyBytesObject *'
+
+	format_unit_map = {
+		frozenset() : "S",
+		}
+
+
+@type_map_register
+class CVariable_PyBytesArrayObject_star(CVariable):
+	"""
+	PyBytesArrayObject * = Y
+	"""
+
+	type = 'PyBytesArrayObject *'
+
+	format_unit_map = {
+		frozenset() : "Y",
+		}
+
+@type_map_register
+class CVariable_unsigned_char(CVariable):
+	"""
+	unsigned char = b
+	unsigned char bitwise = B
+	"""
+
+	type = 'unsigned char'
+
+	format_unit_map = {
+		frozenset() : "b",
+		frozenset(('bitwise',)) : "B",
+		}
+
+@type_map_register
+@type_map_alias('short int')
+@type_map_alias('signed short')
+@type_map_alias('signed short int')
+class CVariable_short(CVariable):
+	"""
+	short = h
+	"""
+
+	type = 'short'
+
+	format_unit_map = {
+		frozenset() : "h",
+		}
+
+
+
+@type_map_register
+@type_map_alias('unsigned short int')
+class CVariable_unsigned_short(CVariable):
+	"""
+	unsigned short bitwise = H
+	"""
+
+	type = 'unsigned short'
+
+	format_unit_map = {
+		frozenset(('bitwise',)) : "H",
+		}
+
+
+@type_map_register
+@type_map_alias('signed int')
+class CVariable_int(CVariable):
+	"""
+	int = i
+	int (bool) = p
+	"""
+
+	type = 'int'
+
+	format_unit_map = {
+		frozenset() : "i",
+		(frozenset(), frozenset(('bool',))) : "p",
+		}
+
+@type_map_register
+@type_map_alias('unsigned')
+class CVariable_unsigned_int(CVariable):
+	"""
+	unsigned int bitwise = I
+	"""
+
+	type = 'unsigned int'
+
+	format_unit_map = {
+		frozenset(('bitwise',)) : "I",
+		}
+
+@type_map_register
+@type_map_alias('signed long')
+@type_map_alias('long int')
+@type_map_alias('signed long int')
+class CVariable_long(CVariable):
+	"""
+	long = l
+	"""
+
+	type = 'long'
+
+	format_unit_map = {
+		frozenset() : "l",
+		}
+
+@type_map_register
+@type_map_alias('unsigned long int')
+class CVariable_unsigned_long(CVariable):
+	"""
+	unsigned long bitwise = k
+	"""
+
+	type = 'unsigned long'
+
+	format_unit_map = {
+		frozenset(('bitwise',)) : "k",
+		}
+
+
+@type_map_register
+class CVariable_PY_LONG_LONG(CVariable):
+	"""
+	PY_LONG_LONG = L
+	"""
+
+	type = 'PY_LONG_LONG'
+
+	format_unit_map = {
+		frozenset() : "L",
+		}
+
+@type_map_register
+class CVariable_unsigned_PY_LONG_LONG(CVariable):
+	"""
+	unsigned PY_LONG_LONG bitwise = K
+	"""
+
+	type = 'unsigned PY_LONG_LONG'
+
+	format_unit_map = {
+		frozenset(('bitwise',)) : "K",
+		}
+
+
+
+
+@type_map_register
+class CVariable_Py_ssize_t(CVariable):
+	"""
+	Py_ssize_t = n
+	"""
+
+	type = 'Py_ssize_t'
+
+	format_unit_map = {
+		frozenset() : "n",
+		}
+
+@type_map_register
+class CVariable_char(CVariable):
+	"""
+	char = C
+	char (bytes bytearray) = c
+	"""
+
+	type = 'char'
+
+	format_unit_map = {
+		frozenset() : "C",
+		(frozenset(), frozenset(('bytes', 'bytearray'))) : "c",
+		}
+
+@type_map_register
+class CVariable_float(CVariable):
+	"""
+	float = f
+	"""
+
+	type = 'float'
+
+	format_unit_map = {
+		frozenset() : "f",
+		}
+
+
+
+@type_map_register
+class CVariable_double(CVariable):
+	"""
+	double = d
+	"""
+
+	type = 'double'
+
+	format_unit_map = {
+		frozenset() : "d",
+		}
+
+@type_map_register
+class CVariable_Py_complex_star(CVariable):
+	"""
+	Py_complex * = D
+	"""
+
+	type = 'Py_complex *'
+
+	format_unit_map = {
+		frozenset() : "D",
+		}
+
 
 class CHardCodedParserArgument(CVariable):
 	def __init__(self, argument):
 	def declaration(self):
 		return self._from_variables("declaration")
 
+	def cleanup(self):
+		return self._from_variables("cleanup")
+
 	def format_unit(self):
 		return ''
 		
 
 
 class Argument(ArgumentBase):
-	def __init__(self, c_type, name, line, default=unspecified):
-		super().__init__()
-		self.c_type = c_type
+	def __init__(self, c_type, name, flags, line, default=unspecified):
+		self.variables = []
+
+		if 'converter' in flags:
+			type = CVariable_converter
+		else:
+			type = type_map.get(c_type)
+		if not type:
+			sys.exit("Could not create variable of type " + repr(c_type))
+		self.variable = type(name, default)
+		self.variable.flags.update(flags)
+
+		if 'converter' in flags:
+			self.variable.type = c_type
+
 		self.name = name
 		self.line = line
 		self.default = default
 		self.docstrings = []
 
+	@property
+	def flags(self):
+		return self.variable.flags
+
 	def is_optional(self):
 		return (self.default != unspecified) and not self.flag('required')
 
 			v = CHardCodedParserArgument(converter)
 			self.variables.append(v)
 
-		v = CVariable(self.c_type, self.name, self.default)
-		self.variables.append(v)
+		self.variables.append(self.variable)
 
 		if self.flag('length'):
 			length_name = self.name + "_length"
 			if value is unspecified:
 				try:
 					value = repr(eval(self.default))
-				except ValueError:
+				except (ValueError, NameError):
 					value = self.default
 			l.append(value)
 		return l
 	def format_unit(self):
 		"""
 		Render argument "format unit" in PyArg_ParseTupleAndKeywords format string.
-		Returns iterable.
+		Returns string.
 		"""
-		types = tuple(sorted(self.flag('types', '').strip().split()))
-		flag_tuple = tuple((bool(self.flag(flag)) for flag in options)) + (types,)
-		format_unit = get_format_unit(self.c_type, *flag_tuple)
-		if not format_unit:
-			sys.exit("Sorry, I don't have a format unit for " + repr(self.line))
-		return format_unit
+		return self.variable.format_unit
 
 
 re_argument = re.compile(r"^(?P<ctype>[A-Za-z0-9_\s]+)((?P<splat>\s*\*\s*)|(\s+))(?P<name>\w+)(\s*=\s*(?P<default>\S.*))?$")
 				file.flush()
 
 
+class CountedDeque:
+	def __init__(self, iterable=()):
+		self.line = -1
+		self.deque = collections.deque(iterable)
+		self.left = []
+
+	def __bool__(self):
+		return bool(self.deque) or bool(self.left)
+
+	def popleft(self):
+		self.line += 1
+		if self.left:
+			return self.left.pop()
+		return self.deque.popleft()
+
+	def extendleft(self, i):
+		for line in i:
+			self.line -= 1
+			self.left.append(line)
+
 class Clinic:
 	def __init__(self, *, verify=True):
 		self.state = self.state_reset
 	def read(self, filename):
 		self.filename = filename
 
-		self.input = collections.deque()
 		self.output = []
 		with open(self.filename, "rt") as f:
-			self.input = collections.deque(f.readlines())
+			self.input = CountedDeque(f.readlines())
 
 	def write(self, filename):
 		if not self.output:
 
 		self.filename = filename
 
-
 		body = "\n".join(self.output)
 		# always end .c files with a blank line
 		if not body.endswith("\n"):
 
 		while self.input:
 			line = self.input.popleft().rstrip()
+			saved.append(line)
 			if line == begin_string:
 				break
 			if line.startswith(terminator_prefix):
 				if self.verify:
+					saved.pop() # remove terminator_prefix line from the end
 					# verify checksum
 					checksum_and_comment_end = line[len(terminator_prefix):]
 					stored_checksum, comment, eol = checksum_and_comment_end.partition(']*/')
 					if checksum != stored_checksum:
 						sys.exit('Checksum doesn\'t match for marker line:\n    ' + line + '\nGiving up.')
 				return
-			saved.append(line)
 
 		# if we got here, we didn't find a valid end marker for this block.
 		# restore the entire remainder of the file.
 
 		if self.ignore_line(line) or self.found_dsl_end(line):
 			return
+
 		assert not line[0].isspace(), "first line of clinic dsl should not start with whitespace:" + repr(line)
 		self.tab_nanny(line)
 		line, arrow, return_annotation = line.partition('->')
 		elif self.flag('positional-only'):
 			sys.exit("Error: Can't use default values for arguments with positional-only functions (see " + self.module_name + "." + self.name + ")")
 
-		a = Argument(ctype, aname, line, default)
-		a.original_line = original_line
-		self.arguments.append(a)
+		self.variable_ctype = ctype
+		self.variable_name = aname
+		self.variable_line = line
+		self.variable_original_line = original_line
+		self.variable_default = default
+		self.variable_flags = {}
+
+		# self.arguments.append(a)
 
 		self.next(self.state_argument_flags)
 
 
 		self.tab_nanny(line)
 
-		assert self.arguments
-		a = self.arguments[-1]
-
 		indent = self.calculate_indent(line)
 		if indent == self.argument_indent:
-			self.parse_flags_line(a.flags, line)
+			self.parse_flags_line(self.variable_flags, line)
 			return
 
 		# transition to next state,
 		# this is a good time for a whole bunch o' processing
+		a = Argument(self.variable_ctype, self.variable_name, self.variable_flags, self.variable_line, self.variable_default)
+		a.original_line = self.variable_original_line
+		self.arguments.append(a)
+
 		keyword_only = a.flag('keyword-only')
 		optional = (a.default != unspecified) and (not a.flag('required'))
 
 			if isinstance(a, Argument):
 				docstring_arguments.append(a.name)
 				for line in a.docstrings:
-					docstring_arguments.append(('  ' + line).rstrip())
+					docstring_arguments.append(line.rstrip())
 
 		docstring_first_line += ', '.join(first_line) + fix_right_bracket_count(0) + ')'
 
 		kwargs = ", PyObject *kwargs" if not self.flag('positional-only') else ''
 		print("{prototype_name}(PyObject *self, PyObject *args{kwargs})".format(**locals()))
 		print("{")
+		print("    PyObject *_return_value;")
 
 		# declarations, with defaults
 		for a in self.arguments:
 		for a in self.arguments:
 			impl_arguments.extend(a.impl_argument())
 		print()
-		print('    return ' + impl + '(self, ' + ', '.join(impl_arguments) + ');')
+		print('    _return_value = ' + impl + '(self, ' + ', '.join(impl_arguments) + ');')
 
+		for a in self.arguments:
+			for line in a.cleanup():
+				print("   ", line)
+
+		print("    return _return_value;")
 		print("}")
 		print()
 
 
 		self.next(self.state_reset)
 
-	def dead_code(self):
-		argument_declarations = argument_declarations
-
-
-		pyarg_format_string += ":" + name
-
-
-		if 0: # else:
-			# the first line of the docstring
-
-
-			parse_code = keywords + """
-    if (!PyArg_ParseTupleAndKeywords(args, kwargs, "{pyarg_format_string}",
-                                     _keywords{pyarg_arguments}))
-        return NULL;"""
-
-		template = """
-{docstring}
-{impl_prototype};
-
-#define {module_name_upper}_{name_upper}_METHODDEF    \\
-    {{"{name}", (PyCFunction){module_name}_{name}, METH_VARARGS | METH_KEYWORDS, {module_name}_{name}__doc__}}
-
-static PyObject *
-{module_name}_{name}(PyObject *self, PyObject *args, PyObject *kwargs)
-{{
-    {argument_declarations}
-""" + parse_code + """
-    return {module_name}_{name}_impl(self{invocation_arguments});
-}}
-
-{impl_prototype}
-"""
-
-		text = template.format(**locals())
-		data = text.encode('utf-8')
-
+	
+clinic = None
 
 def main(argv):
+	global clinic
 	import argparse
 	cmdline = argparse.ArgumentParser()
 	cmdline.add_argument("-f", "--force", action='store_true')