Implements expression's token classes for python

This commit is contained in:
Yann Weber 2023-07-05 12:26:02 +02:00
commit a21802d66b
10 changed files with 751 additions and 5 deletions

View file

@ -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;
}

View file

@ -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

View file

@ -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;

View file

@ -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
View 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
View 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

View file

@ -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;
}

View file

@ -305,6 +305,10 @@ char* rpn_tokenized_expr(rpn_tokenized_t *tokens, char long_op)
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);

View file

@ -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
View 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()