#include <Python.h>
#include <structmember.h>
#include <endian.h>

#include <ctype.h>
#include <stdint.h>

#define __STDC_FORMAT_MACROS
#include <inttypes.h>

/* Values missing from stdint.h */
#define UINT8_MIN 0
#define UINT16_MIN 0
#define UINT32_MIN 0
#define UINT64_MIN 0

/* Marker values (if min == max, skip range check) */
#define FLOAT32_MIN 0
#define FLOAT32_MAX 0
#define FLOAT64_MIN 0
#define FLOAT64_MAX 0

typedef int64_t timestamp_t;

/* Somewhat arbitrary, just so we can use fixed sizes for strings
   etc. */
static const int MAX_LAYOUT_COUNT = 1024;

/* Error object and constants */
static PyObject *ParseError;
typedef enum {
	ERR_OTHER,
	ERR_NON_MONOTONIC,
	ERR_OUT_OF_INTERVAL,
} parseerror_code_t;
static void add_parseerror_codes(PyObject *module)
{
	PyModule_AddIntMacro(module, ERR_OTHER);
	PyModule_AddIntMacro(module, ERR_NON_MONOTONIC);
	PyModule_AddIntMacro(module, ERR_OUT_OF_INTERVAL);
}

/* Helpers to raise ParseErrors.  Use "return raise_str(...)" etc. */
static PyObject *raise_str(int line, int col, int code, const char *string)
{
	PyObject *o;
	o = Py_BuildValue("(iiis)", line, col, code, string);
	if (o != NULL) {
		PyErr_SetObject(ParseError, o);
		Py_DECREF(o);
	}
	return NULL;
}
static PyObject *raise_int(int line, int col, int code, int64_t num)
{
	PyObject *o;
	o = Py_BuildValue("(iiiL)", line, col, code, (long long)num);
	if (o != NULL) {
		PyErr_SetObject(ParseError, o);
		Py_DECREF(o);
	}
	return NULL;
}

/****
 * Layout and type helpers
 */
typedef union {
	int8_t i;
	uint8_t u;
} union8_t;
typedef union {
	int16_t i;
	uint16_t u;
} union16_t;
typedef union {
	int32_t i;
	uint32_t u;
	float f;
} union32_t;
typedef union {
	int64_t i;
	uint64_t u;
	double d;
} union64_t;

typedef enum {
	LAYOUT_TYPE_NONE,
	LAYOUT_TYPE_INT8,
	LAYOUT_TYPE_UINT8,
	LAYOUT_TYPE_INT16,
	LAYOUT_TYPE_UINT16,
	LAYOUT_TYPE_INT32,
	LAYOUT_TYPE_UINT32,
	LAYOUT_TYPE_INT64,
	LAYOUT_TYPE_UINT64,
	LAYOUT_TYPE_FLOAT32,
	LAYOUT_TYPE_FLOAT64,
} layout_type_t;

struct {
	char *string;
	layout_type_t layout;
	int size;
} type_lookup[] = {
	{ "int8",    LAYOUT_TYPE_INT8,    1 },
	{ "uint8",   LAYOUT_TYPE_UINT8,   1 },
	{ "int16",   LAYOUT_TYPE_INT16,   2 },
	{ "uint16",  LAYOUT_TYPE_UINT16,  2 },
	{ "int32",   LAYOUT_TYPE_INT32,   4 },
	{ "uint32",  LAYOUT_TYPE_UINT32,  4 },
	{ "int64",   LAYOUT_TYPE_INT64,   8 },
	{ "uint64",  LAYOUT_TYPE_UINT64,  8 },
	{ "float32", LAYOUT_TYPE_FLOAT32, 4 },
	{ "float64", LAYOUT_TYPE_FLOAT64, 8 },
	{ NULL }
};

/****
 * Object definition, init, etc
 */

/* Rocket object */
typedef struct {
	PyObject_HEAD
	layout_type_t layout_type;
	int layout_count;
	int binary_size;
	FILE *file;
	int file_size;
} Rocket;

/* Dealloc / new */
static void Rocket_dealloc(Rocket *self)
{
	if (self->file) {
		fprintf(stderr, "rocket: file wasn't closed\n");
		fclose(self->file);
		self->file = NULL;
	}
	self->ob_type->tp_free((PyObject *)self);
}

static PyObject *Rocket_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
	Rocket *self;

	self = (Rocket *)type->tp_alloc(type, 0);
	if (!self)
		return NULL;
	self->layout_type = LAYOUT_TYPE_NONE;
	self->layout_count = 0;
	self->binary_size = 0;
	self->file = NULL;
	self->file_size = -1;
	return (PyObject *)self;
}

/* .__init__(layout, file) */
static int Rocket_init(Rocket *self, PyObject *args, PyObject *kwds)
{
	const char *layout, *path;
	static char *kwlist[] = { "layout", "file", NULL };
	if (!PyArg_ParseTupleAndKeywords(args, kwds, "sz", kwlist,
					 &layout, &path))
		return -1;
	if (!layout)
		return -1;
	if (path) {
		if ((self->file = fopen(path, "a+b")) == NULL) {
			PyErr_SetFromErrno(PyExc_OSError);
			return -1;
		}
		self->file_size = -1;
	} else {
		self->file = NULL;
	}

	const char *under;
	char *tmp;
	under = strchr(layout, '_');
	if (!under) {
		PyErr_SetString(PyExc_ValueError, "no such layout: "
				"badly formatted string");
		return -1;
	}
	self->layout_count = strtoul(under+1, &tmp, 10);
	if (self->layout_count < 1 || *tmp != '\0') {
		PyErr_SetString(PyExc_ValueError, "no such layout: "
				"bad count");
		return -1;
	}
	if (self->layout_count >= MAX_LAYOUT_COUNT) {
		PyErr_SetString(PyExc_ValueError, "no such layout: "
				"count too high");
		return -1;
	}

	int i;
	for (i = 0; type_lookup[i].string; i++)
		if (strncmp(layout, type_lookup[i].string, under-layout) == 0)
			break;
	if (!type_lookup[i].string) {
		PyErr_SetString(PyExc_ValueError, "no such layout: "
				"bad data type");
		return -1;
	}
	self->layout_type = type_lookup[i].layout;
	self->binary_size = 8 + (type_lookup[i].size * self->layout_count);

	return 0;
}

/* .close() */
static PyObject *Rocket_close(Rocket *self)
{
	if (self->file) {
		fclose(self->file);
		self->file = NULL;
	}
	Py_INCREF(Py_None);
	return Py_None;
}

/* .file_size property */
static PyObject *Rocket_get_file_size(Rocket *self)
{
	if (!self->file) {
		PyErr_SetString(PyExc_AttributeError, "no file");
		return NULL;
	}
	if (self->file_size < 0) {
		int oldpos;
		if (((oldpos = ftell(self->file)) < 0) ||
		    (fseek(self->file, 0, SEEK_END) < 0) ||
		    ((self->file_size = ftell(self->file)) < 0) ||
		    (fseek(self->file, oldpos, SEEK_SET) < 0)) {
			PyErr_SetFromErrno(PyExc_OSError);
			return NULL;
		}
	}
	return PyInt_FromLong(self->file_size);
}

/****
 * Append from string
 */
static inline long int strtoll10(const char *nptr, char **endptr) {
	return strtoll(nptr, endptr, 10);
}
static inline long int strtoull10(const char *nptr, char **endptr) {
	return strtoull(nptr, endptr, 10);
}

/* .append_string(count, data, offset, linenum, start, end, last_timestamp) */
static PyObject *Rocket_append_string(Rocket *self, PyObject *args)
{
	int count;
	const char *data;
	int offset;
	const char *linestart;
	int linenum;
        long long ll1, ll2, ll3;
	timestamp_t start;
	timestamp_t end;
	timestamp_t last_timestamp;

	int written = 0;
	char *endptr;
	union8_t t8;
	union16_t t16;
	union32_t t32;
	union64_t t64;
	int i;

	/* It would be nice to use 't#' instead of 's' for data,
	   but we need the null termination for strto*.  If we had
	   strnto* that took a length, we could use t# and not require
	   a copy. */
	if (!PyArg_ParseTuple(args, "isiiLLL:append_string", &count,
			      &data, &offset, &linenum,
			      &ll1, &ll2, &ll3))
		return NULL;
        start = ll1;
        end = ll2;
        last_timestamp = ll3;

	/* Skip spaces, but don't skip over a newline. */
#define SKIP_BLANK(buf) do {			\
	while (isspace(*buf)) {			\
		if (*buf == '\n')		\
			break;			\
		buf++;				\
	} } while(0)

	const char *buf = &data[offset];
	while (written < count && *buf)
	{
		linestart = buf;
		linenum++;

		/* Skip leading whitespace and commented lines */
		SKIP_BLANK(buf);
		if (*buf == '#') {
			while (*buf && *buf != '\n')
				buf++;
			if (*buf)
				buf++;
			continue;
		}

		/* Extract timestamp */
		t64.i = strtoll(buf, &endptr, 10);
		if (endptr == buf || !isspace(*endptr)) {
			/* Try parsing as a double instead */
			t64.d = strtod(buf, &endptr);
			if (endptr == buf)
				goto bad_timestamp;
			if (!isspace(*endptr))
				goto cant_parse_value;
			t64.i = round(t64.d);
		}
		if (t64.i <= last_timestamp)
			return raise_int(linenum, buf - linestart + 1,
					 ERR_NON_MONOTONIC, t64.i);
		last_timestamp = t64.i;
		if (t64.i < start || t64.i >= end)
			return raise_int(linenum, buf - linestart + 1,
					 ERR_OUT_OF_INTERVAL, t64.i);
		t64.u = le64toh(t64.u);
		if (fwrite(&t64.u, 8, 1, self->file) != 1)
			goto err;
		buf = endptr;

		/* Parse all values in the line */
		switch (self->layout_type) {
#define CS(type, parsefunc, parsetype, realtype, disktype, letoh, bytes) \
		case LAYOUT_TYPE_##type:				\
			/* parse and write in a loop */			\
			for (i = 0; i < self->layout_count; i++) {	\
				/* skip non-newlines */			\
				SKIP_BLANK(buf);			\
				if (*buf == '\n')			\
					goto wrong_number_of_values;	\
				/* parse number */			\
				parsetype = parsefunc(buf, &endptr);	\
				if (*endptr && !isspace(*endptr))	\
					goto cant_parse_value;		\
				/* check limits */			\
				if (type##_MIN != type##_MAX &&		\
				    (parsetype < type##_MIN ||		\
				     parsetype > type##_MAX))		\
					goto value_out_of_range;	\
				/* convert to disk representation */	\
				realtype = parsetype;			\
				disktype = letoh(disktype);		\
				/* write it */				\
				if (fwrite(&disktype, bytes,		\
					   1, self->file) != 1)		\
					goto err;			\
				/* advance buf */			\
				buf = endptr;				\
			}						\
			/* Skip trailing whitespace and comments */	\
			SKIP_BLANK(buf);				\
			if (*buf == '#')				\
				while (*buf && *buf != '\n')		\
					buf++;				\
			if (*buf == '\n')				\
				buf++;					\
			else if (*buf != '\0')				\
				goto extra_data_on_line;		\
			break

			CS(INT8,   strtoll10,  t64.i, t8.i,  t8.u,         , 1);
			CS(UINT8,  strtoull10, t64.u, t8.u,  t8.u,         , 1);
			CS(INT16,  strtoll10,  t64.i, t16.i, t16.u, le16toh, 2);
			CS(UINT16, strtoull10, t64.u, t16.u, t16.u, le16toh, 2);
			CS(INT32,  strtoll10,  t64.i, t32.i, t32.u, le32toh, 4);
			CS(UINT32, strtoull10, t64.u, t32.u, t32.u, le32toh, 4);
			CS(INT64,  strtoll10,  t64.i, t64.i, t64.u, le64toh, 8);
			CS(UINT64, strtoull10, t64.u, t64.u, t64.u, le64toh, 8);
			CS(FLOAT32, strtod,   t64.d, t32.f, t32.u, le32toh, 4);
			CS(FLOAT64, strtod,   t64.d, t64.d, t64.u, le64toh, 8);
#undef CS
		default:
			PyErr_SetString(PyExc_TypeError, "unknown type");
			return NULL;
		}

		/* Done this line */
		written++;
	}

	fflush(self->file);

	/* Build return value and return */
	offset = buf - data;
	PyObject *o;
	o = Py_BuildValue("(iiLi)", written, offset,
                          (long long)last_timestamp, linenum);
	return o;
err:
	PyErr_SetFromErrno(PyExc_OSError);
	return NULL;
bad_timestamp:
	return raise_str(linenum, buf - linestart + 1,
			 ERR_OTHER, "bad timestamp");
cant_parse_value:
	return raise_str(linenum, buf - linestart + 1,
			 ERR_OTHER, "can't parse value");
wrong_number_of_values:
	return raise_str(linenum, buf - linestart + 1,
			 ERR_OTHER, "wrong number of values");
value_out_of_range:
	return raise_str(linenum, buf - linestart + 1,
			 ERR_OTHER, "value out of range");
extra_data_on_line:
	return raise_str(linenum, buf - linestart + 1,
			 ERR_OTHER, "extra data on line");
}

/****
 * Append from binary data
 */

/* .append_binary(count, data, offset, linenum, start, end, last_timestamp) */
static PyObject *Rocket_append_binary(Rocket *self, PyObject *args)
{
        int count;
	const uint8_t *data;
        int data_len;
        int linenum;
	int offset;
        long long ll1, ll2, ll3;
	timestamp_t start;
	timestamp_t end;
	timestamp_t last_timestamp;

	if (!PyArg_ParseTuple(args, "it#iiLLL:append_binary",
                              &count, &data, &data_len, &offset,
                              &linenum, &ll1, &ll2, &ll3))
		return NULL;
        start = ll1;
        end = ll2;
        last_timestamp = ll3;

        /* Advance to offset */
        if (offset > data_len)
                return raise_str(0, 0, ERR_OTHER, "bad offset");
        data += offset;
        data_len -= offset;

        /* Figure out max number of rows to insert */
        int rows = data_len / self->binary_size;
        if (rows > count)
                rows = count;

        /* Check timestamps */
        timestamp_t ts;
	int i;
        for (i = 0; i < rows; i++) {
                /* Read raw timestamp, byteswap if needed */
                memcpy(&ts, &data[i * self->binary_size], 8);
                ts = le64toh(ts);

                /* Check limits */
                if (ts <= last_timestamp)
                        return raise_int(i, 0, ERR_NON_MONOTONIC, ts);
                last_timestamp = ts;
                if (ts < start || ts >= end)
                        return raise_int(i, 0, ERR_OUT_OF_INTERVAL, ts);
        }

        /* Write binary data */
        if (fwrite(data, self->binary_size, rows, self->file) != rows) {
                PyErr_SetFromErrno(PyExc_OSError);
                return NULL;
        }
	fflush(self->file);

	/* Build return value and return */
	PyObject *o;
	o = Py_BuildValue("(iiLi)", rows, offset + rows * self->binary_size,
                          (long long)last_timestamp, linenum);
	return o;
}

/****
 * Extract to string
 */

static PyObject *Rocket_extract_string(Rocket *self, PyObject *args)
{
	long count;
	long offset;

	if (!PyArg_ParseTuple(args, "ll", &offset, &count))
		return NULL;
	if (!self->file) {
		PyErr_SetString(PyExc_Exception, "no file");
		return NULL;
	}
	/* Seek to target location */
	if (fseek(self->file, offset, SEEK_SET) < 0) {
		PyErr_SetFromErrno(PyExc_OSError);
		return NULL;
	}

	char *str = NULL, *new;
	long len_alloc = 0;
	long len = 0;
	int ret;

	/* min space free in string (and the maximum length of one
	   line); this is generous */
	const int min_free = 32 * MAX_LAYOUT_COUNT;

	/* how much to allocate at once */
	const int alloc_size = 1048576;

	int row, i;
	union8_t t8;
	union16_t t16;
	union32_t t32;
	union64_t t64;
	for (row = 0; row < count; row++) {
		/* Make sure there's space for a line */
		if ((len_alloc - len) < min_free) {
			/* grow by 1 meg at a time */
			len_alloc += alloc_size;
			new = realloc(str, len_alloc);
			if (new == NULL)
				goto err;
			str = new;
		}

		/* Read and print timestamp */
		if (fread(&t64.u, 8, 1, self->file) != 1)
			goto err;
		t64.u = le64toh(t64.u);
		ret = sprintf(&str[len], "%" PRId64, t64.i);
		if (ret <= 0)
			goto err;
		len += ret;

		/* Read and print values */
		switch (self->layout_type) {
#define CASE(type, fmt, fmttype, disktype, letoh, bytes)		\
		case LAYOUT_TYPE_##type:				\
			/* read and format in a loop */			\
			for (i = 0; i < self->layout_count; i++) {	\
				if (fread(&disktype, bytes,		\
					  1, self->file) != 1)		\
					goto err;			\
				disktype = letoh(disktype);		\
				ret = sprintf(&str[len], " " fmt,	\
					      fmttype);			\
				if (ret <= 0)				\
					goto err;			\
				len += ret;				\
			}						\
			break
			CASE(INT8,   "%" PRId8,  t8.i,  t8.u,         , 1);
			CASE(UINT8,  "%" PRIu8,  t8.u,  t8.u,         , 1);
			CASE(INT16,  "%" PRId16, t16.i, t16.u, le16toh, 2);
			CASE(UINT16, "%" PRIu16, t16.u, t16.u, le16toh, 2);
			CASE(INT32,  "%" PRId32, t32.i, t32.u, le32toh, 4);
			CASE(UINT32, "%" PRIu32, t32.u, t32.u, le32toh, 4);
			CASE(INT64,  "%" PRId64, t64.i, t64.u, le64toh, 8);
			CASE(UINT64, "%" PRIu64, t64.u, t64.u, le64toh, 8);
			/* These next two are a bit debatable.  floats
			   are 6-9 significant figures, so we print 7.
			   Doubles are 15-19, so we print 17.  This is
			   similar to the old prep format for float32.
			*/
			CASE(FLOAT32, "%.6e",  t32.f, t32.u, le32toh, 4);
			CASE(FLOAT64, "%.16e", t64.d, t64.u, le64toh, 8);
#undef CASE
		default:
			PyErr_SetString(PyExc_TypeError, "unknown type");
			if (str) free(str);
			return NULL;
		}
		str[len++] = '\n';
	}

	PyObject *pystr = PyString_FromStringAndSize(str, len);
	free(str);
	return pystr;
err:
	if (str) free(str);
	PyErr_SetFromErrno(PyExc_OSError);
	return NULL;
}

/****
 * Extract to binary string containing raw little-endian binary data
 */
static PyObject *Rocket_extract_binary(Rocket *self, PyObject *args)
{
	long count;
	long offset;

	if (!PyArg_ParseTuple(args, "ll", &offset, &count))
		return NULL;
	if (!self->file) {
		PyErr_SetString(PyExc_Exception, "no file");
		return NULL;
	}
	/* Seek to target location */
	if (fseek(self->file, offset, SEEK_SET) < 0) {
		PyErr_SetFromErrno(PyExc_OSError);
		return NULL;
	}

        uint8_t *str;
        int len = count * self->binary_size;
        str = malloc(len);
        if (str == NULL) {
                PyErr_SetFromErrno(PyExc_OSError);
                return NULL;
        }

        /* Data in the file is already in the desired little-endian
           binary format, so just read it directly. */
        if (fread(str, self->binary_size, count, self->file) != count) {
                free(str);
                PyErr_SetFromErrno(PyExc_OSError);
                return NULL;
        }

	PyObject *pystr = PyBytes_FromStringAndSize((char *)str, len);
	free(str);
	return pystr;
}

/****
 * Extract timestamp
 */
static PyObject *Rocket_extract_timestamp(Rocket *self, PyObject *args)
{
	long offset;
	union64_t t64;
	if (!PyArg_ParseTuple(args, "l", &offset))
		return NULL;
	if (!self->file) {
		PyErr_SetString(PyExc_Exception, "no file");
		return NULL;
	}

	/* Seek to target location and read timestamp */
	if ((fseek(self->file, offset, SEEK_SET) < 0) ||
	    (fread(&t64.u, 8, 1, self->file) != 1)) {
		PyErr_SetFromErrno(PyExc_OSError);
		return NULL;
	}

	/* Convert and return */
	t64.u = le64toh(t64.u);
	return Py_BuildValue("L", (long long)t64.i);
}

/****
 * Module and type setup
 */

static PyGetSetDef Rocket_getsetters[] = {
	{ "file_size", (getter)Rocket_get_file_size, NULL,
	  "file size in bytes", NULL },
	{ NULL },
};

static PyMemberDef Rocket_members[] = {
	{ "binary_size", T_INT, offsetof(Rocket, binary_size), 0,
	  "binary size per row" },
	{ NULL },
};

static PyMethodDef Rocket_methods[] = {
	{ "close",
          (PyCFunction)Rocket_close, METH_NOARGS,
	  "close(self)\n\n"
	  "Close file handle" },

	{ "append_string",
          (PyCFunction)Rocket_append_string, METH_VARARGS,
	  "append_string(self, count, data, offset, line, start, end, ts)\n\n"
          "Parse string and append data.\n"
	  "\n"
	  "  count: maximum number of rows to add\n"
          "  data: string data\n"
          "  offset: byte offset into data to start parsing\n"
          "  line: current line number of data\n"
          "  start: starting timestamp for interval\n"
          "  end: end timestamp for interval\n"
          "  ts: last timestamp that was previously parsed\n"
	  "\n"
	  "Raises ParseError if timestamps are non-monotonic, outside\n"
	  "the start/end interval etc.\n"
	  "\n"
          "On success, return a tuple:\n"
          "  added_rows: how many rows were added from the file\n"
          "  data_offset: current offset into the data string\n"
          "  last_timestamp: last timestamp we parsed\n"
          "  linenum: current line number" },

	{ "append_binary",
	  (PyCFunction)Rocket_append_binary, METH_VARARGS,
	  "append_binary(self, count, data, offset, line, start, end, ts)\n\n"
          "Append binary data, which must match the data layout.\n"
	  "\n"
	  "  count: maximum number of rows to add\n"
          "  data: binary data\n"
          "  offset: byte offset into data to start adding\n"
          "  line: current line number (unused)\n"
          "  start: starting timestamp for interval\n"
          "  end: end timestamp for interval\n"
          "  ts: last timestamp that was previously parsed\n"
	  "\n"
	  "Raises ParseError if timestamps are non-monotonic, outside\n"
	  "the start/end interval etc.\n"
	  "\n"
          "On success, return a tuple:\n"
          "  added_rows: how many rows were added from the file\n"
          "  data_offset: current offset into the data string\n"
          "  last_timestamp: last timestamp we parsed\n"
          "  linenum: current line number (copied from argument)" },

	{ "extract_string",
          (PyCFunction)Rocket_extract_string, METH_VARARGS,
	  "extract_string(self, offset, count)\n\n"
	  "Extract count rows of data from the file at offset offset.\n"
	  "Return an ascii formatted string according to the layout" },

	{ "extract_binary",
	  (PyCFunction)Rocket_extract_binary, METH_VARARGS,
	  "extract_binary(self, offset, count)\n\n"
	  "Extract count rows of data from the file at offset offset.\n"
	  "Return a raw binary string of data matching the data layout." },

	{ "extract_timestamp",
	  (PyCFunction)Rocket_extract_timestamp, METH_VARARGS,
	  "extract_timestamp(self, offset)\n\n"
	  "Extract a single timestamp from the file" },

	{ NULL },
};

static PyTypeObject RocketType = {
	PyObject_HEAD_INIT(NULL)

	.tp_name	= "rocket.Rocket",
	.tp_basicsize	= sizeof(Rocket),
	.tp_flags	= Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,

	.tp_new		= Rocket_new,
	.tp_dealloc	= (destructor)Rocket_dealloc,
	.tp_init	= (initproc)Rocket_init,
	.tp_methods	= Rocket_methods,
	.tp_members	= Rocket_members,
	.tp_getset	= Rocket_getsetters,

	.tp_doc		= ("rocket.Rocket(layout, file)\n\n"
			   "C implementation of the \"rocket\" data parsing\n"
			   "interface, which translates between the binary\n"
			   "format on disk and the ASCII or Python list\n"
			   "format used when communicating with the rest of\n"
			   "the system.")
};

static PyMethodDef module_methods[] = {
	{ NULL },
};

PyMODINIT_FUNC
initrocket(void)
{
	PyObject *module;

	RocketType.tp_new = PyType_GenericNew;
	if (PyType_Ready(&RocketType) < 0)
		return;

	module = Py_InitModule3("rocket", module_methods,
				"Rocket data parsing and formatting module");
	Py_INCREF(&RocketType);
	PyModule_AddObject(module, "Rocket", (PyObject *)&RocketType);

	ParseError = PyErr_NewException("rocket.ParseError", NULL, NULL);
	Py_INCREF(ParseError);
	PyModule_AddObject(module, "ParseError", ParseError);
	add_parseerror_codes(module);

	return;
}
