#include "Python.h"
#include "structmember.h"
#ifdef USE_LOCAL_TRE_H
#include "../local_includes/tre.h"
#else
#include <tre/tre.h>
#endif
#define TRE_MODULE "tre"
typedef struct {
PyObject_HEAD
regex_t rgx;
int flags;
} TrePatternObject;
typedef struct {
PyObject_HEAD
regaparams_t ap;
} TreFuzzynessObject;
typedef struct {
PyObject_HEAD
regamatch_t am;
PyObject *targ;
TreFuzzynessObject *fz;
} TreMatchObject;
static PyObject *ErrorObject;
static void
_set_tre_err(int rc, regex_t *rgx)
{
PyObject *errval;
char emsg[256];
size_t elen;
elen = tre_regerror(rc, rgx, emsg, sizeof(emsg));
if (emsg[elen] == '\0')
elen--;
errval = Py_BuildValue("s#", emsg, elen);
PyErr_SetObject(ErrorObject, errval);
Py_XDECREF(errval);
}
static PyObject *
TreFuzzyness_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
static char *kwlist[] = {
"delcost", "inscost", "maxcost", "subcost",
"maxdel", "maxerr", "maxins", "maxsub",
NULL
};
TreFuzzynessObject *self;
self = (TreFuzzynessObject*)type->tp_alloc(type, 0);
if (self == NULL)
return NULL;
tre_regaparams_default(&self->ap);
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|iiiiiiii", kwlist,
&self->ap.cost_del, &self->ap.cost_ins,
&self->ap.max_cost, &self->ap.cost_subst,
&self->ap.max_del, &self->ap.max_err,
&self->ap.max_ins, &self->ap.max_subst))
{
Py_DECREF(self);
return NULL;
}
return (PyObject*)self;
}
static PyObject *
TreFuzzyness_repr(PyObject *obj)
{
TreFuzzynessObject *self = (TreFuzzynessObject*)obj;
PyObject *o;
o = PyUnicode_FromFormat("%s(delcost=%d,inscost=%d,maxcost=%d,subcost=%d,"
"maxdel=%d,maxerr=%d,maxins=%d,maxsub=%d)",
Py_TYPE(self)->tp_name, self->ap.cost_del,
self->ap.cost_ins, self->ap.max_cost,
self->ap.cost_subst, self->ap.max_del,
self->ap.max_err, self->ap.max_ins,
self->ap.max_subst);
return o;
}
static PyMemberDef TreFuzzyness_members[] = {
{ "delcost", T_INT, offsetof(TreFuzzynessObject, ap.cost_del), 0,
"The cost of a deleted character" },
{ "inscost", T_INT, offsetof(TreFuzzynessObject, ap.cost_ins), 0,
"The cost of an inserted character" },
{ "maxcost", T_INT, offsetof(TreFuzzynessObject, ap.max_cost), 0,
"The maximum allowed cost of a match. If this is set to zero, an exact "
"match is searched for" },
{ "subcost", T_INT, offsetof(TreFuzzynessObject, ap.cost_subst), 0,
"The cost of a substituted character" },
{ "maxdel", T_INT, offsetof(TreFuzzynessObject, ap.max_del), 0,
"Maximum allowed number of deleted characters" },
{ "maxerr", T_INT, offsetof(TreFuzzynessObject, ap.max_err), 0,
"Maximum allowed number of errors (inserts + deletes + substitutes)" },
{ "maxins", T_INT, offsetof(TreFuzzynessObject, ap.max_ins), 0,
"Maximum allowed number of inserted characters" },
{ "maxsub", T_INT, offsetof(TreFuzzynessObject, ap.max_subst), 0,
"Maximum allowed number of substituted characters" },
{ NULL }
};
static PyTypeObject TreFuzzynessType = {
PyVarObject_HEAD_INIT(NULL,0)
TRE_MODULE ".Fuzzyness",
sizeof(TreFuzzynessObject),
0,
0,
0,
0,
0,
0,
TreFuzzyness_repr,
0,
0,
0,
0,
0,
0,
0,
0,
0,
Py_TPFLAGS_DEFAULT,
TRE_MODULE ".fuzzyness object holds approximation parameters for match",
0,
0,
0,
0,
0,
0,
0,
TreFuzzyness_members,
0,
0,
0,
0,
0,
0,
0,
0,
TreFuzzyness_new
};
static PyObject *
PyTreMatch_groups(TreMatchObject *self, PyObject *dummy)
{
PyObject *result;
size_t i;
if (self->am.nmatch < 1)
{
Py_INCREF(Py_None);
return Py_None;
}
result = PyTuple_New(self->am.nmatch);
for (i = 0; i < self->am.nmatch; i++)
{
PyObject *range;
regmatch_t *rm = &self->am.pmatch[i];
if (rm->rm_so == (-1) && rm->rm_eo == (-1))
{
Py_INCREF(Py_None);
range = Py_None;
}
else
{
range = Py_BuildValue("(ii)", rm->rm_so, rm->rm_eo);
}
PyTuple_SetItem(result, i, range);
}
return (PyObject*)result;
}
static PyObject *
PyTreMatch_groupi(PyObject *obj, Py_ssize_t gn)
{
TreMatchObject *self = (TreMatchObject*)obj;
PyObject *result;
regmatch_t *rm;
if (gn < 0 || (size_t)gn > self->am.nmatch - 1)
{
PyErr_SetString(PyExc_ValueError, "out of bounds");
return NULL;
}
rm = &self->am.pmatch[gn];
if (rm->rm_so == (-1) && rm->rm_eo == (-1))
{
Py_INCREF(Py_None);
return Py_None;
}
result = PySequence_GetSlice(self->targ, rm->rm_so, rm->rm_eo);
return result;
}
static PyObject *
PyTreMatch_group(TreMatchObject *self, PyObject *grpno)
{
PyObject *result;
long gn;
gn = PyLong_AsLong(grpno);
if (PyErr_Occurred())
return NULL;
result = PyTreMatch_groupi((PyObject*)self, gn);
return result;
}
static PyMethodDef TreMatch_methods[] = {
{"group", (PyCFunction)PyTreMatch_group, METH_O,
"return submatched string or None if a parenthesized subexpression did "
"not participate in a match"},
{"groups", (PyCFunction)PyTreMatch_groups, METH_NOARGS,
"return the tuple of slice tuples for all parenthesized subexpressions "
"(None for not participated)"},
{NULL, NULL}
};
static PyMemberDef TreMatch_members[] = {
{ "cost", T_INT, offsetof(TreMatchObject, am.cost), READONLY,
"Cost of the match" },
{ "numdel", T_INT, offsetof(TreMatchObject, am.num_del), READONLY,
"Number of deletes in the match" },
{ "numins", T_INT, offsetof(TreMatchObject, am.num_ins), READONLY,
"Number of inserts in the match" },
{ "numsub", T_INT, offsetof(TreMatchObject, am.num_subst), READONLY,
"Number of substitutes in the match" },
{ "fuzzyness", T_OBJECT, offsetof(TreMatchObject, fz), READONLY,
"Fuzzyness used during match" },
{ NULL }
};
static void
PyTreMatch_dealloc(TreMatchObject *self)
{
Py_XDECREF(self->targ);
Py_XDECREF(self->fz);
if (self->am.pmatch != NULL)
PyMem_Del(self->am.pmatch);
PyObject_Del(self);
}
static PySequenceMethods TreMatch_as_sequence_methods = {
0,
0,
0,
PyTreMatch_groupi,
0,
0,
0,
0,
0,
0
};
static PyTypeObject TreMatchType = {
PyVarObject_HEAD_INIT(NULL,0)
TRE_MODULE ".Match",
sizeof(TreMatchObject),
0,
(destructor)PyTreMatch_dealloc,
0,
0,
0,
0,
0,
0,
&TreMatch_as_sequence_methods,
0,
0,
0,
0,
0,
0,
0,
Py_TPFLAGS_DEFAULT,
TRE_MODULE ".match object holds result of successful match",
0,
0,
0,
0,
0,
0,
TreMatch_methods,
TreMatch_members
};
static TreMatchObject *
newTreMatchObject(void)
{
TreMatchObject *self;
self = PyObject_New(TreMatchObject, &TreMatchType);
if (self == NULL)
return NULL;
memset(&self->am, '\0', sizeof(self->am));
self->targ = NULL;
self->fz = NULL;
return self;
}
static PyObject *
PyTrePattern_search(TrePatternObject *self, PyObject *args)
{
PyObject *pstring;
Py_ssize_t num_codepoints = 0;
int codepoint_kind = PyUnicode_1BYTE_KIND;
void const *src = NULL;
#ifdef TRE_WCHAR
Py_UCS4 *ucs_mstring = NULL;
#else
Py_UCS1 *ucs_mstring = NULL;
#endif
int eflags = 0;
TreMatchObject *mo;
TreFuzzynessObject *fz;
size_t nsub;
int rc;
regmatch_t *pm;
if (PyTuple_Size(args) > 0 && PyUnicode_Check(PyTuple_GetItem(args, 0)))
{
if (!PyArg_ParseTuple(args, "UO!|i:search", &pstring, &TreFuzzynessType,
&fz, &eflags))
return NULL;
num_codepoints = PyUnicode_GET_LENGTH(pstring); codepoint_kind = PyUnicode_KIND(pstring);
#ifndef TRE_WCHAR
if (PyUnicode_1BYTE_KIND != codepoint_kind)
{
PyErr_SetString(PyExc_ValueError, "In search(), this build of TRE does not support characters with codepoints that cannot fit in a byte.");
return NULL;
}
#endif
src = PyUnicode_DATA(pstring);
}
else if (PyTuple_Size(args) > 0 && PyBytes_Check(PyTuple_GetItem(args, 0)))
{
if (!PyArg_ParseTuple(args, "SO!|i:search", &pstring, &TreFuzzynessType,
&fz, &eflags))
return NULL;
num_codepoints = PyBytes_GET_SIZE(pstring);
codepoint_kind = PyUnicode_1BYTE_KIND;
src = PyBytes_AS_STRING(pstring); }
else
{
PyErr_SetString(PyExc_ValueError, "In search(), argument is not str or bytes");
return(NULL);
}
mo = newTreMatchObject();
if (mo == NULL)
return NULL;
nsub = self->rgx.re_nsub + 1;
pm = PyMem_New(regmatch_t, nsub);
if (!pm)
{
Py_DECREF(mo);
return PyErr_NoMemory();
}
mo->am.nmatch = nsub;
mo->am.pmatch = pm;
#ifdef TRE_WCHAR
((void)sizeof(char[1 - 2*!!(4!=sizeof(wchar_t))])); #endif
#ifdef TRE_WCHAR
ucs_mstring = (Py_UCS4 *) calloc(sizeof(Py_UCS4), num_codepoints+1);
#else
ucs_mstring = (Py_UCS1 *) calloc(sizeof(Py_UCS1), num_codepoints+1);
#endif
if (NULL == ucs_mstring)
{
Py_DECREF(mo);
return PyErr_NoMemory();
}
switch (codepoint_kind)
{
#ifdef TRE_WCHAR
#if PY_MAJOR_VERSION >= 3 && (PY_MINOR_VERSION >= 3 && PY_MINOR_VERSION < 10)
case PyUnicode_WCHAR_KIND: for (int cpx = 0; cpx < num_codepoints; cpx++)
{
ucs_mstring[cpx] = ((wchar_t *) src)[cpx];
}
break;
#endif
#endif
case PyUnicode_1BYTE_KIND:
#ifdef TRE_WCHAR
for (int cpx = 0; cpx < num_codepoints; cpx++)
{
ucs_mstring[cpx] = ((Py_UCS1 *) src)[cpx];
}
#else
memcpy(ucs_mstring,src,num_codepoints*sizeof(Py_UCS1));
#endif
break;
#ifdef TRE_WCHAR
case PyUnicode_2BYTE_KIND:
for (int cpx = 0; cpx < num_codepoints; cpx++)
{
ucs_mstring[cpx] = ((Py_UCS2 *) src)[cpx];
}
break;
case PyUnicode_4BYTE_KIND:
memcpy(ucs_mstring,src,num_codepoints*sizeof(Py_UCS4));
break;
#endif
default:
PyErr_Format(PyExc_ValueError, "In search(), argument is unrecognized codepoint kind (%d)",codepoint_kind);
Py_DECREF(mo);
free(ucs_mstring);
return(NULL);
}
ucs_mstring[num_codepoints] = 0;
#ifdef FUTURE_RELEASE_GIL
Py_BEGIN_ALLOW_THREADS
#endif
#ifdef TRE_WCHAR
rc = tre_regawnexec(&self->rgx, (wchar_t const *) ucs_mstring, num_codepoints, &mo->am, fz->ap, eflags);
#else
rc = tre_reganexec(&self->rgx, (char const *) ucs_mstring, num_codepoints, &mo->am, fz->ap, eflags);
#endif
#ifdef FUTURE_RELEASE_GIL
Py_END_ALLOW_THREADS
#endif
free(ucs_mstring);
if (PyErr_Occurred())
{
Py_DECREF(mo);
return NULL;
}
if (rc == REG_OK)
{
Py_INCREF(pstring);
mo->targ = pstring;
Py_INCREF(fz);
mo->fz = fz;
return (PyObject*)mo;
}
if (rc == REG_NOMATCH)
{
Py_DECREF(mo);
Py_INCREF(Py_None);
return Py_None;
}
_set_tre_err(rc, &self->rgx);
Py_DECREF(mo);
return NULL;
}
static PyMethodDef TrePattern_methods[] = {
{ "search", (PyCFunction)PyTrePattern_search, METH_VARARGS,
"try to search in the given string, returning " TRE_MODULE ".match object "
"or None on failure" },
{NULL, NULL}
};
static PyMemberDef TrePattern_members[] = {
{ "nsub", T_INT, offsetof(TrePatternObject, rgx.re_nsub), READONLY,
"Number of parenthesized subexpressions in regex" },
{ NULL }
};
static void
PyTrePattern_dealloc(TrePatternObject *self)
{
tre_regfree(&self->rgx);
PyObject_Del(self);
}
static PyTypeObject TrePatternType = {
PyVarObject_HEAD_INIT(NULL,0)
TRE_MODULE ".Pattern",
sizeof(TrePatternObject),
0,
(destructor)PyTrePattern_dealloc,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
Py_TPFLAGS_DEFAULT,
TRE_MODULE ".pattern object holds compiled tre regex",
0,
0,
0,
0,
0,
0,
TrePattern_methods,
TrePattern_members
};
static TrePatternObject *
newTrePatternObject(void)
{
TrePatternObject *self;
self = PyObject_New(TrePatternObject, &TrePatternType);
if (self == NULL)
return NULL;
self->flags = 0;
return self;
}
static PyObject *
PyTre_ncompile(PyObject *self, PyObject *args)
{
TrePatternObject *rv;
PyObject *pattern = NULL;
int codepoint_kind = PyUnicode_1BYTE_KIND;
Py_ssize_t pat_len = 0;
int cflags = 0;
int rc;
void const *src = NULL;
#ifdef TRE_WCHAR
Py_UCS4 *ucs_pattern = NULL;
#else
Py_UCS1 *ucs_pattern = NULL;
#endif
if (PyTuple_Size(args) > 0 && PyUnicode_Check(PyTuple_GetItem(args, 0)))
{
if (!PyArg_ParseTuple(args, "U|i:search", &pattern, &cflags))
return NULL;
codepoint_kind = PyUnicode_KIND(pattern);
pat_len = PyUnicode_GET_LENGTH(pattern); #ifndef TRE_WCHAR
if (PyUnicode_1BYTE_KIND != codepoint_kind)
{
PyErr_SetString(PyExc_ValueError, "In compile(), this build of TRE does not support characters with codepoints that cannot fit in a byte.");
return NULL;
}
#endif
src = PyUnicode_DATA(pattern);
}
else if (PyTuple_Size(args) > 0 && PyBytes_Check(PyTuple_GetItem(args, 0)))
{
if (!PyArg_ParseTuple(args, "SO!|i:search", &pattern, &cflags))
return NULL;
codepoint_kind = PyUnicode_1BYTE_KIND;
pat_len = PyBytes_GET_SIZE(pattern);
src = PyBytes_AS_STRING(pattern); }
else
{
PyErr_SetString(PyExc_ValueError, "In compile(), argument is not str or bytes");
return(NULL);
}
rv = newTrePatternObject();
if (rv == NULL)
return NULL;
#ifdef TRE_WCHAR
((void)sizeof(char[1 - 2*!!(4!=sizeof(wchar_t))])); #endif
#ifdef TRE_WCHAR
ucs_pattern = (Py_UCS4 *) calloc(sizeof(Py_UCS4), pat_len+1);
#else
ucs_pattern = (Py_UCS1 *) calloc(sizeof(Py_UCS1), pat_len+1);
#endif
switch (codepoint_kind)
{
#ifdef TRE_WCHAR
#if PY_MAJOR_VERSION >= 3 && (PY_MINOR_VERSION >= 3 && PY_MINOR_VERSION < 10)
case PyUnicode_WCHAR_KIND: for (int cpx = 0; cpx < pat_len; cpx++)
{
ucs_pattern[cpx] = ((wchar_t *) src)[cpx];
}
break;
#endif
#endif
case PyUnicode_1BYTE_KIND:
#ifdef TRE_WCHAR
for (int cpx = 0; cpx < pat_len; cpx++)
{
ucs_pattern[cpx] = ((Py_UCS1 *) src)[cpx];
}
#else
memcpy(ucs_pattern,src,pat_len*sizeof(Py_UCS1));
#endif
break;
#ifdef TRE_WCHAR
case PyUnicode_2BYTE_KIND:
for (int cpx = 0; cpx < pat_len; cpx++)
{
ucs_pattern[cpx] = ((Py_UCS2 *) src)[cpx];
}
break;
case PyUnicode_4BYTE_KIND:
memcpy(ucs_pattern,src,pat_len*sizeof(Py_UCS4));
break;
#endif
default:
PyErr_Format(PyExc_ValueError, "In compile(), argument is unrecognized or unsupported codepoint kind (%d)",codepoint_kind);
Py_DECREF(rv);
free(ucs_pattern);
return(NULL);
}
ucs_pattern[pat_len] = 0;
#ifdef FUTURE_RELEASE_GIL
Py_BEGIN_ALLOW_THREADS
#endif
#ifdef TRE_WCHAR
rc = tre_regwncomp(&rv->rgx, (wchar_t const *) ucs_pattern, pat_len, cflags);
#else
rc = tre_regncomp(&rv->rgx, (char const *) ucs_pattern, pat_len, cflags);
#endif
#ifdef FUTURE_RELEASE_GIL
Py_END_ALLOW_THREADS
#endif
free(ucs_pattern);
if (rc != REG_OK)
{
if (!PyErr_Occurred())
_set_tre_err(rc, &rv->rgx);
Py_DECREF(rv);
return NULL;
}
rv->flags = cflags;
return (PyObject*)rv;
}
static PyMethodDef tre_methods[] = {
{ "compile", PyTre_ncompile, METH_VARARGS,
"Compile a regular expression pattern, returning a "
TRE_MODULE ".pattern object" },
{NULL, NULL, 0, NULL}
};
static struct _tre_flags {
char *name;
int val;
} tre_flags[] = {
{ "EXTENDED", REG_EXTENDED },
{ "ICASE", REG_ICASE },
{ "NEWLINE", REG_NEWLINE },
{ "NOSUB", REG_NOSUB },
{ "LITERAL", REG_LITERAL },
{ "NOTBOL", REG_NOTBOL },
{ "NOTEOL", REG_NOTEOL },
{ NULL, 0 }
};
static struct PyModuleDef cModPyTRE =
{
PyModuleDef_HEAD_INIT,
"tre",
"Python module for TRE library\n\nModule exports "
"the only function: compile",
-1,
tre_methods
};
PyMODINIT_FUNC
PyInit_tre(void)
{
PyObject *m = NULL;
struct _tre_flags *fp;
if (PyType_Ready(&TreFuzzynessType) < 0)
{
PyErr_SetString(PyExc_Exception,"TreFuzzynessType not ready");
return(NULL);
}
if (PyType_Ready(&TreMatchType) < 0)
{
PyErr_SetString(PyExc_Exception,"TreMatchType not ready");
return(NULL);
}
if (PyType_Ready(&TrePatternType) < 0)
{
PyErr_SetString(PyExc_Exception,"TrePatternType not ready");
return(NULL);
}
m = PyModule_Create(&cModPyTRE);
if (m == NULL)
{
PyErr_SetString(PyExc_Exception,"PyModule_Create() failed");
return(NULL);
}
Py_INCREF(&TreFuzzynessType);
if (PyModule_AddObject(m, "Fuzzyness", (PyObject*)&TreFuzzynessType) < 0)
{
PyErr_SetString(PyExc_Exception,"PyModule_AddObject(Fuzzyness) failed");
return(NULL);
}
Py_INCREF(&TreMatchType);
if (PyModule_AddObject(m, "Match", (PyObject*)&TreMatchType) < 0)
{
PyErr_SetString(PyExc_Exception,"PyModule_AddObject(Match) failed");
return(NULL);
}
Py_INCREF(&TrePatternType);
if (PyModule_AddObject(m, "Pattern", (PyObject*)&TrePatternType) < 0)
{
PyErr_SetString(PyExc_Exception,"PyModule_AddObject(Pattern) failed");
return(NULL);
}
ErrorObject = PyErr_NewException(TRE_MODULE ".Error", NULL, NULL);
Py_INCREF(ErrorObject);
if (PyModule_AddObject(m, "Error", ErrorObject) < 0)
{
PyErr_SetString(PyExc_Exception,"PyModule_AddObject(Error) failed");
return(NULL);
}
for (fp = tre_flags; fp->name != NULL; fp++)
if (PyModule_AddIntConstant(m, fp->name, fp->val) < 0)
{
char const *mp1 = "PyModule_AddIntConstant(";
char const *mp2 = ") failed";
long len = strlen(mp1) + strlen(mp2) + strlen(fp->name) + 1;
char *msg = calloc(sizeof(char),len);
snprintf(msg,len,"%s%s%s",mp1,fp->name,mp2);
PyErr_SetString(PyExc_Exception,msg);
free(msg);
return(NULL);
}
return(m);
}