Browse Source

Implementing pickle interface for RPNExpr

Yann Weber 4 years ago
parent
commit
1973bbfa35
4 changed files with 271 additions and 10 deletions
  1. 227
    8
      python_rpnexpr.c
  2. 29
    1
      python_rpnexpr.h
  3. 1
    0
      test.c
  4. 14
    1
      tests/tests_rpn.py

+ 227
- 8
python_rpnexpr.c View File

@@ -22,6 +22,11 @@ PyMethodDef RPNExpr_methods[] = {
22 22
 	{"eval", (PyCFunction)rpnexpr_eval, METH_FASTCALL, "Evaluate an expression"},
23 23
 	{"reset_stack", (PyCFunction)rpnexpr_reset_stack, METH_NOARGS,
24 24
 		"Reset stack memory storage (set all items to 0)"},
25
+	{"__getstate__", (PyCFunction)rpnexpr_getstate, METH_NOARGS,
26
+		"Pickling method. Return a bytes repr of tokenized expression \
27
+and the stack state."},
28
+	{"__setstate__", (PyCFunction)rpnexpr_setstate, METH_O,
29
+		"Unpickling method"},
25 30
 	{NULL} //Sentinel
26 31
 };
27 32
 
@@ -31,7 +36,7 @@ PyMemberDef RPNExpr_members[] = {
31 36
 
32 37
 PyTypeObject RPNExprType = {
33 38
 	PyVarObject_HEAD_INIT(NULL, 0)
34
-	"rpn.RPNExpr",                     /* tp_name */
39
+	"pyrpn.RPNExpr",                     /* tp_name */
35 40
 	sizeof(PyRPNExpr_t),                        /* tp_basicsize */
36 41
 	0,                                               /* tp_itemsize */
37 42
 	(destructor)rpnexpr_del, /* tp_dealloc */
@@ -149,13 +154,7 @@ int rpnexpr_init(PyObject *self, PyObject *args, PyObject *kwds)
149 154
 		return -1;
150 155
 	}
151 156
 
152
-	if(!stack_size)
153
-	{
154
-		expr_self->args = NULL;
155
-		return 0;
156
-	}
157
-
158
-	expr_self->args = malloc(sizeof(unsigned long) * args_count);
157
+	expr_self->args = malloc(sizeof(rpn_value_t) * args_count);
159 158
 	if(!expr_self->args)
160 159
 	{
161 160
 		snprintf(err_str, 256,
@@ -191,6 +190,226 @@ void rpnexpr_del(PyObject *self)
191 190
 	}
192 191
 }
193 192
 
193
+
194
+PyObject* rpnexpr_getstate(PyObject *self, PyObject *noargs)
195
+{
196
+	PyObject *res, *part;
197
+	PyRPNExpr_state_t resbuf;
198
+	PyRPNExpr_t *expr_self;
199
+	size_t total_sz;
200
+	char err_str[128];
201
+
202
+	expr_self = (PyRPNExpr_t*)self;
203
+
204
+	if(expr_self->rpn->state != RPN_READY)
205
+	{
206
+		snprintf(err_str, 128, 
207
+			"RPNExpr.__getstate__() instance in bad state : %s",
208
+			expr_self->rpn->state==RPN_ERROR?"error":"init");
209
+		PyErr_SetString(PyExc_ValueError, err_str);
210
+		return NULL;
211
+	}
212
+
213
+	total_sz = sizeof(PyRPNExpr_state_t);
214
+	total_sz += expr_self->rpn->toks.tokens_sz * sizeof(rpn_token_t);
215
+	total_sz += expr_self->rpn->stack_sz * sizeof(rpn_value_t);
216
+
217
+	resbuf.total_sz = total_sz;
218
+	resbuf.argc = expr_self->rpn->args_count;
219
+	resbuf.stack_sz = expr_self->rpn->stack_sz;
220
+	resbuf.token_sz = expr_self->rpn->toks.tokens_sz;
221
+
222
+	if(!(res = PyBytes_FromStringAndSize((char*)&resbuf, sizeof(resbuf))))
223
+	{
224
+		return NULL;
225
+	}
226
+	if(resbuf.stack_sz)
227
+	{
228
+		if(!(part=PyBytes_FromStringAndSize(
229
+				(char*)expr_self->rpn->stack,
230
+				sizeof(rpn_value_t) * resbuf.stack_sz)))
231
+		{
232
+			return NULL;
233
+		}
234
+		PyBytes_ConcatAndDel(&res, part);
235
+		if(!res)
236
+		{
237
+			return NULL;
238
+		}
239
+	}
240
+	if(resbuf.token_sz)
241
+	{
242
+		if(!(part=PyBytes_FromStringAndSize(
243
+				(char*)expr_self->rpn->toks.tokens,
244
+				sizeof(rpn_token_t) * resbuf.token_sz)))
245
+		{
246
+			return NULL;
247
+		}
248
+		PyBytes_ConcatAndDel(&res, part);
249
+		if(!res)
250
+		{
251
+			return NULL;
252
+		}
253
+	}
254
+	return res;
255
+}
256
+
257
+PyObject* rpnexpr_setstate(PyObject *self, PyObject *state_bytes)
258
+{
259
+	PyObject *tmp, *tmp2;
260
+	PyRPNExpr_state_t *state;
261
+	PyRPNExpr_t *expr_self;
262
+	rpn_value_t *stack;
263
+	rpn_tokenized_t toks;
264
+	const char *data;
265
+	size_t bsize, csize;
266
+	int err;
267
+	char err_str[256];
268
+	size_t i;
269
+
270
+	expr_self = (PyRPNExpr_t*)self;
271
+	
272
+	if(!PyBytes_Check(state_bytes)) /* Arg check */
273
+	{
274
+		tmp2 = NULL;
275
+		tmp = PyObject_Type(state_bytes);
276
+		if(tmp)
277
+		{
278
+			tmp2 = PyObject_Str(tmp);
279
+		}
280
+		if(tmp2)
281
+		{
282
+			snprintf(err_str, 128, 
283
+				"RPNExpr.__setstate__() expected a bytes as \
284
+argument but %s found",
285
+				PyUnicode_AsUTF8(tmp2));
286
+			PyErr_SetString(PyExc_ValueError, err_str);
287
+		}
288
+		else
289
+		{
290
+			PyErr_SetString(PyExc_RuntimeError,
291
+				"Failing to fetch arguments type will \
292
+generating exception message !");
293
+		}
294
+		return NULL;
295
+	}
296
+	if(expr_self->rpn || expr_self->args) /* checking instance state */
297
+	{
298
+		PyErr_SetString(PyExc_ValueError,
299
+			"RPNExpr.__getstate__() instance in bad state : \
300
+should not be initialized");
301
+		return NULL;
302
+	}
303
+	
304
+	/* Checking data size */
305
+	bsize = PyBytes_GET_SIZE(state_bytes);
306
+	data = PyBytes_AS_STRING(state_bytes);
307
+	state = (PyRPNExpr_state_t*)data;
308
+
309
+	if(bsize < sizeof(size_t))
310
+	{
311
+		PyErr_SetString(PyExc_ValueError, "Invalid argument");
312
+	}
313
+	if(bsize != state->total_sz)
314
+	{
315
+		snprintf(err_str, 128,
316
+			"RPNExpr.__setstate__() error : expected state to \
317
+contains %ld bytes but %ld found",
318
+			state->total_sz, bsize);
319
+		PyErr_SetString(PyExc_ValueError, err_str);
320
+	}
321
+
322
+	/* Checking data "integrity" */
323
+	csize = sizeof(PyRPNExpr_state_t) +
324
+		state->token_sz * sizeof(rpn_token_t) +
325
+		state->stack_sz * sizeof(rpn_value_t);
326
+	if(state->total_sz != csize)
327
+	{
328
+		snprintf(err_str, 128,
329
+			"RPNExpr.__setstate__() error : should have %ld as \
330
+total size but %ld found",
331
+			csize, state->total_sz);
332
+		PyErr_SetString(PyExc_ValueError, err_str);
333
+		return NULL;
334
+	}
335
+
336
+
337
+	/* Alloc and init rpn expression & args buffer*/
338
+	if(!(expr_self->rpn = malloc(sizeof(rpn_expr_t))))
339
+	{
340
+		err = errno;
341
+		snprintf(err_str, 128, 
342
+			"RPNExpr.__setstate__() failed to allocate memory for \
343
+rpn_expr_t : %s",
344
+			strerror(err));
345
+		PyErr_SetString(PyExc_MemoryError, err_str);
346
+		return NULL;
347
+	}
348
+	bzero(expr_self->rpn, sizeof(rpn_expr_t));
349
+	if(!(expr_self->args = malloc(sizeof(rpn_value_t)*state->argc)))
350
+	{
351
+		err = errno;
352
+		snprintf(err_str, 128, 
353
+			"RPNExpr.__setstate__() failed to allocate memory for \
354
+args buffer : %s",
355
+			strerror(err));
356
+		PyErr_SetString(PyExc_MemoryError, err_str);
357
+		return NULL;
358
+	}
359
+
360
+	if(rpn_expr_init(expr_self->rpn, state->stack_sz, state->argc) < 0)
361
+	{
362
+		snprintf(err_str, 256,
363
+			"RPNExpr.__setstate__() failed to init RPN expression \
364
+: %s",
365
+			expr_self->rpn->err_reason);
366
+		PyErr_SetString(PyExc_RuntimeError, err_str);
367
+		goto free_err;
368
+	}
369
+
370
+	/* restore stack & untokenize expression */
371
+	stack = (rpn_value_t*)(state+1);
372
+	memcpy(expr_self->rpn->stack, stack,
373
+		sizeof(rpn_value_t)*state->stack_sz);
374
+
375
+	toks.argc = state->argc;
376
+	toks.tokens_sz = state->token_sz;
377
+	toks.tokens = malloc(sizeof(rpn_token_t)*state->token_sz);
378
+	if(!toks.tokens)
379
+	{
380
+		snprintf(err_str, 128,
381
+			"RPNExpr.__setstate__() failed to allocate tokens \
382
+: %s",
383
+			strerror(errno));
384
+		PyErr_SetString(PyExc_RuntimeError, err_str);
385
+		goto close_err;
386
+	}
387
+	memcpy(toks.tokens, (rpn_token_t*)(stack+state->stack_sz),
388
+		sizeof(rpn_token_t)*state->token_sz);
389
+
390
+	for(i=0; i<toks.tokens_sz;i++)
391
+	{
392
+		// restore op pointers
393
+		toks.tokens[i].op = &(rpn_ops[toks.tokens[i].op_n]);
394
+	}
395
+	
396
+	
397
+	if(rpn_expr_untokenize(expr_self->rpn, &toks, 0) < 0)
398
+	{
399
+		snprintf(err_str, 256,
400
+			"RPNExpr.__setstate__() unable to untokenize : %s",
401
+			expr_self->rpn->err_reason);
402
+		PyErr_SetString(PyExc_RuntimeError, err_str);
403
+		goto close_err;
404
+	}
405
+
406
+	Py_RETURN_NONE;
407
+	close_err:
408
+		rpn_expr_close(expr_self->rpn);
409
+	free_err:
410
+		return NULL;
411
+}
412
+
194 413
 PyObject* rpnexpr_eval(PyObject* self, PyObject** argv, Py_ssize_t argc)
195 414
 {
196 415
 	PyRPNExpr_t *expr_self;

+ 29
- 1
python_rpnexpr.h View File

@@ -62,9 +62,20 @@ typedef struct
62 62
 
63 63
 	/**@brief Array storing expression argument
64 64
 	 * @note As attribute of rpn_expr allowing malloc & free only once */
65
-	unsigned long *args;
65
+	rpn_value_t *args;
66 66
 } PyRPNExpr_t;
67 67
 
68
+/**@brief Organize PyRPNExpr_t state data
69
+ * @see rpnexpr_getstate
70
+ * @see rpnexpr_setstate */
71
+typedef struct
72
+{
73
+	size_t total_sz;
74
+	size_t argc;
75
+	unsigned char stack_sz;
76
+	size_t token_sz;
77
+} PyRPNExpr_state_t;
78
+
68 79
 /**@brief RpnExpr __new__ method
69 80
  * @param subtype Type of object being created (pyrpn.RPNExpr)
70 81
  * @param args positional arguments for subtype
@@ -87,6 +98,23 @@ int rpnexpr_init(PyObject *self, PyObject *args, PyObject *kwds);
87 98
  */
88 99
 void rpnexpr_del(PyObject *self);
89 100
 
101
+/**@brief RPNExpr __getstate__ method for pickling
102
+ * @param cls RPNExpr type object
103
+ * @param noargs Not an argument...
104
+ * @return A bytes Python instance suitable as argument for
105
+ * @ref rpnexpr_setstate
106
+ */
107
+PyObject* rpnexpr_getstate(PyObject *cls, PyObject *noargs);
108
+
109
+/**@brief RPNExpr __setstate__ method for pickling
110
+ * @param cls RPNExpr type object
111
+ * @param state Should by a bytes Python instance returned by @ref
112
+ * rpnexpr_getstate
113
+ * @return A bytes Python instance suitable as argument for
114
+ * @ref rpnexpr_setstate
115
+ */
116
+PyObject* rpnexpr_setstate(PyObject *cls, PyObject *state);
117
+
90 118
 /**@brief Eval an RPN expression given arguments and return the
91 119
  * value
92 120
  * @param self RPNExpr instance

+ 1
- 0
test.c View File

@@ -219,6 +219,7 @@ int test_tokenization()
219 219
 				expr_orig, expr_untok);
220 220
 			return 1;
221 221
 		}
222
+
222 223
 		rpn_expr_close(&expr);
223 224
 		free(expr_untok);
224 225
 	}

+ 14
- 1
tests/tests_rpn.py View File

@@ -20,6 +20,7 @@
20 20
 import sys
21 21
 import random
22 22
 import math
23
+import pickle
23 24
 
24 25
 import unittest
25 26
 from unittest import skip
@@ -144,7 +145,19 @@ class TestRpnCompile(unittest.TestCase):
144 145
             for j in range(256,4):
145 146
                 exprs = ' '.join(['+' for _ in range(i)])
146 147
                 expr = pyrpn.RPNExpr(exprs, j)
147
-            
148
+
149
+    def test_pickling(self):  
150
+        """ Pickle & unpickle tests """
151
+        argc = 4
152
+        for _ in range(512):
153
+            expr = pyrpn.RPNExpr(pyrpn.random_expr(2,10), argc)
154
+            pik = pickle.dumps(expr)
155
+            new_expr = pickle.loads(pik)
156
+            self.assertEqual(str(expr), str(new_expr))
157
+            args = [random.randint(0,IMAX) for _ in range(argc)]
158
+            self.assertEqual(expr.eval(*args), new_expr.eval(*args))
159
+
160
+
148 161
 
149 162
 class TestRpnEval(unittest.TestCase):
150 163
 

Loading…
Cancel
Save