Implements expression's token classes for python
This commit is contained in:
parent
7531536af3
commit
a21802d66b
10 changed files with 751 additions and 5 deletions
|
|
@ -49,7 +49,7 @@ PyMODINIT_FUNC
|
|||
PyInit_pyrpn(void)
|
||||
{
|
||||
|
||||
PyObject *mod, *const_mod;
|
||||
PyObject *mod, *const_mod, *tokens_mod;
|
||||
// init module & globals
|
||||
mod = PyModule_Create(&rpnmodule);
|
||||
if(mod == NULL) { return NULL; }
|
||||
|
|
@ -66,6 +66,18 @@ PyInit_pyrpn(void)
|
|||
goto fail_add_const;
|
||||
}
|
||||
|
||||
//init tokens module
|
||||
tokens_mod = rpntokens_module_init();
|
||||
if(tokens_mod == NULL)
|
||||
{
|
||||
goto fail_add_const;
|
||||
}
|
||||
Py_INCREF(tokens_mod);
|
||||
if(PyModule_AddObject(mod, "tokens", tokens_mod) < 0)
|
||||
{
|
||||
goto fail_add_tokens;
|
||||
}
|
||||
|
||||
// Init RPNExpr type
|
||||
if(PyType_Ready(&RPNExprType) < 0)
|
||||
{
|
||||
|
|
@ -137,6 +149,8 @@ fail_iter_type_ready:
|
|||
fail_add_rpnexpr:
|
||||
Py_DECREF(&RPNExprType);
|
||||
fail_expr_type_ready:
|
||||
fail_add_tokens:
|
||||
Py_DECREF(tokens_mod);
|
||||
fail_add_const:
|
||||
Py_DECREF(const_mod);
|
||||
fail_init:
|
||||
|
|
@ -186,7 +200,7 @@ PyObject* pyrpn_random(PyObject *mod, PyObject *args, PyObject *kwds)
|
|||
return NULL;
|
||||
}
|
||||
|
||||
expr = rpn_random(expr_sz, args_count);
|
||||
expr = expr_sz?rpn_random(expr_sz, args_count):"";
|
||||
if(!expr)
|
||||
{
|
||||
snprintf(err_str, 128,
|
||||
|
|
@ -196,6 +210,5 @@ PyObject* pyrpn_random(PyObject *mod, PyObject *args, PyObject *kwds)
|
|||
return NULL;
|
||||
}
|
||||
res = Py_BuildValue("s", expr);
|
||||
//free(expr);
|
||||
return res;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@
|
|||
#include "python_rpnexpr.h"
|
||||
#include "python_if.h"
|
||||
#include "python_const.h"
|
||||
#include "python_rpntoken.h"
|
||||
|
||||
/**@defgroup python_ext Python API
|
||||
* @brief Python API definitions
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ PyMemberDef RPNExpr_members[] = {
|
|||
|
||||
PySequenceMethods RPNExpr_seq_methods = {
|
||||
.sq_length = rpnexpr_len,
|
||||
.sq_item = rpnexpr_token_item,
|
||||
};
|
||||
|
||||
PyTypeObject RPNExprType = {
|
||||
|
|
@ -515,6 +516,26 @@ Py_ssize_t rpnexpr_len(PyObject *self)
|
|||
}
|
||||
|
||||
|
||||
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 || 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]);
|
||||
}
|
||||
|
||||
|
||||
PyObject* rpnexpr_eval(PyObject* self, PyObject** argv, Py_ssize_t argc)
|
||||
{
|
||||
PyRPNExpr_t *expr_self;
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@
|
|||
#include "structmember.h"
|
||||
|
||||
#include "rpn_jit.h"
|
||||
#include "python_rpntoken.h"
|
||||
#include "python_const.h"
|
||||
|
||||
/**@defgroup python_type RPNExpr Python class
|
||||
|
|
@ -152,6 +153,8 @@ PyObject* rpnexpr_copy(PyObject *cls, PyObject *noargs);
|
|||
*/
|
||||
Py_ssize_t rpnexpr_len(PyObject *self);
|
||||
|
||||
PyObject* rpnexpr_token_item(PyObject *self, Py_ssize_t);
|
||||
|
||||
/**@brief Eval an RPN expression given arguments and return the
|
||||
* value
|
||||
* @param self RPNExpr instance
|
||||
|
|
|
|||
417
python_rpntoken.c
Normal file
417
python_rpntoken.c
Normal file
|
|
@ -0,0 +1,417 @@
|
|||
#include "python_rpntoken.h"
|
||||
|
||||
static PyMethodDef RPNToken_methods[] = {
|
||||
{"from_str", (PyCFunction)rpntoken_from_str, METH_CLASS | METH_O,
|
||||
"Return a new RPNToken subclass instance from string"},
|
||||
{NULL} //
|
||||
};
|
||||
|
||||
PyTypeObject RPNTokenType = {
|
||||
PyVarObject_HEAD_INIT(NULL, 0)
|
||||
.tp_name = "pyrpn.tokens.Token",
|
||||
.tp_doc = "Abstract class for RPN expression tokens",
|
||||
.tp_basicsize = sizeof(RPNToken_t),
|
||||
.tp_itemsize = 0,
|
||||
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
|
||||
.tp_init = rpntoken_init,
|
||||
.tp_new = PyType_GenericNew,
|
||||
.tp_str = rpntoken_str,
|
||||
.tp_repr = rpntoken_repr,
|
||||
.tp_methods = RPNToken_methods,
|
||||
};
|
||||
|
||||
static PyMethodDef RPNTokenOp_methods[] = {
|
||||
{"opcode_max", (PyCFunction)rpntokenop_opcode_max,
|
||||
METH_STATIC | METH_NOARGS,
|
||||
"Return the maximum valid value for an opcode"},
|
||||
{"chr", (PyCFunction)rpntokenop_opchr, METH_NOARGS,
|
||||
"Return the single char representation of the operand"},
|
||||
{"str", (PyCFunction)rpntokenop_opstr, METH_NOARGS,
|
||||
"Return the string (multi-char) representation of the operand"},
|
||||
{NULL} //
|
||||
};
|
||||
|
||||
static PyMemberDef RPNTokenOp_members[] = {
|
||||
{"opcode", T_BYTE, offsetof(RPNTokenOp_t, super.value.op_n), READONLY,
|
||||
"The number representing the operand"},
|
||||
/*
|
||||
{"chr", T_CHAR, offsetof(RPNTokenOp_t, super.value.op->chr), READONLY,
|
||||
"The single char representation of the operand"},
|
||||
{"str", T_STRING, offsetof(RPNTokenOp_t, super.value.op->str), READONLY,
|
||||
"The str representation of the operand"},
|
||||
*/
|
||||
{NULL} //
|
||||
};
|
||||
|
||||
PyTypeObject RPNTokenOpType = {
|
||||
PyVarObject_HEAD_INIT(NULL, 0)
|
||||
.tp_base = &RPNTokenType,
|
||||
.tp_name = "pyrpn.tokens.Operand",
|
||||
.tp_doc = "RPN expression operand token",
|
||||
.tp_basicsize = sizeof(RPNTokenOp_t),
|
||||
.tp_itemsize = 0,
|
||||
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
|
||||
.tp_init = rpntokenop_init,
|
||||
.tp_members = RPNTokenOp_members,
|
||||
.tp_methods = RPNTokenOp_methods,
|
||||
};
|
||||
|
||||
static PyMemberDef RPNTokenArg_members[] = {
|
||||
{"argno", T_ULONG, offsetof(RPNTokenOp_t, super.value.arg_n), READONLY,
|
||||
"The argument number"},
|
||||
{NULL} //
|
||||
};
|
||||
|
||||
PyTypeObject RPNTokenArgType = {
|
||||
PyVarObject_HEAD_INIT(NULL, 0)
|
||||
.tp_base = &RPNTokenType,
|
||||
.tp_name = "pyrpn.tokens.Argument",
|
||||
.tp_doc = "RPN expression argument token",
|
||||
.tp_basicsize = sizeof(RPNTokenArg_t),
|
||||
.tp_itemsize = 0,
|
||||
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
|
||||
.tp_init = rpntokenarg_init,
|
||||
.tp_members = RPNTokenArg_members,
|
||||
};
|
||||
|
||||
static PyMemberDef RPNTokenVal_members[] = {
|
||||
{"value", T_ULONG, offsetof(RPNTokenOp_t, super.value.value), READONLY,
|
||||
"The immediate value"},
|
||||
{NULL} //
|
||||
};
|
||||
|
||||
PyTypeObject RPNTokenValType = {
|
||||
PyVarObject_HEAD_INIT(NULL, 0)
|
||||
.tp_base = &RPNTokenType,
|
||||
.tp_name = "pyrpn.tokens.Value",
|
||||
.tp_doc = "RPN expression value token",
|
||||
.tp_basicsize = sizeof(RPNTokenVal_t),
|
||||
.tp_itemsize = 0,
|
||||
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
|
||||
.tp_init = rpntokenval_init,
|
||||
.tp_members = RPNTokenVal_members,
|
||||
};
|
||||
|
||||
PyModuleDef rpntokens_module = {
|
||||
PyModuleDef_HEAD_INIT,
|
||||
.m_name = "pyrpn.tokens",
|
||||
.m_doc = "RPN expression tokens classes",
|
||||
.m_size = -1,
|
||||
.m_methods = NULL,
|
||||
.m_clear = NULL,
|
||||
};
|
||||
|
||||
|
||||
PyObject* rpntokens_module_init(void)
|
||||
{
|
||||
PyObject *mod;
|
||||
|
||||
mod = PyModule_Create(&rpntokens_module);
|
||||
if(!mod) { return NULL; }
|
||||
|
||||
if(PyType_Ready(&RPNTokenType) < 0
|
||||
|| PyType_Ready(&RPNTokenOpType) < 0 \
|
||||
|| PyType_Ready(&RPNTokenValType) < 0 \
|
||||
|| PyType_Ready(&RPNTokenArgType) < 0)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
Py_INCREF(&RPNTokenType);
|
||||
Py_INCREF(&RPNTokenOpType);
|
||||
Py_INCREF(&RPNTokenValType);
|
||||
Py_INCREF(&RPNTokenArgType);
|
||||
|
||||
if( PyModule_AddObject(mod, "Token",
|
||||
(PyObject*)&RPNTokenType) < 0 \
|
||||
|| PyModule_AddObject(mod, "Operand",
|
||||
(PyObject*)&RPNTokenOpType) < 0 \
|
||||
|| PyModule_AddObject(mod, "Value",
|
||||
(PyObject*)&RPNTokenValType) < 0 \
|
||||
|| PyModule_AddObject(mod, "Argument",
|
||||
(PyObject*)&RPNTokenArgType) < 0)
|
||||
{
|
||||
Py_DECREF(&RPNTokenType);
|
||||
Py_DECREF(&RPNTokenOpType);
|
||||
Py_DECREF(&RPNTokenValType);
|
||||
Py_DECREF(&RPNTokenArgType);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return mod;
|
||||
}
|
||||
|
||||
PyObject* rpntoken_from_str(PyObject *cls, PyObject *arg)
|
||||
{
|
||||
if(!PyUnicode_Check(arg))
|
||||
{
|
||||
PyErr_SetString(PyExc_TypeError, "Expected argument to be a str");
|
||||
return NULL;
|
||||
}
|
||||
PyObject *bytes_str = PyUnicode_AsASCIIString(arg);
|
||||
if(PyErr_Occurred())
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
const char *str = PyBytes_AS_STRING(bytes_str);
|
||||
rpn_token_t token;
|
||||
|
||||
char err_str[64];
|
||||
if(rpn_tokenize(str, &token, err_str) < 0)
|
||||
{
|
||||
Py_DECREF(bytes_str);
|
||||
PyObject *ascii = PyObject_ASCII(arg);
|
||||
PyErr_Format(PyExc_ValueError, "Unable to parse %s", ascii);
|
||||
Py_DECREF(ascii);
|
||||
return NULL;
|
||||
}
|
||||
Py_DECREF(bytes_str);
|
||||
|
||||
return rpntoken_from_token(&token);
|
||||
}
|
||||
|
||||
|
||||
PyObject* rpntoken_from_token(const rpn_token_t *token)
|
||||
{
|
||||
PyTypeObject *type;
|
||||
|
||||
switch(token->type)
|
||||
{
|
||||
case RPN_op:
|
||||
type = &RPNTokenOpType;
|
||||
break;
|
||||
case RPN_val:
|
||||
type = &RPNTokenValType;
|
||||
break;
|
||||
case RPN_arg:
|
||||
type = &RPNTokenArgType;
|
||||
break;
|
||||
default:
|
||||
PyErr_SetString(PyExc_RuntimeError,
|
||||
"Unrecognized parsed token");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
PyObject *ret = PyObject_CallMethod((PyObject*)type, "__new__", "O", type);
|
||||
if(PyErr_Occurred())
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
// Any child type should work here since struct begins with super
|
||||
RPNToken_t *_ret = (RPNToken_t*)ret;
|
||||
memcpy(&_ret->value, token, sizeof(*token));
|
||||
return ret;
|
||||
|
||||
}
|
||||
|
||||
|
||||
int rpntoken_init(PyObject *_self, PyObject *args, PyObject *kwds)
|
||||
{
|
||||
PyErr_SetString(PyExc_NotImplementedError, "Abstract class");
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
PyObject* rpntoken_repr(PyObject *_self)
|
||||
{
|
||||
RPNToken_t *self = (RPNToken_t*)_self;
|
||||
|
||||
|
||||
int needed_sz = rpn_token_snprintf(&self->value, NULL, 0);
|
||||
if(needed_sz < 0)
|
||||
{
|
||||
PyErr_Format(PyExc_RuntimeError, "Error : %s", strerror(errno));
|
||||
return NULL;
|
||||
}
|
||||
char str[needed_sz+1];
|
||||
rpn_token_snprintf(&self->value, str, needed_sz+1);
|
||||
|
||||
PyTypeObject *tp = Py_TYPE(_self);
|
||||
PyObject *tp_name = PyType_GetName(tp);
|
||||
PyObject *bytes_str = PyUnicode_AsASCIIString(tp_name);
|
||||
Py_DECREF(tp_name);
|
||||
const char *typename = PyBytes_AS_STRING(bytes_str);
|
||||
|
||||
needed_sz = snprintf(NULL, 0, "<%s '%s'>", typename, str);
|
||||
char res_str[needed_sz+1];
|
||||
needed_sz = snprintf(res_str, needed_sz+1, "<%s '%s'>", typename, str);
|
||||
Py_DECREF(bytes_str);
|
||||
|
||||
return PyUnicode_FromString(res_str);
|
||||
}
|
||||
|
||||
PyObject* rpntoken_str(PyObject *_self)
|
||||
{
|
||||
RPNToken_t *self = (RPNToken_t*)_self;
|
||||
|
||||
int needed_sz = rpn_token_snprintf(&self->value, NULL, 0);
|
||||
if(needed_sz < 0)
|
||||
{
|
||||
PyErr_Format(PyExc_RuntimeError, "Error : %s", strerror(errno));
|
||||
return NULL;
|
||||
}
|
||||
char str[needed_sz+1];
|
||||
rpn_token_snprintf(&self->value, str, needed_sz+1);
|
||||
return PyUnicode_FromString(str);
|
||||
}
|
||||
|
||||
|
||||
int rpntokenop_init(PyObject *_self, PyObject *args, PyObject *kwds)
|
||||
{
|
||||
RPNTokenOp_t *self = (RPNTokenOp_t*)_self;
|
||||
PyObject *pyop = NULL;
|
||||
char *names[] = {"op", NULL};
|
||||
|
||||
|
||||
if(!PyArg_ParseTupleAndKeywords(args, kwds, "O:RPNTokenOP.__init__",
|
||||
names,
|
||||
&pyop))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
Py_INCREF(pyop);
|
||||
|
||||
if(PyLong_Check(pyop))
|
||||
{
|
||||
//opcode given ?
|
||||
long opcode = PyLong_AsLong(pyop);
|
||||
if(PyErr_Occurred()) { return -1; }
|
||||
if(opcode < 0)
|
||||
{
|
||||
PyErr_SetString(PyExc_ValueError, "Opcode cannot be negative");
|
||||
return -1;
|
||||
}
|
||||
else if (opcode >= rpn_op_sz())
|
||||
{
|
||||
PyErr_Format(PyExc_ValueError,
|
||||
"Maximum opcode is %ld but %ld given",
|
||||
rpn_op_sz()-1, opcode);
|
||||
return -1;
|
||||
}
|
||||
self->super.value.op_n = opcode;
|
||||
self->super.value.op = rpn_op_from_opcode(opcode);
|
||||
}
|
||||
else if(PyUnicode_Check(pyop))
|
||||
{
|
||||
PyObject *bytes_str = PyUnicode_AsASCIIString(pyop);
|
||||
if(PyErr_Occurred())
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
const char *token_str = PyBytes_AS_STRING(bytes_str);
|
||||
char err_str[64];
|
||||
|
||||
if(rpn_tokenize(token_str, &(self->super.value), err_str) < 0)
|
||||
{
|
||||
Py_DECREF(bytes_str);
|
||||
PyObject *ascii = PyObject_ASCII(pyop);
|
||||
PyErr_Format(PyExc_ValueError, "Unrecognized token '%s' : %s",
|
||||
ascii, err_str);
|
||||
Py_DECREF(ascii);
|
||||
goto err;
|
||||
}
|
||||
Py_DECREF(bytes_str);
|
||||
if(self->super.value.type != RPN_op)
|
||||
{
|
||||
PyErr_SetString(PyExc_TypeError, "Decoded token is not an operand");
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
PyErr_SetString(PyExc_TypeError, "Given argument is neither str neither int");
|
||||
goto err;
|
||||
|
||||
}
|
||||
|
||||
Py_DECREF(pyop);
|
||||
return 0;
|
||||
|
||||
err:
|
||||
if(pyop)
|
||||
{
|
||||
Py_DECREF(pyop);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
PyObject *rpntokenop_opcode_max(PyObject* Py_UNUSED(_static))
|
||||
{
|
||||
return PyLong_FromSize_t(rpn_op_sz()-1);
|
||||
}
|
||||
|
||||
PyObject *rpntokenop_opchr(PyObject *_self, PyObject* Py_UNUSED(_null))
|
||||
{
|
||||
RPNTokenOp_t *self = (RPNTokenOp_t*)_self;
|
||||
if(self->super.value.op->chr == '\0')
|
||||
{
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
char buf[2];
|
||||
buf[1] = '\0';
|
||||
buf[0] = self->super.value.op->chr;
|
||||
return PyUnicode_FromString(buf);
|
||||
}
|
||||
|
||||
|
||||
PyObject *rpntokenop_opstr(PyObject *_self, PyObject* Py_UNUSED(_null))
|
||||
{
|
||||
RPNTokenOp_t *self = (RPNTokenOp_t*)_self;
|
||||
if(!self->super.value.op->str)
|
||||
{
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
Py_RETURN_NONE;
|
||||
return PyUnicode_FromString(self->super.value.op->str);
|
||||
}
|
||||
|
||||
int rpntokenval_init(PyObject *_self, PyObject *args, PyObject *kwds)
|
||||
{
|
||||
RPNTokenOp_t *self = (RPNTokenOp_t*)_self;
|
||||
char *names[] = {"value", NULL};
|
||||
PyObject *arg = NULL;
|
||||
|
||||
if(!PyArg_ParseTupleAndKeywords(args, kwds, "O:RPNTokenVal.__init__",
|
||||
names, &arg))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
if(!PyLong_Check(arg))
|
||||
{
|
||||
PyErr_SetString(PyExc_TypeError, "Expected integer as argument");
|
||||
return -1;
|
||||
}
|
||||
self->super.value.value = PyLong_AsUnsignedLong(arg);
|
||||
if(PyErr_Occurred())
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
self->super.value.type = RPN_val;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int rpntokenarg_init(PyObject *_self, PyObject *args, PyObject *kwds)
|
||||
{
|
||||
RPNTokenOp_t *self = (RPNTokenOp_t*)_self;
|
||||
char *names[] = {"argno", NULL};
|
||||
PyObject *arg = NULL;
|
||||
|
||||
if(!PyArg_ParseTupleAndKeywords(args, kwds, "O:RPNTokenArg.__init__",
|
||||
names, &arg))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
if(!PyLong_Check(arg))
|
||||
{
|
||||
PyErr_SetString(PyExc_TypeError, "Expected integer as argument");
|
||||
return -1;
|
||||
}
|
||||
self->super.value.arg_n = PyLong_AsUnsignedLong(arg);
|
||||
if(PyErr_Occurred())
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
self->super.value.type = RPN_arg;
|
||||
return 0;
|
||||
}
|
||||
|
||||
80
python_rpntoken.h
Normal file
80
python_rpntoken.h
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
* Copyright (C) 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/>.
|
||||
*/
|
||||
#ifndef _PYTHON_RPNTOKEN_H__
|
||||
#define _PYTHON_RPNTOKEN_H__
|
||||
|
||||
#include "config.h"
|
||||
#include "rpn_parse.h"
|
||||
|
||||
#define PY_SSIZE_T_CLEAN
|
||||
#include <Python.h>
|
||||
#include "structmember.h"
|
||||
|
||||
/**@file python_rpnexpr.h
|
||||
* @brief Python RPNToken type headers
|
||||
* @ingroup python_type
|
||||
*
|
||||
* This file is the header of the RPNToken classes and subclasses
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
extern PyTypeObject RPNTokenType;
|
||||
extern PyTypeObject RPNTokenOpType;
|
||||
extern PyTypeObject RPNTokenValType;
|
||||
extern PyTypeObject RPNTokenArgType;
|
||||
extern PyModuleDef rpntokens_module;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
PyObject_HEAD;
|
||||
rpn_token_t value;
|
||||
} RPNToken_t;
|
||||
|
||||
typedef struct {
|
||||
RPNToken_t super;
|
||||
} RPNTokenOp_t;
|
||||
|
||||
typedef struct {
|
||||
RPNToken_t super;
|
||||
} RPNTokenVal_t;
|
||||
|
||||
typedef struct {
|
||||
RPNToken_t super;
|
||||
} RPNTokenArg_t;
|
||||
|
||||
PyObject* rpntokens_module_init(void);
|
||||
|
||||
PyObject* rpntoken_from_str(PyObject *_self, PyObject *arg);
|
||||
/** Instanciate a new RPNToken subclass given a token */
|
||||
PyObject* rpntoken_from_token(const rpn_token_t *token);
|
||||
|
||||
int rpntoken_init(PyObject *_self, PyObject *args, PyObject *kwds);
|
||||
PyObject* rpntoken_str(PyObject *_self);
|
||||
PyObject* rpntoken_repr(PyObject *_self);
|
||||
|
||||
int rpntokenop_init(PyObject *_self, PyObject *args, PyObject *kwds);
|
||||
PyObject *rpntokenop_opcode_max(PyObject *Py_UNUSED(_static));
|
||||
PyObject *rpntokenop_opchr(PyObject *_self, PyObject* Py_UNUSED(_null));
|
||||
PyObject *rpntokenop_opstr(PyObject *_self, PyObject* Py_UNUSED(_null));
|
||||
|
||||
int rpntokenval_init(PyObject *_self, PyObject *args, PyObject *kwds);
|
||||
int rpntokenarg_init(PyObject *_self, PyObject *args, PyObject *kwds);
|
||||
|
||||
#endif
|
||||
|
|
@ -280,7 +280,10 @@ char* rpn_random(size_t op_sz, size_t args_count)
|
|||
offset += nchr;
|
||||
}
|
||||
}
|
||||
buff[offset] = '\0';
|
||||
if(offset)
|
||||
{
|
||||
buff[offset-1] = '\0';
|
||||
}
|
||||
return buff;
|
||||
}
|
||||
|
||||
|
|
|
|||
37
rpn_parse.c
37
rpn_parse.c
|
|
@ -304,7 +304,11 @@ char* rpn_tokenized_expr(rpn_tokenized_t *tokens, char long_op)
|
|||
#endif
|
||||
cur += written;
|
||||
}
|
||||
|
||||
|
||||
if(cur > expr)
|
||||
{
|
||||
*(cur-1) = '\0';
|
||||
}
|
||||
return expr;
|
||||
|
||||
free_err:
|
||||
|
|
@ -324,6 +328,15 @@ const rpn_op_t* rpn_match_token(const char* token)
|
|||
return &(rpn_ops[rep]);
|
||||
}
|
||||
|
||||
const rpn_op_t* rpn_op_from_opcode(unsigned char opcode)
|
||||
{
|
||||
if(opcode > RPN_OP_SZ)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
return &(rpn_ops[opcode]);
|
||||
}
|
||||
|
||||
int rpn_match_token_i(const char* token)
|
||||
{
|
||||
unsigned char i;
|
||||
|
|
@ -387,6 +400,28 @@ int rpn_match_number(const char* token, unsigned long *result)
|
|||
return 0;
|
||||
}
|
||||
|
||||
int rpn_token_snprintf(rpn_token_t *tok, char *dst, size_t sz)
|
||||
{
|
||||
switch(tok->type)
|
||||
{
|
||||
case RPN_op:
|
||||
if(tok->op->chr)
|
||||
{
|
||||
return snprintf(dst, sz, "%c", tok->op->chr);
|
||||
}
|
||||
return snprintf(dst, sz, "%s", tok->op->str);
|
||||
case RPN_val:
|
||||
return snprintf(dst, sz, "0x%lX", tok->value);
|
||||
case RPN_arg:
|
||||
return snprintf(dst, sz, "A%lu", tok->arg_n);
|
||||
default:
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
size_t rpn_op_sz()
|
||||
{
|
||||
return sizeof(rpn_ops)/sizeof(rpn_op_t);
|
||||
|
|
|
|||
11
rpn_parse.h
11
rpn_parse.h
|
|
@ -221,6 +221,13 @@ char* rpn_tokenized_expr(rpn_tokenized_t *tokens, char long_op);
|
|||
* @ingroup rpn_parse
|
||||
*/
|
||||
const rpn_op_t* rpn_match_token(const char* token);
|
||||
|
||||
/**@brief Returns NULL or pointer on corresponding operation infos
|
||||
* @param unsigned char opcode (index in @ref rpn_ops )
|
||||
* @return NULL or operation informations
|
||||
*/
|
||||
const rpn_op_t* rpn_op_from_opcode(unsigned char opcode);
|
||||
|
||||
/**@brief Return -1 or an index corresponding to @ref rpn_ops
|
||||
* @param token The token we want to match
|
||||
* @return NULL or operation informations
|
||||
|
|
@ -237,10 +244,14 @@ int rpn_match_token_i(const char* token);
|
|||
*/
|
||||
int rpn_match_number(const char* token, unsigned long *result);
|
||||
|
||||
/**@todo doc */
|
||||
int rpn_token_snprintf(rpn_token_t *token, char *dst, size_t sz);
|
||||
|
||||
/**@brief Get operations list size
|
||||
* @return number of operations in @ref rpn_ops
|
||||
*/
|
||||
size_t rpn_op_sz();
|
||||
#define RPN_OPS_SZ (sizeof(rpn_ops)/sizeof(rpn_op_t))
|
||||
|
||||
/**@page rpn_lang RPN expression syntax
|
||||
* @brief Howto write an expression
|
||||
|
|
|
|||
162
tests/tests_rpn_tokens.py
Normal file
162
tests/tests_rpn_tokens.py
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
#!/usr/bin/python3
|
||||
# Copyright 2023 Weber Yann
|
||||
#
|
||||
# This file is part of rpnifs.
|
||||
#
|
||||
# geneifs 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
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# geneifs 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 geneifs. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
import sys
|
||||
import random
|
||||
|
||||
import unittest
|
||||
|
||||
try:
|
||||
import pyrpn
|
||||
except (ImportError, NameError) as e:
|
||||
print("Error importing pyrpn. Try to run make.",
|
||||
file=sys.stderr)
|
||||
raise e
|
||||
|
||||
|
||||
class TestRpnToken(unittest.TestCase):
|
||||
""" Testing tokens submodule """
|
||||
|
||||
def test_token_str_convertion(self):
|
||||
""" Testing token <-> str convvertion """
|
||||
expl = ('+', '-', '&',
|
||||
'A1', 'A0', 'A1337',
|
||||
'0x0', '0x1312')
|
||||
|
||||
for estr in expl:
|
||||
t = pyrpn.tokens.Token.from_str(estr)
|
||||
with self.subTest(str=estr, token=t):
|
||||
self.assertEqual(str(t), estr)
|
||||
|
||||
class TestRpnTokenOperand(unittest.TestCase):
|
||||
""" Testing operand tokens """
|
||||
|
||||
def test_str_init(self):
|
||||
""" Testing operand token initialization """
|
||||
test_str = ('add', '+', '-', 'sub', 'and', 'xor')
|
||||
for op in test_str:
|
||||
t = pyrpn.tokens.Operand(op)
|
||||
with self.subTest(opstr=op, token=t):
|
||||
self.assertIn(str(t), [t.chr(), t.str()])
|
||||
|
||||
def test_int_init(self):
|
||||
""" Testing operand token initialization with all opcodes """
|
||||
for i in range(pyrpn.tokens.Operand.opcode_max()+1):
|
||||
t = pyrpn.tokens.Operand(i)
|
||||
with self.subTest(opcode=i, token=t):
|
||||
self.assertEqual(i, t.opcode)
|
||||
|
||||
def test_badarg_init(self):
|
||||
""" Testing operand token initialization with bad argument """
|
||||
badargs = ('cxwccseuhefsdfiyg', -1, -150,
|
||||
pyrpn.tokens.Operand.opcode_max()+1,
|
||||
pyrpn.tokens.Operand.opcode_max()*2)
|
||||
badtypes = ('0x12', 'A1',
|
||||
dict(), tuple(), b'+')
|
||||
|
||||
for badarg in badargs:
|
||||
with self.subTest(badarg=badarg):
|
||||
with self.assertRaises(ValueError):
|
||||
t = pyrpn.tokens.Operand(badarg)
|
||||
|
||||
for badtype in badtypes:
|
||||
with self.subTest(badtype=badtype):
|
||||
with self.assertRaises(TypeError):
|
||||
t = pyrpn.tokens.Operand(badtype)
|
||||
|
||||
class TestRpnTokenValue(unittest.TestCase):
|
||||
""" Testing value tokens """
|
||||
|
||||
def test_init(self):
|
||||
""" Testing value token initialization """
|
||||
for _ in range(1000):
|
||||
rnd = random.randint(0,(1<<64) -1 )
|
||||
t = pyrpn.tokens.Value(rnd)
|
||||
with self.subTest(token=t, value=rnd):
|
||||
self.assertEqual(int(str(t), 16), rnd)
|
||||
|
||||
def test_badarg_init(self):
|
||||
""" Testing value token initialization with bad argument """
|
||||
badtypes = ('A1', 'add', '+', dict(), tuple(), 5.0)
|
||||
for badtype in badtypes:
|
||||
with self.subTest(badarg=badtype):
|
||||
with self.assertRaises(TypeError):
|
||||
t = pyrpn.tokens.Value(badtype)
|
||||
self.assertEqual(int(str(t), 16), badtype)
|
||||
|
||||
def test_badarg_overflow(self):
|
||||
""" Testing value token initialization overflow """
|
||||
badtypes = (1<<64, -1)
|
||||
for badtype in badtypes:
|
||||
with self.subTest(badarg=badtype):
|
||||
with self.assertRaises(OverflowError):
|
||||
t = pyrpn.tokens.Value(badtype)
|
||||
self.assertEqual(int(str(t), 16), badtype)
|
||||
|
||||
class TestRpnTokenArgument(unittest.TestCase):
|
||||
""" Testing argument tokens """
|
||||
|
||||
def test_init(self):
|
||||
""" Testing argument token initialization """
|
||||
for _ in range(1000):
|
||||
rnd = random.randint(0,(1<<64) -1 )
|
||||
t = pyrpn.tokens.Argument(rnd)
|
||||
with self.subTest(token=t, argument=rnd):
|
||||
self.assertEqual(int(str(t)[1:],10), rnd)
|
||||
|
||||
def test_badarg_init(self):
|
||||
""" Testing argument token initialization with bad argument """
|
||||
badtypes = ('0x1', 'add', '+', dict(), tuple(), 5.0)
|
||||
for badtype in badtypes:
|
||||
with self.subTest(badarg=badtype):
|
||||
with self.assertRaises(TypeError):
|
||||
t = pyrpn.tokens.Argument(badtype)
|
||||
self.assertEqual(int(str(t), 16), badtype)
|
||||
|
||||
def test_badarg_overflow(self):
|
||||
""" Testing argument token initialization overflow """
|
||||
badtypes = (1<<64, -1)
|
||||
for badtype in badtypes:
|
||||
with self.subTest(badarg=badtype):
|
||||
with self.assertRaises(OverflowError):
|
||||
t = pyrpn.tokens.Argument(badtype)
|
||||
self.assertEqual(int(str(t), 16), badtype)
|
||||
|
||||
|
||||
#TODO move in a file dedicated to RPNExpr checks
|
||||
class TestTokenExprRepr(unittest.TestCase):
|
||||
""" Test RPNExpr sequence implementation """
|
||||
|
||||
def test_rpnexpr_sequence(self):
|
||||
""" Testing sequence implementation of RPNExpr """
|
||||
for _ in range(100):
|
||||
argc = random.randint(0,100)
|
||||
token_count = random.randint(0, 200)
|
||||
expr_str = pyrpn.random_expr(argc, token_count)
|
||||
expr = pyrpn.RPNExpr(expr_str, argc)
|
||||
with self.subTest(argc=argc, token_count=token_count,
|
||||
expr=expr, expr_str=expr_str):
|
||||
self.assertEqual(token_count, len(expr))
|
||||
self.assertEqual(' '.join([str(token) for token in expr]),
|
||||
expr_str)
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Loading…
Add table
Add a link
Reference in a new issue