rpnifs/python_rpnexpr.c

738 lines
17 KiB
C

/*
* Copyright (C) 2020,2023 Weber Yann
*
* This file is part of pyrpn.
*
* pyrpn is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* pyrpn is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with pyrpn. If not, see <http://www.gnu.org/licenses/>.
*/
#include "python_rpnexpr.h"
/**@file python_rpnexpr.c
* @brief Python module & type definition
* @ingroup python_ext
* @ingroup python_pyrpn_RPNExpr
*/
/**@brief @ref pymod_pyrpn_RPNExpr methods definition
* @ingroup python_pyrpn_RPNExpr */
static PyMethodDef RPNExpr_methods[] = {
PYRPN_method("random", rpnexpr_random,
METH_CLASS | METH_VARARGS | METH_KEYWORDS,
"cls, args_count, token_count=10",
"Return a new random RPN expression string"),
PYRPN_method("default_mutation_params", rpnexpr_default_mutation_params,
METH_CLASS | METH_FASTCALL,
"cls, /",
"Return the default mutation parameters"),
PYRPN_method("eval", rpnexpr_eval, METH_FASTCALL,
"self, /, *args",
"Evaluate an expression"),
PYRPN_method("mutate", rpnexpr_mutate,
METH_VARARGS | METH_KEYWORDS,
"self, n_mutations=1, params=None",
"Mutate an expression"),
PYRPN_method("reset_stack", rpnexpr_reset_stack,
METH_NOARGS,
"self, /",
"Reset the stack (set all items to 0)"),
PYRPN_method("__getstate__", rpnexpr_getstate,
METH_NOARGS,
"self, /",
"Pickling method (see pickle module).\n"
"Return a bytes representation of the expression state."),
PYRPN_method("__setstate__", rpnexpr_setstate,
METH_O,
"self, state, /",
"Unpickling method (see pickle module)."),
PYRPN_method("__copy__", rpnexpr_copy,
METH_NOARGS,
"self, /",
"Return a new equivalent instance (see copy module)"),
PYRPN_method("uid", rpnexpr_getexprstate,
METH_NOARGS,
"self, /",
"Return a base64 uid for expression"),
{NULL} //Sentinel
};
/**@brief @ref pymod_pyrpn_RPNExpr members definition
* @ingroup python_pyrpn_RPNExpr */
static PyMemberDef RPNExpr_members[] = {
{NULL}
};
/**@brief @ref pymod_pyrpn_RPNExpr methods definition
* @todo Continue sequence implementation with contains, concat, repeat etc.
* @ingroup python_pyrpn_RPNExpr */
static PySequenceMethods RPNExpr_seq_methods = {
.sq_length = rpnexpr_len,
.sq_item = rpnexpr_token_item,
.sq_ass_item = rpnexpr_token_ass_item,
};
PyTypeObject RPNExprType = {
PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = "pyrpn.RPNExpr",
.tp_doc = "RPN expression evaluator\n\
\n\
Instanciation :\n\
RPNExpr(expression:str, args_count:int, stack_size:int=16)",
.tp_basicsize = sizeof(PyRPNExpr_t),
.tp_itemsize = 0,
.tp_del = rpnexpr_del,
.tp_repr = rpnexpr_repr,
.tp_str = rpnexpr_str,
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
.tp_richcompare = rpnexpr_richcompare,
.tp_methods = RPNExpr_methods,
.tp_as_sequence = &RPNExpr_seq_methods,
.tp_members = RPNExpr_members,
.tp_init = rpnexpr_init,
.tp_new = rpnexpr_new,
};
PyObject* rpnexpr_new(PyTypeObject *subtype, PyObject *args, PyObject* kwds)
{
PyObject *ret, *err;
PyRPNExpr_t *expr;
ret = PyType_GenericNew(subtype, args, kwds);
if((err = PyErr_Occurred()))
{
Py_DECREF(err);
return ret;
}
expr = (PyRPNExpr_t*)ret;
expr->rpn = NULL;
expr->args = NULL;
expr->borrowed_expr = 0;
return ret;
}
int rpnexpr_init(PyObject *self, PyObject *args, PyObject *kwds)
{
PyRPNExpr_t *expr_self;
char *names[] = {"expression", "args_count", "stack_size", NULL};
char err_str[256];
const char *expr;
long long int args_count, stack_size;
expr_self = (PyRPNExpr_t*)self;
stack_size = 16;
expr_self->rpn = NULL;
if(!PyArg_ParseTupleAndKeywords(args, kwds, "sL|L:RPNExpr.__init__", names, &expr,
&args_count, &stack_size))
{
return -1;
}
if(args_count < 0 || args_count > 255)
{
snprintf(err_str, 128,
"Argument count should be in [4..255] but %lld given",
args_count);
PyErr_SetString(PyExc_ValueError, err_str);
return -1;
}
if(stack_size < 4 || stack_size > 255)
{
snprintf(err_str, 128,
"Stack size should be in [0..255] but %lld given",
stack_size);
PyErr_SetString(PyExc_ValueError, err_str);
return -1;
}
expr_self->rpn = malloc(sizeof(rpn_expr_t));
if(!expr_self->rpn)
{
snprintf(err_str, 256,
"Expression memory allocation error : %s",
strerror(errno));
}
bzero(expr_self->rpn, sizeof(rpn_expr_t));
if(rpn_expr_init(expr_self->rpn, stack_size, args_count) < 0)
{
snprintf(err_str, 256,
"Expression init error : %s",
expr_self->rpn->err_reason);
PyErr_SetString(PyExc_ValueError, err_str);
return -1;
}
expr_self->args = malloc(sizeof(rpn_value_t) * args_count);
if(!expr_self->args)
{
snprintf(err_str, 256,
"Error allocating arguments memory : %s",
strerror(errno));
return -1;
}
if(rpn_expr_compile(expr_self->rpn, expr))
{
PyErr_SetString(PyExc_ValueError, expr_self->rpn->err_reason);
return -1;
}
return 0;
}
PyObject* rpnexpr_init_borrowing(rpn_expr_t *borrowed)
{
PyObject *args, *ret;
PyRPNExpr_t *instance;
args = Py_BuildValue("sLL", "", borrowed->args_count,
borrowed->stack_sz);
if(!args || PyErr_Occurred())
{
return NULL;
}
ret = PyObject_CallObject((PyObject*)&RPNExprType, args);
if(!ret || PyErr_Occurred())
{
Py_DECREF(args);
return NULL;
}
Py_DECREF(args);
instance = (PyRPNExpr_t*)ret;
rpn_expr_close(instance->rpn);
free(instance->rpn);
instance->borrowed_expr = 1;
instance->rpn = borrowed;
return ret;
}
void rpnexpr_del(PyObject *self)
{
PyRPNExpr_t *expr_self;
expr_self = (PyRPNExpr_t*)self;
if(expr_self->rpn && !expr_self->borrowed_expr)
{
rpn_expr_close(expr_self->rpn);
free(expr_self->rpn);
expr_self->rpn = NULL;
}
if(expr_self->args)
{
free(expr_self->args);
expr_self->args = NULL;
}
}
PyObject* rpnexpr_getexprstate(PyObject *self, PyObject *noargs)
{
/**@todo check if usefull */
PyErr_SetString(PyExc_NotImplementedError, "Not implemented...");
return NULL;
}
PyObject* rpnexpr_getstate(PyObject *self, PyObject *noargs)
{
PyObject *res;
PyRPNExpr_t *expr_self;
size_t total_sz;
char err_str[128];
expr_self = (PyRPNExpr_t*)self;
if(expr_self->rpn->state != RPN_READY)
{
snprintf(err_str, 128,
"RPNExpr.__getstate__() instance in bad state : %s",
expr_self->rpn->state==RPN_ERROR?"error":"init");
PyErr_SetString(PyExc_ValueError, err_str);
return NULL;
}
total_sz = rpn_expr_serialize(expr_self->rpn, NULL, 0);
char serial[total_sz];
rpn_expr_serialize(expr_self->rpn, serial, total_sz);
if(!(res = PyBytes_FromStringAndSize(serial, total_sz)))
{
return NULL;
}
return res;
}
PyObject* rpnexpr_setstate(PyObject *self, PyObject *state_bytes)
{
PyObject *tmp, *tmp2;
PyRPNExpr_t *expr_self;
const char *data;
size_t bsize;
int err;
char err_str[256];
expr_self = (PyRPNExpr_t*)self;
if(!PyBytes_Check(state_bytes)) /* Arg check */
{
tmp2 = NULL;
tmp = PyObject_Type(state_bytes);
if(tmp)
{
tmp2 = PyObject_Str(tmp);
}
if(tmp2)
{
snprintf(err_str, 128,
"RPNExpr.__setstate__() expected a bytes as \
argument but %s found",
PyUnicode_AsUTF8(tmp2));
PyErr_SetString(PyExc_ValueError, err_str);
}
else
{
PyErr_SetString(PyExc_RuntimeError,
"Failing to fetch arguments type will \
generating exception message !");
}
return NULL;
}
if(expr_self->rpn || expr_self->args) /* checking instance state */
{
PyErr_SetString(PyExc_ValueError,
"RPNExpr.__setstate__() instance in bad state : \
should not be initialized");
return NULL;
}
/* Checking data size */
bsize = PyBytes_GET_SIZE(state_bytes);
data = PyBytes_AS_STRING(state_bytes);
if(bsize < sizeof(size_t))
{
PyErr_SetString(PyExc_ValueError, "Invalid argument");
return NULL;
}
//alloc & init
if(!(expr_self->rpn = malloc(sizeof(rpn_expr_t))))
{
err = errno;
snprintf(err_str, 128,
"RPNExpr.__setstate__() failed to allocate memory for \
rpn_expr_t : %s",
strerror(err));
PyErr_SetString(PyExc_MemoryError, err_str);
return NULL;
}
bzero(expr_self->rpn, sizeof(rpn_expr_t));
if(rpn_expr_deserialize(expr_self->rpn, data, bsize) < 0)
{
PyErr_Format(PyExc_ValueError,
"RPNExpr.__setstate__() fails to deserialize (%s)) %s",
strerror(errno), expr_self->rpn->err_reason);
return NULL;
}
if(!(expr_self->args = malloc(sizeof(rpn_value_t)*expr_self->rpn->args_count)))
{
err = errno;
snprintf(err_str, 128,
"RPNExpr.__setstate__() failed to allocate memory for \
args buffer : %s",
strerror(err));
PyErr_SetString(PyExc_MemoryError, err_str);
return NULL;
}
Py_RETURN_NONE;
}
PyObject* rpnexpr_copy(PyObject *self, PyObject *noargs)
{
PyRPNExpr_t *copy;
PyObject *ret, *state, *setret;
ret = PyObject_CallMethod((PyObject*)&RPNExprType, "__new__", "O", &RPNExprType);
copy = (PyRPNExpr_t*)ret;
state = PyObject_CallMethod(self, "__getstate__", NULL);
if(PyErr_Occurred()) {
goto err;
}
setret = PyObject_CallMethod(ret, "__setstate__", "O", state);
if(PyErr_Occurred()) {
Py_DECREF(state);
goto err;
}
Py_DECREF(state);
Py_DECREF(setret);
return ret;
err:
PyObject_Del(copy);
return NULL;
}
Py_ssize_t rpnexpr_len(PyObject *self)
{
PyRPNExpr_t *expr_self = (PyRPNExpr_t*)self;
return (Py_ssize_t)expr_self->rpn->toks.tokens_sz;
}
PyObject* rpnexpr_token_item(PyObject *self, Py_ssize_t idx)
{
PyRPNExpr_t *expr_self = (PyRPNExpr_t*)self;
Py_ssize_t _idx = idx;
if(idx < 0)
{
idx = expr_self->rpn->toks.tokens_sz - 1 + idx;
}
if(idx < 0 || (size_t)idx >= expr_self->rpn->toks.tokens_sz)
{
PyErr_Format(PyExc_IndexError,
"No token %ld in expression of size %ld",
_idx, expr_self->rpn->toks.tokens_sz);
return NULL;
}
return rpntoken_from_token(&expr_self->rpn->toks.tokens[idx]);
}
int rpnexpr_token_ass_item(PyObject *self, Py_ssize_t idx, PyObject* elt)
{
PyRPNExpr_t *expr_self = (PyRPNExpr_t*)self;
Py_ssize_t _idx = idx;
if(idx < 0)
{
idx = expr_self->rpn->toks.tokens_sz - 1 + idx;
}
if(idx < 0 || (size_t)idx > expr_self->rpn->toks.tokens_sz)
{
PyErr_Format(PyExc_IndexError,
"Cannot set token %ld in expression of size %ld",
_idx, expr_self->rpn->toks.tokens_sz);
return -1;
}
if(!PyObject_IsInstance(elt, (PyObject*)&RPNTokenType))
{
PyErr_Format(PyExc_TypeError,
"Given element in not RPNToken subtype");
return -1;
}
short new_elt = 0;
if((size_t)idx == expr_self->rpn->toks.tokens_sz)
{
new_elt = 1;
expr_self->rpn->toks.tokens_sz++;
size_t new_sz = expr_self->rpn->toks.tokens_sz*sizeof(rpn_token_t);
rpn_token_t *tmp = realloc(expr_self->rpn->toks.tokens, new_sz);
if(!tmp)
{
PyErr_Format(PyExc_MemoryError,
"Error reallocation tokenized expression : %s",
strerror(errno));
return -1;
}
expr_self->rpn->toks.tokens = tmp;
}
rpn_token_t original = expr_self->rpn->toks.tokens[idx];
RPNToken_t *token = (RPNToken_t*)elt;
expr_self->rpn->toks.tokens[idx] = token->value;
if(rpn_expr_tokens_updated(expr_self->rpn) < 0)
{
PyErr_Format(PyExc_ValueError,
"Unable to update expression : %s",
strerror(errno));
goto rollback;
}
return 0;
rollback:
if(new_elt)
{
expr_self->rpn->toks.tokens_sz--;
}
else
{
expr_self->rpn->toks.tokens[idx] = original;
if(rpn_expr_tokens_updated(expr_self->rpn) < 0)
{
PyErr_Format(PyExc_RuntimeError,
"Unable to rollback expression : %s",
strerror(errno));
goto rollback;
}
}
return -1;
}
PyObject* rpnexpr_eval(PyObject* self, PyObject** argv, Py_ssize_t argc)
{
PyRPNExpr_t *expr_self;
unsigned long res;
char err_str[128];
Py_ssize_t i;
PyObject *cur, *ret;
expr_self = (PyRPNExpr_t*)self;
if((unsigned long)argc != expr_self->rpn->args_count)
{
snprintf(err_str, 128,
"RPNExpr expected %ld arguments but %ld given",
expr_self->rpn->args_count,
argc);
PyErr_SetString(PyExc_ValueError, err_str);
return NULL;
}
for(i=0; i<argc; i++)
{
cur = argv[i];
if(!PyLong_Check(cur))
{
snprintf(err_str, 128,
"RpnExpr.__call__ expect int as arguments but argument %ld is not",
i+1);
PyErr_SetString(PyExc_ValueError, err_str);
return NULL;
}
expr_self->args[i] = PyLong_AsUnsignedLong(argv[i]);
if((ret = PyErr_Occurred()))
{
Py_DECREF(ret);
return NULL;
}
}
res = rpn_expr_eval(expr_self->rpn, expr_self->args);
return PyLong_FromUnsignedLong(res);
}
PyObject* rpnexpr_mutate(PyObject* slf, PyObject *args, PyObject *kwds)
{
PyRPNExpr_t *self = (PyRPNExpr_t*)slf;
char *str_args = "|IO:RPNExpr.mutate";
char *names[] = {"n_mutations", "params", NULL};
PyObject *py_params = NULL;
unsigned int n_mutations = 1;
rpn_mutation_params_t params;
if(!PyArg_ParseTupleAndKeywords(args, kwds, str_args, names,
&n_mutations, &py_params))
{
return NULL;
}
if(!py_params || py_params == Py_None)
{
if(py_params == Py_None) { Py_DECREF(Py_None); }
memcpy(&params, &rpn_mutation_params_default,
sizeof(rpn_mutation_params_t));
}
else
{
if(pyrpn_pyobj_to_mutation_params(py_params, &params) < 0)
{
PyErr_SetString(PyExc_ValueError, "Bad value for params arguments");
return NULL;
}
}
for(size_t i=0; i<n_mutations; i++)
{
if(rpn_mutation(&(self->rpn->toks), &params) < 0)
{
PyErr_Format(PyExc_RuntimeError, "Mutation failed : %s",
strerror(errno));
return NULL;
}
}
rpn_expr_tokens_updated(self->rpn);
Py_RETURN_NONE;
}
PyObject* rpnexpr_reset_stack(PyObject *self, PyObject *noargs)
{
rpn_expr_reset_stack(((PyRPNExpr_t*)self)->rpn);
Py_RETURN_NONE;
}
PyObject* rpnexpr_str(PyObject *self)
{
PyRPNExpr_t *expr_self;
PyObject *res;
expr_self = (PyRPNExpr_t*)self;
res = Py_BuildValue("s", expr_self->rpn->expr);
return res;
}
PyObject* rpnexpr_repr(PyObject *self)
{
PyRPNExpr_t *expr_self;
PyObject *res;
size_t sz;
char *buff, err_str[128];
expr_self = (PyRPNExpr_t*)self;
sz = snprintf(NULL, 0, "<RPNExpr argc:%ld stck_sz:%d '%s'>",
expr_self->rpn->args_count, expr_self->rpn->stack_sz,
expr_self->rpn->expr);
buff = malloc(sizeof(char) * (sz + 1));
if(!buff)
{
snprintf(err_str, 128,
"Error allocating repr : %s",
strerror(errno));
PyErr_SetString(PyExc_RuntimeError, err_str);
return NULL;
}
snprintf(buff, sz+1, "<RPNExpr argc:%ld stck_sz:%d '%s'>",
expr_self->rpn->args_count, expr_self->rpn->stack_sz,
expr_self->rpn->expr);
res = Py_BuildValue("s", buff);
free(buff);
return res;
}
PyObject* rpnexpr_richcompare(PyObject *_self, PyObject *other, int op)
{
PyObject *sta, *stb, *res;
if(!PyObject_IsInstance(other, (PyObject*)&RPNExprType))
{
PyErr_Format(PyExc_TypeError,
"Can only be compared with RPNExpr");
return NULL;
}
sta = rpnexpr_getstate(_self, NULL);
stb = rpnexpr_getstate(other, NULL);
res = PyObject_RichCompare(sta, stb, op);
Py_DECREF(sta);
Py_DECREF(stb);
return res;
}
PyObject* rpnexpr_random(PyObject *cls, PyObject *args, PyObject *kwds)
{
long long int args_count, expr_sz;
char *expr, err_str[128];
PyObject *res;
char *names[] = {"args_count", "token_count", NULL};
expr_sz = 10;
if(!PyArg_ParseTupleAndKeywords(args, kwds, "L|L:pyrpn.random_expr", names,
&args_count, &expr_sz))
{
return NULL;
}
expr = rpn_random(expr_sz, args_count);
if(!expr)
{
snprintf(err_str, 128,
"Error generating random expression : %s",
strerror(errno));
PyErr_SetString(PyExc_RuntimeError, err_str);
return NULL;
}
res = Py_BuildValue("s", expr);
//free(expr);
return res;
}
PyObject* rpnexpr_default_mutation_params(PyObject *cls, PyObject **argv, Py_ssize_t argc)
{
PyObject *res, *wtypes;
if(!(wtypes = PyStructSequence_New(&rpn_token_types_SeqDesc)))
{
return NULL;
}
if(!(res = PyStructSequence_New(&rpn_mutation_params_SeqDesc)))
{
return NULL;
}
// wtypes filled with 1.0
PyObject *one = PyFloat_FromDouble(1.0);
for(size_t i=0; i<3; i++)
{
PyStructSequence_SET_ITEM(wtypes, i, one);
}
// max_len
PyStructSequence_SET_ITEM(res, 0,
PyLong_FromLong(rpn_mutation_params_default.min_len));
// weight_add
PyStructSequence_SET_ITEM(res, 1,
PyFloat_FromDouble(rpn_mutation_params_default.w_add));
// weight_del
PyStructSequence_SET_ITEM(res, 2,
PyFloat_FromDouble(rpn_mutation_params_default.w_del));
// weight_mut
PyStructSequence_SET_ITEM(res, 3,
PyFloat_FromDouble(rpn_mutation_params_default.w_mut));
// weight_mut_soft
PyStructSequence_SET_ITEM(res, 4,
PyFloat_FromDouble(rpn_mutation_params_default.w_mut_soft));
/** TODO use rpn_mutation_params_default instead of wtypes [1,1,1] */
// weight_add_elt
PyStructSequence_SET_ITEM(res, 5, wtypes);
Py_INCREF(wtypes);
// weight_mut_elt
PyStructSequence_SET_ITEM(res, 6, wtypes);
Py_INCREF(wtypes);
return res;
}