Browse Source

Implements expression's token classes for python

Yann Weber 1 year ago
parent
commit
a21802d66b
10 changed files with 751 additions and 5 deletions
  1. 16
    3
      python_pyrpn.c
  2. 1
    0
      python_pyrpn.h
  3. 21
    0
      python_rpnexpr.c
  4. 3
    0
      python_rpnexpr.h
  5. 417
    0
      python_rpntoken.c
  6. 80
    0
      python_rpntoken.h
  7. 4
    1
      rpn_jit.c
  8. 36
    1
      rpn_parse.c
  9. 11
    0
      rpn_parse.h
  10. 162
    0
      tests/tests_rpn_tokens.py

+ 16
- 3
python_pyrpn.c View File

@@ -49,7 +49,7 @@ PyMODINIT_FUNC
49 49
 PyInit_pyrpn(void)
50 50
 {
51 51
 
52
-	PyObject *mod, *const_mod;
52
+	PyObject *mod, *const_mod, *tokens_mod;
53 53
 	// init module & globals
54 54
 	mod = PyModule_Create(&rpnmodule);
55 55
 	if(mod == NULL) { return NULL; }
@@ -66,6 +66,18 @@ PyInit_pyrpn(void)
66 66
 		goto fail_add_const;
67 67
 	}
68 68
 
69
+	//init tokens module
70
+	tokens_mod = rpntokens_module_init();
71
+	if(tokens_mod == NULL)
72
+	{
73
+		goto fail_add_const;
74
+	}
75
+	Py_INCREF(tokens_mod);
76
+	if(PyModule_AddObject(mod, "tokens", tokens_mod) < 0)
77
+	{
78
+		goto fail_add_tokens;
79
+	}
80
+
69 81
 	// Init RPNExpr type
70 82
 	if(PyType_Ready(&RPNExprType) < 0)
71 83
 	{
@@ -137,6 +149,8 @@ fail_iter_type_ready:
137 149
 fail_add_rpnexpr:
138 150
 	Py_DECREF(&RPNExprType);
139 151
 fail_expr_type_ready:
152
+fail_add_tokens:
153
+	Py_DECREF(tokens_mod);
140 154
 fail_add_const:
141 155
 	Py_DECREF(const_mod);
142 156
 fail_init:
@@ -186,7 +200,7 @@ PyObject* pyrpn_random(PyObject *mod, PyObject *args, PyObject *kwds)
186 200
 		return NULL;
187 201
 	}
188 202
 	
189
-	expr = rpn_random(expr_sz, args_count);
203
+	expr = expr_sz?rpn_random(expr_sz, args_count):"";
190 204
 	if(!expr)
191 205
 	{
192 206
 		snprintf(err_str, 128,
@@ -196,6 +210,5 @@ PyObject* pyrpn_random(PyObject *mod, PyObject *args, PyObject *kwds)
196 210
 		return NULL;
197 211
 	}
198 212
 	res = Py_BuildValue("s", expr);
199
-	//free(expr);
200 213
 	return res;
201 214
 }

+ 1
- 0
python_pyrpn.h View File

@@ -31,6 +31,7 @@
31 31
 #include "python_rpnexpr.h"
32 32
 #include "python_if.h"
33 33
 #include "python_const.h"
34
+#include "python_rpntoken.h"
34 35
 
35 36
 /**@defgroup python_ext Python API
36 37
  * @brief Python API definitions

+ 21
- 0
python_rpnexpr.c View File

@@ -48,6 +48,7 @@ PyMemberDef RPNExpr_members[] = {
48 48
 
49 49
 PySequenceMethods RPNExpr_seq_methods = {
50 50
 	.sq_length = rpnexpr_len,
51
+	.sq_item = rpnexpr_token_item,
51 52
 };
52 53
 
53 54
 PyTypeObject RPNExprType = {
@@ -515,6 +516,26 @@ Py_ssize_t rpnexpr_len(PyObject *self)
515 516
 }
516 517
 
517 518
 
519
+PyObject* rpnexpr_token_item(PyObject *self, Py_ssize_t idx)
520
+{
521
+	PyRPNExpr_t *expr_self = (PyRPNExpr_t*)self;
522
+	Py_ssize_t _idx = idx;
523
+	if(idx < 0)
524
+	{
525
+		idx = expr_self->rpn->toks.tokens_sz - 1 + idx;
526
+	}
527
+	if(idx < 0 || idx >= expr_self->rpn->toks.tokens_sz)
528
+	{
529
+		PyErr_Format(PyExc_IndexError,
530
+			"No token %ld in expression of size %ld",
531
+			_idx, expr_self->rpn->toks.tokens_sz);
532
+		return NULL;
533
+
534
+	}
535
+	return rpntoken_from_token(&expr_self->rpn->toks.tokens[idx]);
536
+}
537
+
538
+
518 539
 PyObject* rpnexpr_eval(PyObject* self, PyObject** argv, Py_ssize_t argc)
519 540
 {
520 541
 	PyRPNExpr_t *expr_self;

+ 3
- 0
python_rpnexpr.h View File

@@ -29,6 +29,7 @@
29 29
 #include "structmember.h"
30 30
 
31 31
 #include "rpn_jit.h"
32
+#include "python_rpntoken.h"
32 33
 #include "python_const.h"
33 34
 
34 35
 /**@defgroup python_type RPNExpr Python class
@@ -152,6 +153,8 @@ PyObject* rpnexpr_copy(PyObject *cls, PyObject *noargs);
152 153
  */
153 154
 Py_ssize_t rpnexpr_len(PyObject *self);
154 155
 
156
+PyObject* rpnexpr_token_item(PyObject *self, Py_ssize_t);
157
+
155 158
 /**@brief Eval an RPN expression given arguments and return the
156 159
  * value
157 160
  * @param self RPNExpr instance

+ 417
- 0
python_rpntoken.c View File

@@ -0,0 +1,417 @@
1
+#include "python_rpntoken.h"
2
+
3
+static PyMethodDef RPNToken_methods[] = {
4
+	{"from_str", (PyCFunction)rpntoken_from_str, METH_CLASS | METH_O,
5
+		"Return a new RPNToken subclass instance from string"},
6
+	{NULL} //
7
+};
8
+
9
+PyTypeObject RPNTokenType = {
10
+	PyVarObject_HEAD_INIT(NULL, 0)
11
+	.tp_name = "pyrpn.tokens.Token",
12
+	.tp_doc = "Abstract class for RPN expression tokens",
13
+	.tp_basicsize = sizeof(RPNToken_t),
14
+	.tp_itemsize = 0,
15
+	.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
16
+	.tp_init = rpntoken_init,
17
+	.tp_new = PyType_GenericNew,
18
+	.tp_str = rpntoken_str,
19
+	.tp_repr = rpntoken_repr,
20
+	.tp_methods = RPNToken_methods,
21
+};
22
+
23
+static PyMethodDef RPNTokenOp_methods[] = {
24
+	{"opcode_max", (PyCFunction)rpntokenop_opcode_max,
25
+		 METH_STATIC | METH_NOARGS,
26
+		"Return the maximum valid value for an opcode"},
27
+	{"chr", (PyCFunction)rpntokenop_opchr, METH_NOARGS,
28
+		"Return the single char representation of the operand"},
29
+	{"str", (PyCFunction)rpntokenop_opstr, METH_NOARGS,
30
+		"Return the string (multi-char) representation of the operand"},
31
+	{NULL} //
32
+};
33
+
34
+static PyMemberDef RPNTokenOp_members[] = {
35
+	{"opcode", T_BYTE, offsetof(RPNTokenOp_t, super.value.op_n), READONLY,
36
+		"The number representing the operand"},
37
+	/*
38
+	{"chr", T_CHAR, offsetof(RPNTokenOp_t, super.value.op->chr), READONLY,
39
+		"The single char representation of the operand"},
40
+	{"str", T_STRING, offsetof(RPNTokenOp_t, super.value.op->str), READONLY,
41
+		"The str representation of the operand"},
42
+	*/
43
+	{NULL} //
44
+};
45
+
46
+PyTypeObject RPNTokenOpType = {
47
+	PyVarObject_HEAD_INIT(NULL, 0)
48
+	.tp_base = &RPNTokenType,
49
+	.tp_name = "pyrpn.tokens.Operand",
50
+	.tp_doc = "RPN expression operand token",
51
+	.tp_basicsize = sizeof(RPNTokenOp_t),
52
+	.tp_itemsize = 0,
53
+	.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
54
+	.tp_init = rpntokenop_init,
55
+	.tp_members = RPNTokenOp_members,
56
+	.tp_methods = RPNTokenOp_methods,
57
+};
58
+
59
+static PyMemberDef RPNTokenArg_members[] = {
60
+	{"argno", T_ULONG, offsetof(RPNTokenOp_t, super.value.arg_n), READONLY,
61
+		"The argument number"},
62
+	{NULL} //
63
+};
64
+
65
+PyTypeObject RPNTokenArgType = {
66
+	PyVarObject_HEAD_INIT(NULL, 0)
67
+	.tp_base = &RPNTokenType,
68
+	.tp_name = "pyrpn.tokens.Argument",
69
+	.tp_doc = "RPN expression argument token",
70
+	.tp_basicsize = sizeof(RPNTokenArg_t),
71
+	.tp_itemsize = 0,
72
+	.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, 
73
+	.tp_init = rpntokenarg_init,
74
+	.tp_members = RPNTokenArg_members,
75
+};
76
+
77
+static PyMemberDef RPNTokenVal_members[] = {
78
+	{"value", T_ULONG, offsetof(RPNTokenOp_t, super.value.value), READONLY,
79
+		"The immediate value"},
80
+	{NULL} //
81
+};
82
+
83
+PyTypeObject RPNTokenValType = {
84
+	PyVarObject_HEAD_INIT(NULL, 0)
85
+	.tp_base = &RPNTokenType,
86
+	.tp_name = "pyrpn.tokens.Value",
87
+	.tp_doc = "RPN expression value token",
88
+	.tp_basicsize = sizeof(RPNTokenVal_t),
89
+	.tp_itemsize = 0,
90
+	.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, 
91
+	.tp_init = rpntokenval_init,
92
+	.tp_members = RPNTokenVal_members,
93
+};
94
+
95
+PyModuleDef rpntokens_module = {
96
+	PyModuleDef_HEAD_INIT,
97
+	.m_name = "pyrpn.tokens",
98
+	.m_doc = "RPN expression tokens classes",
99
+	.m_size = -1,
100
+	.m_methods = NULL,
101
+	.m_clear = NULL,
102
+};
103
+
104
+
105
+PyObject* rpntokens_module_init(void)
106
+{
107
+	PyObject *mod;
108
+
109
+	mod = PyModule_Create(&rpntokens_module);
110
+	if(!mod) { return NULL; }
111
+
112
+	if(PyType_Ready(&RPNTokenType) < 0
113
+			|| PyType_Ready(&RPNTokenOpType) < 0 \
114
+			|| PyType_Ready(&RPNTokenValType) < 0 \
115
+			|| PyType_Ready(&RPNTokenArgType) < 0)
116
+	{
117
+		return NULL;
118
+	}
119
+	Py_INCREF(&RPNTokenType);
120
+	Py_INCREF(&RPNTokenOpType);
121
+	Py_INCREF(&RPNTokenValType);
122
+	Py_INCREF(&RPNTokenArgType);
123
+
124
+	if(	PyModule_AddObject(mod, "Token",
125
+			(PyObject*)&RPNTokenType) < 0 \
126
+		|| PyModule_AddObject(mod, "Operand",
127
+			(PyObject*)&RPNTokenOpType) < 0 \
128
+		|| PyModule_AddObject(mod, "Value",
129
+			(PyObject*)&RPNTokenValType) < 0 \
130
+		|| PyModule_AddObject(mod, "Argument",
131
+			(PyObject*)&RPNTokenArgType) < 0)
132
+	{
133
+		Py_DECREF(&RPNTokenType);
134
+		Py_DECREF(&RPNTokenOpType);
135
+		Py_DECREF(&RPNTokenValType);
136
+		Py_DECREF(&RPNTokenArgType);
137
+		return NULL;
138
+	}
139
+
140
+	return mod;
141
+}
142
+
143
+PyObject* rpntoken_from_str(PyObject *cls, PyObject *arg)
144
+{
145
+	if(!PyUnicode_Check(arg))
146
+	{
147
+		PyErr_SetString(PyExc_TypeError, "Expected argument to be a str");
148
+		return NULL;
149
+	}
150
+	PyObject *bytes_str = PyUnicode_AsASCIIString(arg);
151
+	if(PyErr_Occurred())
152
+	{
153
+		return NULL;
154
+	}
155
+	const char *str = PyBytes_AS_STRING(bytes_str);
156
+	rpn_token_t token;
157
+
158
+	char err_str[64];
159
+	if(rpn_tokenize(str, &token, err_str) < 0)
160
+	{
161
+		Py_DECREF(bytes_str);
162
+		PyObject *ascii = PyObject_ASCII(arg);
163
+		PyErr_Format(PyExc_ValueError, "Unable to parse %s", ascii);
164
+		Py_DECREF(ascii);
165
+		return NULL;
166
+	}
167
+	Py_DECREF(bytes_str);
168
+
169
+	return rpntoken_from_token(&token);
170
+}
171
+
172
+
173
+PyObject* rpntoken_from_token(const rpn_token_t *token)
174
+{
175
+	PyTypeObject *type;
176
+
177
+	switch(token->type)
178
+	{
179
+		case RPN_op:
180
+			type = &RPNTokenOpType;
181
+			break;
182
+		case RPN_val:
183
+			type = &RPNTokenValType;
184
+			break;
185
+		case RPN_arg:
186
+			type = &RPNTokenArgType;
187
+			break;
188
+		default:
189
+			PyErr_SetString(PyExc_RuntimeError,
190
+					"Unrecognized parsed token");
191
+			return NULL;
192
+	}
193
+
194
+	PyObject *ret = PyObject_CallMethod((PyObject*)type, "__new__", "O", type);
195
+	if(PyErr_Occurred())
196
+	{
197
+		return NULL;
198
+	}
199
+	// Any child type should work here since struct begins with super
200
+	RPNToken_t *_ret = (RPNToken_t*)ret;
201
+	memcpy(&_ret->value, token, sizeof(*token));
202
+	return ret;
203
+
204
+}
205
+
206
+
207
+int rpntoken_init(PyObject *_self, PyObject *args, PyObject *kwds)
208
+{
209
+	PyErr_SetString(PyExc_NotImplementedError, "Abstract class");
210
+	return -1;
211
+}
212
+
213
+
214
+PyObject* rpntoken_repr(PyObject *_self)
215
+{
216
+	RPNToken_t *self = (RPNToken_t*)_self;
217
+
218
+
219
+	int needed_sz = rpn_token_snprintf(&self->value, NULL, 0);
220
+	if(needed_sz < 0)
221
+	{
222
+		PyErr_Format(PyExc_RuntimeError, "Error : %s", strerror(errno));
223
+		return NULL;
224
+	}
225
+	char str[needed_sz+1];
226
+	rpn_token_snprintf(&self->value, str, needed_sz+1);
227
+
228
+	PyTypeObject *tp = Py_TYPE(_self);
229
+	PyObject *tp_name = PyType_GetName(tp);
230
+	PyObject *bytes_str = PyUnicode_AsASCIIString(tp_name);
231
+	Py_DECREF(tp_name);
232
+	const char *typename = PyBytes_AS_STRING(bytes_str);
233
+
234
+	needed_sz = snprintf(NULL, 0, "<%s '%s'>", typename, str);
235
+	char res_str[needed_sz+1];
236
+	needed_sz = snprintf(res_str, needed_sz+1, "<%s '%s'>", typename, str);
237
+	Py_DECREF(bytes_str);
238
+
239
+	return PyUnicode_FromString(res_str);
240
+}
241
+
242
+PyObject* rpntoken_str(PyObject *_self)
243
+{
244
+	RPNToken_t *self = (RPNToken_t*)_self;
245
+
246
+	int needed_sz = rpn_token_snprintf(&self->value, NULL, 0);
247
+	if(needed_sz < 0)
248
+	{
249
+		PyErr_Format(PyExc_RuntimeError, "Error : %s", strerror(errno));
250
+		return NULL;
251
+	}
252
+	char str[needed_sz+1];
253
+	rpn_token_snprintf(&self->value, str, needed_sz+1);
254
+	return PyUnicode_FromString(str);
255
+}
256
+
257
+
258
+int rpntokenop_init(PyObject *_self, PyObject *args, PyObject *kwds)
259
+{
260
+	RPNTokenOp_t *self = (RPNTokenOp_t*)_self;
261
+	PyObject *pyop = NULL;
262
+	char *names[] = {"op", NULL};
263
+
264
+
265
+	if(!PyArg_ParseTupleAndKeywords(args, kwds, "O:RPNTokenOP.__init__",
266
+				names,
267
+				&pyop))
268
+	{
269
+		return -1;
270
+	}
271
+
272
+	Py_INCREF(pyop);
273
+
274
+	if(PyLong_Check(pyop))
275
+	{
276
+		//opcode given ?
277
+		long opcode = PyLong_AsLong(pyop);
278
+		if(PyErr_Occurred()) { return -1; }
279
+		if(opcode < 0)
280
+		{
281
+			PyErr_SetString(PyExc_ValueError, "Opcode cannot be negative");
282
+			return -1;
283
+		}
284
+		else if (opcode >= rpn_op_sz())
285
+		{
286
+			PyErr_Format(PyExc_ValueError,
287
+					"Maximum opcode is %ld but %ld given",
288
+					rpn_op_sz()-1, opcode);
289
+			return -1;
290
+		}
291
+		self->super.value.op_n = opcode;
292
+		self->super.value.op = rpn_op_from_opcode(opcode);
293
+	}
294
+	else if(PyUnicode_Check(pyop))
295
+	{
296
+		PyObject *bytes_str = PyUnicode_AsASCIIString(pyop);
297
+		if(PyErr_Occurred())
298
+		{
299
+			return -1;
300
+		}
301
+		const char *token_str = PyBytes_AS_STRING(bytes_str);
302
+		char err_str[64];
303
+
304
+		if(rpn_tokenize(token_str, &(self->super.value), err_str) < 0)
305
+		{
306
+			Py_DECREF(bytes_str);
307
+			PyObject *ascii = PyObject_ASCII(pyop);
308
+			PyErr_Format(PyExc_ValueError, "Unrecognized token '%s' : %s",
309
+					ascii, err_str);
310
+			Py_DECREF(ascii);
311
+			goto err;
312
+		}
313
+		Py_DECREF(bytes_str);
314
+		if(self->super.value.type != RPN_op)
315
+		{
316
+			PyErr_SetString(PyExc_TypeError, "Decoded token is not an operand");
317
+			goto err;
318
+		}
319
+	}
320
+	else
321
+	{
322
+		PyErr_SetString(PyExc_TypeError, "Given argument is neither str neither int");
323
+		goto err;
324
+
325
+	}
326
+
327
+	Py_DECREF(pyop);
328
+	return 0;
329
+
330
+err:
331
+	if(pyop)
332
+	{
333
+		Py_DECREF(pyop);
334
+	}
335
+	return -1;
336
+}
337
+
338
+PyObject *rpntokenop_opcode_max(PyObject* Py_UNUSED(_static))
339
+{
340
+	return PyLong_FromSize_t(rpn_op_sz()-1);
341
+}
342
+
343
+PyObject *rpntokenop_opchr(PyObject *_self, PyObject*  Py_UNUSED(_null))
344
+{
345
+	RPNTokenOp_t *self = (RPNTokenOp_t*)_self;
346
+	if(self->super.value.op->chr == '\0')
347
+	{
348
+		Py_RETURN_NONE;
349
+	}
350
+	char buf[2];
351
+	buf[1] = '\0';
352
+	buf[0] = self->super.value.op->chr;
353
+	return PyUnicode_FromString(buf);
354
+}
355
+
356
+
357
+PyObject *rpntokenop_opstr(PyObject *_self, PyObject*  Py_UNUSED(_null))
358
+{
359
+	RPNTokenOp_t *self = (RPNTokenOp_t*)_self;
360
+	if(!self->super.value.op->str)
361
+	{
362
+		Py_RETURN_NONE;	
363
+	}
364
+	Py_RETURN_NONE;	
365
+	return PyUnicode_FromString(self->super.value.op->str);
366
+}
367
+
368
+int rpntokenval_init(PyObject *_self, PyObject *args, PyObject *kwds)
369
+{
370
+	RPNTokenOp_t *self = (RPNTokenOp_t*)_self;
371
+	char *names[] = {"value", NULL};
372
+	PyObject *arg = NULL;
373
+
374
+	if(!PyArg_ParseTupleAndKeywords(args, kwds, "O:RPNTokenVal.__init__",
375
+				names, &arg))
376
+	{
377
+		return -1;
378
+	}
379
+	if(!PyLong_Check(arg))
380
+	{
381
+		PyErr_SetString(PyExc_TypeError, "Expected integer as argument");
382
+		return -1;
383
+	}
384
+	self->super.value.value = PyLong_AsUnsignedLong(arg);
385
+	if(PyErr_Occurred())
386
+	{
387
+		return -1;
388
+	}
389
+	self->super.value.type = RPN_val;
390
+	return 0;
391
+}
392
+
393
+int rpntokenarg_init(PyObject *_self, PyObject *args, PyObject *kwds)
394
+{
395
+	RPNTokenOp_t *self = (RPNTokenOp_t*)_self;
396
+	char *names[] = {"argno", NULL};
397
+	PyObject *arg = NULL;
398
+
399
+	if(!PyArg_ParseTupleAndKeywords(args, kwds, "O:RPNTokenArg.__init__",
400
+				names, &arg))
401
+	{
402
+		return -1;
403
+	}
404
+	if(!PyLong_Check(arg))
405
+	{
406
+		PyErr_SetString(PyExc_TypeError, "Expected integer as argument");
407
+		return -1;
408
+	}
409
+	self->super.value.arg_n = PyLong_AsUnsignedLong(arg);
410
+	if(PyErr_Occurred())
411
+	{
412
+		return -1;
413
+	}
414
+	self->super.value.type = RPN_arg;
415
+	return 0;
416
+}
417
+

+ 80
- 0
python_rpntoken.h View File

@@ -0,0 +1,80 @@
1
+/*
2
+ * Copyright (C) 2023 Weber Yann
3
+ * 
4
+ * This file is part of pyrpn.
5
+ * 
6
+ * pyrpn is free software: you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation, either version 3 of the License, or
9
+ * any later version.
10
+ * 
11
+ * pyrpn is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+ * GNU General Public License for more details.
15
+ * 
16
+ * You should have received a copy of the GNU General Public License
17
+ * along with pyrpn.  If not, see <http://www.gnu.org/licenses/>.
18
+ */
19
+#ifndef _PYTHON_RPNTOKEN_H__
20
+#define _PYTHON_RPNTOKEN_H__
21
+
22
+#include "config.h"
23
+#include "rpn_parse.h"
24
+
25
+#define PY_SSIZE_T_CLEAN
26
+#include <Python.h>
27
+#include "structmember.h"
28
+
29
+/**@file python_rpnexpr.h
30
+ * @brief Python RPNToken type headers
31
+ * @ingroup python_type
32
+ *
33
+ * This file is the header of the RPNToken classes and subclasses
34
+ *
35
+ */
36
+
37
+
38
+extern PyTypeObject RPNTokenType;
39
+extern PyTypeObject RPNTokenOpType;
40
+extern PyTypeObject RPNTokenValType;
41
+extern PyTypeObject RPNTokenArgType;
42
+extern PyModuleDef rpntokens_module;
43
+
44
+typedef struct
45
+{
46
+	PyObject_HEAD;
47
+	rpn_token_t value;
48
+} RPNToken_t;
49
+
50
+typedef struct {
51
+	RPNToken_t super;
52
+} RPNTokenOp_t;
53
+
54
+typedef struct {
55
+	RPNToken_t super;
56
+} RPNTokenVal_t;
57
+
58
+typedef struct {
59
+	RPNToken_t super;
60
+} RPNTokenArg_t;
61
+
62
+PyObject* rpntokens_module_init(void);
63
+
64
+PyObject* rpntoken_from_str(PyObject *_self, PyObject *arg);
65
+/** Instanciate a new RPNToken subclass given a token */
66
+PyObject* rpntoken_from_token(const rpn_token_t *token);
67
+
68
+int rpntoken_init(PyObject *_self, PyObject *args, PyObject *kwds);
69
+PyObject* rpntoken_str(PyObject *_self);
70
+PyObject* rpntoken_repr(PyObject *_self);
71
+
72
+int rpntokenop_init(PyObject *_self, PyObject *args, PyObject *kwds);
73
+PyObject *rpntokenop_opcode_max(PyObject *Py_UNUSED(_static));
74
+PyObject *rpntokenop_opchr(PyObject *_self, PyObject*  Py_UNUSED(_null));
75
+PyObject *rpntokenop_opstr(PyObject *_self, PyObject*  Py_UNUSED(_null));
76
+
77
+int rpntokenval_init(PyObject *_self, PyObject *args, PyObject *kwds);
78
+int rpntokenarg_init(PyObject *_self, PyObject *args, PyObject *kwds);
79
+
80
+#endif

+ 4
- 1
rpn_jit.c View File

@@ -280,7 +280,10 @@ char* rpn_random(size_t op_sz, size_t args_count)
280 280
 			offset += nchr;
281 281
 		}
282 282
 	}
283
-	buff[offset] = '\0';
283
+	if(offset)
284
+	{
285
+		buff[offset-1] = '\0';
286
+	}
284 287
 	return buff;
285 288
 }
286 289
 

+ 36
- 1
rpn_parse.c View File

@@ -304,7 +304,11 @@ char* rpn_tokenized_expr(rpn_tokenized_t *tokens, char long_op)
304 304
 		#endif
305 305
 		cur += written;
306 306
 	}
307
-
307
+	
308
+	if(cur > expr)
309
+	{
310
+		*(cur-1) = '\0';
311
+	}
308 312
 	return expr;
309 313
 
310 314
 	free_err:
@@ -324,6 +328,15 @@ const rpn_op_t* rpn_match_token(const char* token)
324 328
 	return &(rpn_ops[rep]);
325 329
 }
326 330
 
331
+const rpn_op_t* rpn_op_from_opcode(unsigned char opcode)
332
+{
333
+	if(opcode > RPN_OP_SZ)
334
+	{
335
+		return NULL;
336
+	}
337
+	return &(rpn_ops[opcode]);
338
+}
339
+
327 340
 int rpn_match_token_i(const char* token)
328 341
 {
329 342
 	unsigned char i;
@@ -387,6 +400,28 @@ int rpn_match_number(const char* token, unsigned long *result)
387 400
 	return 0;
388 401
 }
389 402
 
403
+int rpn_token_snprintf(rpn_token_t *tok, char *dst, size_t sz)
404
+{
405
+	switch(tok->type)
406
+	{
407
+		case RPN_op:
408
+			if(tok->op->chr)
409
+			{
410
+				return snprintf(dst, sz, "%c", tok->op->chr);
411
+			}
412
+			return snprintf(dst, sz, "%s", tok->op->str);
413
+		case RPN_val:
414
+			return snprintf(dst, sz, "0x%lX", tok->value);
415
+		case RPN_arg:
416
+			return snprintf(dst, sz, "A%lu", tok->arg_n);
417
+		default:
418
+			errno = EINVAL;
419
+			return -1;
420
+	}
421
+
422
+}
423
+
424
+
390 425
 size_t rpn_op_sz()
391 426
 {
392 427
 	return sizeof(rpn_ops)/sizeof(rpn_op_t);

+ 11
- 0
rpn_parse.h View File

@@ -221,6 +221,13 @@ char* rpn_tokenized_expr(rpn_tokenized_t *tokens, char long_op);
221 221
  * @ingroup rpn_parse
222 222
  */
223 223
 const rpn_op_t* rpn_match_token(const char* token);
224
+
225
+/**@brief Returns NULL or pointer on corresponding operation infos
226
+ * @param unsigned char opcode (index in @ref rpn_ops )
227
+ * @return NULL or operation informations
228
+ */
229
+const rpn_op_t* rpn_op_from_opcode(unsigned char opcode);
230
+
224 231
 /**@brief Return -1 or an index corresponding to @ref rpn_ops
225 232
  * @param token The token we want to match
226 233
  * @return NULL or operation informations
@@ -237,10 +244,14 @@ int rpn_match_token_i(const char* token);
237 244
  */
238 245
 int rpn_match_number(const char* token, unsigned long *result);
239 246
 
247
+/**@todo doc */
248
+int rpn_token_snprintf(rpn_token_t *token, char *dst, size_t sz);
249
+
240 250
 /**@brief Get operations list size
241 251
  * @return number of operations in @ref rpn_ops
242 252
  */
243 253
 size_t rpn_op_sz();
254
+#define RPN_OPS_SZ (sizeof(rpn_ops)/sizeof(rpn_op_t))
244 255
 
245 256
 /**@page rpn_lang RPN expression syntax
246 257
  * @brief Howto write an expression

+ 162
- 0
tests/tests_rpn_tokens.py View File

@@ -0,0 +1,162 @@
1
+#!/usr/bin/python3
2
+# Copyright 2023 Weber Yann
3
+#
4
+# This file is part of rpnifs.
5
+#
6
+#        geneifs is free software: you can redistribute it and/or modify
7
+#        it under the terms of the GNU General Public License as published by
8
+#        the Free Software Foundation, either version 3 of the License, or
9
+#        (at your option) any later version.
10
+#
11
+#        geneifs is distributed in the hope that it will be useful,
12
+#        but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+#        MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+#        GNU General Public License for more details.
15
+#
16
+#        You should have received a copy of the GNU General Public License
17
+#        along with geneifs.  If not, see <http://www.gnu.org/licenses/>.
18
+#
19
+
20
+import sys
21
+import random
22
+
23
+import unittest
24
+
25
+try:
26
+    import pyrpn
27
+except (ImportError, NameError) as e:
28
+    print("Error importing pyrpn. Try to run make.",
29
+          file=sys.stderr)
30
+    raise e
31
+
32
+
33
+class TestRpnToken(unittest.TestCase):
34
+    """ Testing tokens submodule """
35
+
36
+    def test_token_str_convertion(self):
37
+        """ Testing token <-> str convvertion """
38
+        expl = ('+', '-', '&',
39
+                'A1', 'A0', 'A1337',
40
+                '0x0', '0x1312')
41
+
42
+        for estr in expl:
43
+            t = pyrpn.tokens.Token.from_str(estr)
44
+            with self.subTest(str=estr, token=t):
45
+                self.assertEqual(str(t), estr)
46
+
47
+class TestRpnTokenOperand(unittest.TestCase):
48
+    """ Testing operand tokens """
49
+
50
+    def test_str_init(self):
51
+        """ Testing operand token initialization """
52
+        test_str = ('add', '+', '-', 'sub', 'and', 'xor')
53
+        for op in test_str:
54
+            t = pyrpn.tokens.Operand(op)
55
+            with self.subTest(opstr=op, token=t):
56
+                self.assertIn(str(t), [t.chr(), t.str()])
57
+
58
+    def test_int_init(self):
59
+        """ Testing operand token initialization with all opcodes """
60
+        for i in range(pyrpn.tokens.Operand.opcode_max()+1):
61
+            t = pyrpn.tokens.Operand(i)
62
+            with self.subTest(opcode=i, token=t):
63
+                self.assertEqual(i, t.opcode)
64
+
65
+    def test_badarg_init(self):
66
+        """ Testing operand token initialization with bad argument """
67
+        badargs = ('cxwccseuhefsdfiyg', -1, -150, 
68
+                   pyrpn.tokens.Operand.opcode_max()+1,
69
+                   pyrpn.tokens.Operand.opcode_max()*2)
70
+        badtypes = ('0x12', 'A1',
71
+                    dict(), tuple(), b'+')
72
+
73
+        for badarg in badargs:
74
+            with self.subTest(badarg=badarg):
75
+                with self.assertRaises(ValueError):
76
+                    t = pyrpn.tokens.Operand(badarg)
77
+
78
+        for badtype in badtypes:
79
+            with self.subTest(badtype=badtype):
80
+                with self.assertRaises(TypeError):
81
+                    t = pyrpn.tokens.Operand(badtype)
82
+
83
+class TestRpnTokenValue(unittest.TestCase):
84
+    """ Testing value tokens """
85
+
86
+    def test_init(self):
87
+        """ Testing value token initialization """
88
+        for _ in range(1000):
89
+            rnd = random.randint(0,(1<<64) -1 )
90
+            t = pyrpn.tokens.Value(rnd)
91
+            with self.subTest(token=t, value=rnd):
92
+                self.assertEqual(int(str(t), 16), rnd)
93
+
94
+    def test_badarg_init(self):
95
+        """ Testing value token initialization with bad argument """
96
+        badtypes = ('A1', 'add', '+', dict(), tuple(), 5.0)
97
+        for badtype in badtypes:
98
+            with self.subTest(badarg=badtype):
99
+                with self.assertRaises(TypeError):
100
+                    t = pyrpn.tokens.Value(badtype)
101
+                    self.assertEqual(int(str(t), 16), badtype)
102
+
103
+    def test_badarg_overflow(self):
104
+        """ Testing value token initialization overflow """
105
+        badtypes = (1<<64, -1)
106
+        for badtype in badtypes:
107
+            with self.subTest(badarg=badtype):
108
+                with self.assertRaises(OverflowError):
109
+                    t = pyrpn.tokens.Value(badtype)
110
+                    self.assertEqual(int(str(t), 16), badtype)
111
+
112
+class TestRpnTokenArgument(unittest.TestCase):
113
+    """ Testing argument tokens """
114
+
115
+    def test_init(self):
116
+        """ Testing argument token initialization """
117
+        for _ in range(1000):
118
+            rnd = random.randint(0,(1<<64) -1 )
119
+            t = pyrpn.tokens.Argument(rnd)
120
+            with self.subTest(token=t, argument=rnd):
121
+                self.assertEqual(int(str(t)[1:],10), rnd)
122
+
123
+    def test_badarg_init(self):
124
+        """ Testing argument token initialization with bad argument """
125
+        badtypes = ('0x1', 'add', '+', dict(), tuple(), 5.0)
126
+        for badtype in badtypes:
127
+            with self.subTest(badarg=badtype):
128
+                with self.assertRaises(TypeError):
129
+                    t = pyrpn.tokens.Argument(badtype)
130
+                    self.assertEqual(int(str(t), 16), badtype)
131
+
132
+    def test_badarg_overflow(self):
133
+        """ Testing argument token initialization overflow """
134
+        badtypes = (1<<64, -1)
135
+        for badtype in badtypes:
136
+            with self.subTest(badarg=badtype):
137
+                with self.assertRaises(OverflowError):
138
+                    t = pyrpn.tokens.Argument(badtype)
139
+                    self.assertEqual(int(str(t), 16), badtype)
140
+
141
+
142
+#TODO move in a file dedicated to RPNExpr checks
143
+class TestTokenExprRepr(unittest.TestCase):
144
+    """ Test RPNExpr sequence implementation """
145
+
146
+    def test_rpnexpr_sequence(self):
147
+        """ Testing sequence implementation of RPNExpr """
148
+        for _ in range(100):
149
+            argc = random.randint(0,100)
150
+            token_count = random.randint(0, 200)
151
+            expr_str = pyrpn.random_expr(argc, token_count)
152
+            expr = pyrpn.RPNExpr(expr_str, argc)
153
+            with self.subTest(argc=argc, token_count=token_count,
154
+                              expr=expr, expr_str=expr_str):
155
+                self.assertEqual(token_count, len(expr))
156
+                self.assertEqual(' '.join([str(token) for token in expr]),
157
+                                 expr_str)
158
+
159
+
160
+
161
+if __name__ == '__main__':
162
+    unittest.main()

Loading…
Cancel
Save