diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..2f19173 --- /dev/null +++ b/Makefile @@ -0,0 +1,9 @@ +all: libs + +libs: + make -C libs + +.PHONY: libs clean + +clean: + make -C libs clean diff --git a/gte.sh b/gte.sh new file mode 100755 index 0000000..fdd7731 --- /dev/null +++ b/gte.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +PYTHONPATH=":`pwd`/libs" /usr/bin/env python3 -m gte "$@" diff --git a/gte/__main__.py b/gte/__main__.py index 1bb9bb6..2f247cb 100644 --- a/gte/__main__.py +++ b/gte/__main__.py @@ -16,18 +16,22 @@ logger = logging.getLogger() logger.debug('Logger started') from . import turmit, rpnlib -from .turmit import Turmit +#from .turmit import Turmit +from cturmit import CTurmit as Turmit from .world import World, LivingTurmit, eval_prog from .mutator import mutate parser = argparse.ArgumentParser(description='Genetic Turmit Evolver') +parser.add_argument('-L', '--list-op', action="store_const", default=False, + const=True, help="List operations and exit") + subparsers = parser.add_subparsers(help='sub-commands help') parser_evolve = subparsers.add_parser('evolve', help='evolving help') parser_evolve.add_argument('--steps', '-s', type=int, metavar='N', - default=30000, help="Number of steps") + default=None, help="Number of steps") parser_evolve.add_argument('--repeat-eval', '-R', type=int, metavar='N', default=5) @@ -109,10 +113,26 @@ parser_gen.add_argument('--no-diagonals', '-d', action='store_const', parser_gen.add_argument('--threads', '-t', type=int, metavar='N', default=os.cpu_count()-1, help="Animation writting threads") +parser_gen.add_argument('--buffer-min', '-b', type=int, metavar='N', + default=10, + help="Start populating animation buffer when this \ +amount of images are in the buffer") +parser_gen.add_argument('--buffer-max', '-B', type=int, metavar='N', + default=100, + help="Start a buffer flush when this amount of images \ +are in the buffer") args = parser.parse_args() -if 'pool_size' in args: +if args.list_op: + from . import rpnlib + print("Operations list & aliases :") + for i, opname in enumerate(rpnlib._op_list): + alias = rpnlib._op_alias[opname] if opname in rpnlib._op_alias else "" + print("\t%02X: %8s\t%s" % (i, opname, alias)) + exit(0) + +elif 'pool_size' in args: # Evolver if args.prog is None: prog = rpnlib.RpnExpr.random(args.prog_size) @@ -184,10 +204,8 @@ if 'pool_size' in args: # Displaying eval results logger.info('Generation evaluating ended in %.2fs' % (genstop - genstart)) - if args.target is None: - scores = sorted(scores, key=lambda x: x[0], reverse=True) - else: - scores = sorted(scores, key=lambda x: abs(x[0] - args.target),) + scores = sorted(scores, key=lambda x: x[0], reverse=True) + for i, (score, prog, sinfo) in enumerate(scores): logger.info('P%d %.3f:(D:%.3f,F:%.3f) : "%s"' % (i, score, sinfo['D'], @@ -224,7 +242,7 @@ if 'pool_size' in args: logger.debug('P%d : %s' % (pid, str(prog))) generation += 1 exit(0) -else: +elif 'output_dir' in args: # Generate if args.output_dir is not None: from PIL import Image @@ -233,10 +251,12 @@ else: im.save(fname) logger.debug('Image %s saved in %.3fs' % (fname, time.time() - st1)) + del(im) write_pool = Pool(args.threads) async_res = [] img_count = 0 + img_total = args.steps if args.anim_div <=1 else args.steps / (args.anim_div - 1) w = World(args.world_height, args.world_width, gray=args.gray) prog = rpnlib.RpnExpr.from_string(args.prog) turmits = [LivingTurmit(world=w, prog=prog, diag=not args.no_diagonals) @@ -246,7 +266,7 @@ else: start = time.time() for step in range(args.steps): if step % 512 == 1: - msg = 'Step %d/%d %dus/step fractdim=%.3f' + msg = 'Step %d/%d avg: %dus/step fractdim=%.3f' msg %= (step, args.steps, ((time.time() - start)*1000000)//step//len(turmits), w.fractdim()) @@ -261,14 +281,23 @@ else: fname = os.path.join(args.output_dir, fname) im = Image.fromarray(np.uint8(w._val)) async_res.append(write_pool.apply_async(save_pool, (im, fname))) - while async_res[0].ready(): - async_res[0].get() - async_res.pop(0) + if len(async_res) > args.buffer_max: + logger.debug('Flushing animation on disk to empty RAM') + while len(async_res) > args.buffer_min: + while async_res[0].ready(): + async_res[0].get() + async_res.pop(0) + msg = '%d/%d images written at avg %.0f img/s ETL : %ds' + img_prog = img_count - args.buffer_min + img_pers = img_prog/((time.time() - start)) + etl = (img_total - img_prog) // img_pers + msg %= (img_prog, img_total, img_pers, etl) + logger.info(msg) if args.output_dir is not None: - while async_res[0].ready(): + while len(async_res) and async_res[0].ready(): async_res[0].get() - sync_res.pop(0) + async_res.pop(0) write_pool.close() logger.info('Waiting for animation to be written on disk') img_st1 = None @@ -318,5 +347,8 @@ else: w.save(args.output) logger.info('Image saved in %s' % args.output) +else: + parser.print_help() + exit(1) exit(0) diff --git a/gte/turmit.py b/gte/turmit.py index a348673..c668f1f 100644 --- a/gte/turmit.py +++ b/gte/turmit.py @@ -42,6 +42,8 @@ class Turmit(object): # @note This limit is handled by @ref rpnlib.RpnOp() self._signed_int = True + self._stack_max = 0xFFF + if 'max_int' in kwargs: v = kwargs['max_int'] if v < 1: @@ -273,7 +275,7 @@ class Turmit(object): ''' stksz = len(self._stack) - new_sz %= 0xFFF + new_sz %= self._stack_max if new_sz < 2: new_sz = 2 if new_sz > stksz: @@ -424,11 +426,11 @@ class Turmit(object): Condition type is encoded on an int : - 0b0000 : je - 0b0001 : jne - - 0b0010 : jle - - 0b0011 : jl + - 0b0010 : jl + - 0b0011 : jle - 0b0100 : jg - 0b0101 : jge - - 0b0110 : jne + - 0b0110 : jmp - 0b0111 : jmp @param a int : operand diff --git a/gte/world.py b/gte/world.py index 1973366..b1474e7 100644 --- a/gte/world.py +++ b/gte/world.py @@ -12,7 +12,8 @@ import time import numpy as np from random import randint -from .turmit import Turmit +#from .turmit import Turmit +from cturmit import CTurmit as Turmit logger = logging.getLogger() @@ -25,7 +26,8 @@ def eval_prog(args): w = World(args.world_height, args.world_width, gray=True) logger.debug('Running P%d run#%d %s' % (progid, trynum, prog)) w.raz() - turmits = [LivingTurmit(world=w, prog=prog, diag=not args.no_diagonals) + base = LivingTurmit(world=w, prog=prog, diag=not args.no_diagonals) + turmits = [ LivingTurmit(world=w, turmit=base) for _ in range(args.turmit_count)] start = time.time() @@ -33,24 +35,49 @@ def eval_prog(args): steps = args.steps else: steps = args.steps - int((args.steps / 1.5) // (args.repeat_eval - trynum)) - for step in range(steps): - for turmit in turmits: - turmit() + + if not steps: # 0 or None + pfdim = 0 + dfdim = 1 + steps = 0 + lim_reach = 0 + retry_lim = 0 + while lim_reach < 2 and steps < 0x40000: + for _ in range(3000): + for turmit in turmits: + turmit() + steps += 0x1000 + cfdim = w.fractdim() # delta fdim on not targeted dim + dfdim = cfdim - pfdim + pfdim = cfdim + if abs(dfdim) < 0.001: + lim_reach += 1 + else: + retry_lim += 1 + if retry_lim < 3: + lim_reach = 0 + else: + for step in range(steps): + for turmit in turmits: + turmit() stop = time.time() score_dir = sum([t.dirvar() for t in turmits]) / len(turmits) score_deter = sum([t.determinist() for t in turmits]) / len(turmits) - score_fract = w.fractdim() + score_fract, raw_fract = w.fractdim_target(args.target) score = (score_dir * score_fract) * (score_deter * 0.2 + 0.8) sinfo = {'F':score_fract, 'D':score_dir} #score = sum([score_fract] + [score_dir] * 4) / 5 if not args.quiet: tall = time.time() - startall - msg = 'G%d P%d R%d: %d steps scores %.3f:(D:%.3f,F:%.3f,Dtr:%.3f) in %.3fs (%dus per step)\t%s' + msg = 'G%d P%d R%d: %d steps scores %.3f:(D:%.3f,F:%.3f,' msg %= (generation, progid, trynum, steps, score, score_dir, - score_fract, score_deter, tall, - ((stop - start)*1000000)//steps//len(turmits), - str(prog)) + score_fract) + if args.target: + msg += 'RawF:%.3f,' % raw_fract + msg += 'Dtr:%.3f) in %.3fs (%dus per step)\t%s' + msg %= (score_deter, tall, + ((stop - start)*1000000)//steps//len(turmits), str(prog)) logger.info(msg) return progid, score, sinfo @@ -98,6 +125,16 @@ class World(object): ''' @brief Save as an image ''' scipy.misc.imsave(filename, self._val) + def fractdim_target(self, target, threshold=None): + res = self.fractdim(threshold) + if target: + ret = (2.0 - (abs(target - res))*5) + if ret < 0: + ret = 0.0 + return (ret, res) + else: + return (res, res) + def fractdim(self, threshold=None): ''' @return Minkowski–Bouligand dimension (computed) ''' if not self._gray: @@ -169,6 +206,8 @@ class LivingTurmit(Turmit): ''' @brief Represent a Turmit with coordinates in a world ''' def __init__(self, world, color = None, x=None, y=None, diag=True, **kwargs): + if 'prog' in kwargs: + kwargs['prog'] = str(kwargs['prog']).lower() super().__init__(**kwargs) if not isinstance(world, World): @@ -228,6 +267,7 @@ class LivingTurmit(Turmit): r = g = b = self._world[self._y][self._x] else: r,g,b = self._world[self._y][self._x] + r = int(r); g=int(g); b=int(b) tdir = super().__call__(x=self._x, y=self._y, r=r, g=g, b=b) self.move(tdir) @@ -253,7 +293,10 @@ class LivingTurmit(Turmit): ''' @brief Process a determinit score : determinist_expr / total expr @return float in [0.0..1.0] ''' - return (self._determin_score + 1) / self._steps + try: + return (self._determin_score + 1) / self._steps + except AttributeError: + return 1 def dirvar(self): ''' @brief Process @ref _dirvar to return a direction variation diff --git a/libs/Makefile b/libs/Makefile new file mode 100644 index 0000000..7379c89 --- /dev/null +++ b/libs/Makefile @@ -0,0 +1,17 @@ +PYTHON=/usr/bin/python3 +#CFLAGS=-Wall -Werror -O0 -g +CFLAGS=-Wall -Werror +CC=gcc $(CFLAGS) + +all: cturmit*.so + +cturmit*.so: setup.py build/lib.*/cturmit*.so + cp build/lib.*/cturmit*.so ./ + +build/lib.*/cturmit*.so: cturmit.c cturmit.h turmit.c turmit.h + make clean; CFLAGS="$(CFLAGS)" $(PYTHON) setup.py --verbose build + +.PHONY: clean + +clean: + -rm -Rf build cturmit*.so diff --git a/libs/cturmit.c b/libs/cturmit.c new file mode 100644 index 0000000..0c36a0e --- /dev/null +++ b/libs/cturmit.c @@ -0,0 +1,352 @@ +#include "cturmit.h" + +PyMODINIT_FUNC +PyInit_cturmit(void) +{ + PyObject* m; + + CTurmitType.tp_new = PyType_GenericNew; + if (PyType_Ready(&CTurmitType) < 0) + return NULL; + + m = PyModule_Create(&cturmitmodule); + if (m == NULL) + return NULL; + + Py_INCREF(&CTurmitType); + PyModule_AddObject(m, "CTurmit", (PyObject *)&CTurmitType); + return m; +} + +static int +CTurmit_init(CTurmit *self, PyObject *args, PyObject *kwds) +{ + size_t sz; + char err_msg[128]; + ssize_t arg_stk_sz, arg_int_max; + char *arg_expr; + int i, ret; + CTurmit *turmit; + PyObject *stack_size, *int_max, *expr, *keys, *asc_str; + PyObject *errtype, *errvalue, *errbck; + + PyErr_Fetch(&errtype, &errvalue, &errbck); + turmit = (CTurmit*)PyMapping_GetItemString(kwds, "turmit"); + if(turmit) + { + Py_XDECREF(errtype); + Py_XDECREF(errvalue); + Py_XDECREF(errbck); + ret = _CTurmit_copy_init(self, turmit); + Py_DECREF(turmit); + return ret; + } + PyErr_Restore(errtype, errvalue, errbck); + + stack_size = int_max = expr = NULL; + self->turmit = NULL; + + //Checking arguments + if((sz = PySequence_Length(args)) >= 0) + { + if(sz > 1) + { + PyErr_Restore(errtype, errvalue, errbck); + snprintf(err_msg, 128, "1 positional argument expected \ +but %ld found", sz); + PyErr_SetString(PyExc_TypeError, + err_msg); + goto cturmit_init_error; + } + stack_size = PySequence_GetItem(args, 0); + } + if(!stack_size) + { + stack_size = PyMapping_GetItemString(kwds, "stack_size"); + PyMapping_DelItemString(kwds, "stack_size"); + } + if(stack_size) + { + if(!PyLong_CheckExact(stack_size)) + { + PyErr_Restore(errtype, errvalue, errbck); + Py_DECREF(stack_size); + snprintf(err_msg, 128, + "stack_size expected to be an integer"); + PyErr_SetString(PyExc_TypeError, + err_msg); + goto cturmit_init_error; + } + arg_stk_sz = PyLong_AsSsize_t(stack_size); + Py_DECREF(stack_size); + if(arg_stk_sz == (size_t)-1 && PyErr_Occurred()) + { + PyErr_Restore(errtype, errvalue, errbck); + snprintf(err_msg, 128, + "size_t overflow with stack_size"); + PyErr_SetString(PyExc_ValueError, + err_msg); + goto cturmit_init_error; + } + } + else + { + arg_stk_sz = 8; + } + + int_max = PyMapping_GetItemString(kwds, "max_int"); + PyMapping_DelItemString(kwds, "max_int"); + if(int_max) + { + if(!PyLong_CheckExact(int_max)) + { + PyErr_Restore(errtype, errvalue, errbck); + snprintf(err_msg, 128, + "int_max expected to be an integer"); + PyErr_SetString(PyExc_TypeError, + err_msg); + goto cturmit_init_error; + } + arg_int_max = PyLong_AsSsize_t(int_max); + Py_DECREF(int_max); + if(arg_int_max == (size_t)-1 && PyErr_Occurred()) + { + PyErr_Restore(errtype, errvalue, errbck); + PyErr_SetString(PyExc_TypeError, + "size_t overflow with stack_size"); + PyErr_SetString(PyExc_ValueError, + err_msg); + goto cturmit_init_error; + } + } + else + { + arg_int_max = 0x10000; + } + + expr = PyMapping_GetItemString(kwds, "prog"); + PyMapping_DelItemString(kwds, "prog"); + if(expr) + { + if(!PyUnicode_Check(expr) && !(expr = PyObject_Str(expr))) + { + PyErr_Restore(errtype, errvalue, errbck); + snprintf(err_msg, 128, + "prog expected to be a string !"); + PyErr_SetString(PyExc_TypeError, + err_msg); + goto cturmit_init_error; + } + asc_str = PyUnicode_AsASCIIString(expr); + Py_DECREF(expr); + if(!asc_str) + { + PyErr_Restore(errtype, errvalue, errbck); + snprintf(err_msg, 128, + "prog expected to be an ASCII string"); + PyErr_SetString(PyExc_ValueError, + err_msg); + goto cturmit_init_error; + } + arg_expr = (char*)PyBytes_AsString(asc_str); + Py_DECREF(asc_str); + if(!arg_expr) + { + PyErr_Restore(errtype, errvalue, errbck); + snprintf(err_msg, 128, + "Unable to convert prog to Cstring"); + PyErr_SetString(PyExc_RuntimeError, + err_msg); + goto cturmit_init_error; + } + } + else + { + arg_expr = ""; + } + + if(PyMapping_Length(kwds) > 0) + { + keys = PyMapping_Keys(kwds); + strncpy(err_msg, "Unrecognized arguments : '", 128); + for(i=0; iturmit = turmit_alloc(arg_stk_sz, arg_int_max, arg_expr, + TURMIT_AUTOCOMP); + if(self->turmit->err) + { + PyErr_Restore(errtype, errvalue, errbck); + snprintf(err_msg, 128, + "Unable to compile '%s' err : %s", arg_expr, + self->turmit->err_str); + PyErr_SetString(PyExc_ValueError, + err_msg); + goto cturmit_init_error; + } + Py_XDECREF(errtype); + Py_XDECREF(errvalue); + Py_XDECREF(errbck); + return 0; + + cturmit_init_error: + Py_XDECREF(errtype); + Py_XDECREF(errvalue); + Py_XDECREF(errbck); + return -1; + +} + +static PyObject * +CTurmit_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + CTurmit *self; + self = (CTurmit *)type->tp_alloc(type, 0); + if(self != NULL) { + //do stuff + } + return (PyObject *)self; +} + +void +CTurmit_dealloc(CTurmit *self) +{ + if(self->turmit) { turmit_free(self->turmit); } + Py_TYPE(self)->tp_free((PyObject*)self); +} + +static int _CTurmit_copy_init(CTurmit *self, CTurmit *t) +{ + self->turmit = turmit_copy(t->turmit); + if(!self->turmit) + { + //set error + return -1; + } + return 0; +} + +PyObject* CTurmit_call(PyObject *func, PyObject *args, PyObject *kwds) +{ + turmit_int res; + PyObject *arg; + CTurmit *self; + turmit_int exec_args[5]; + char *argsname[5] = {"x", "y", "r", "g", "b"}; // see TURMIT_VAR_L + char err_msg[64]; + int i; + PyObject *errtype, *errvalue, *errbck; + + self = (CTurmit*)func; + + PyErr_Fetch(&errtype, &errvalue, &errbck); + + for(i=0; i<5; i++) + { + arg = PyMapping_GetItemString(kwds, argsname[i]); + if(PyErr_Occurred()) + { + PyErr_Restore(errtype, errvalue, errbck); + snprintf(err_msg, 64, "Argument %s missing", + argsname[i]); + PyErr_SetString(PyExc_TypeError, + err_msg); + Py_RETURN_NONE; + } + exec_args[i] = PyLong_AsUnsignedLongLong(arg); + if(PyErr_Occurred()) + { + PyErr_Restore(errtype, errvalue, errbck); + snprintf(err_msg, 64, "An integer is required for %s", + argsname[i]); + PyErr_SetString(PyExc_TypeError, + err_msg); + Py_RETURN_NONE; + } + Py_DECREF(arg); + } + res = turmit_exec(self->turmit, exec_args); + return PyLong_FromUnsignedLongLong(res); +} + +PyObject* CTurmit__push(CTurmit* self, PyObject *args) +{ + char err_msg[128]; + size_t sz; + turmit_int ival; + PyObject *val; + PyObject *errtype, *errvalue, *errbck; + + PyErr_Fetch(&errtype, &errvalue, &errbck); + + if((sz = PySequence_Length(args)) >= 0) + { + if(sz > 1) + { + PyErr_Restore(errtype, errvalue, errbck); + snprintf(err_msg, 128, "1 positional argument expected \ +but %ld found", sz); + PyErr_SetString(PyExc_TypeError, err_msg); + Py_RETURN_NONE; + } + val = PySequence_GetItem(args, 0); + } + if(!val) + { + PyErr_Restore(errtype, errvalue, errbck); + PyErr_SetString(PyExc_TypeError, + "Argument val is missing"); + Py_RETURN_NONE; + + } + ival = PyLong_AsSize_t(val); + if(ival == (size_t)-1 && PyErr_Occurred()) { Py_RETURN_NONE; } + SPUSH(self->turmit, ival); + PyErr_Restore(errtype, errvalue, errbck); + Py_RETURN_NONE; +} + +PyObject* CTurmit__pop(CTurmit* self) +{ + return PyLong_FromUnsignedLongLong(SPOP(self->turmit)); +} + +PyObject* CTurmit_get_shead(CTurmit *self, void *ref) +{ + return PyLong_FromUnsignedLongLong(SCUR(self->turmit)); +} + +PyObject* CTurmit_get__cur(CTurmit *self, void *ref) +{ + return PyLong_FromSsize_t(self->turmit->stack_cur); +} + +PyObject* CTurmit_get__stack(CTurmit *self, void *ref) +{ + size_t i; + PyObject *res; + + res = PyList_New(self->turmit->stack_sz); + + for(i=0; iturmit->stack_sz;i++) + { + PyList_SET_ITEM(res, i, + PyLong_FromUnsignedLongLong(self->turmit->stack[i])); + } + return res; +} + diff --git a/libs/cturmit.h b/libs/cturmit.h new file mode 100644 index 0000000..03c0270 --- /dev/null +++ b/libs/cturmit.h @@ -0,0 +1,205 @@ +#include +#include"structmember.h" + +#include "turmit.h" + +/** @brief Python module containing CTurmit python class */ + +static PyModuleDef cturmitmodule = { + PyModuleDef_HEAD_INIT, + "cturmit", + "Module implementing turmits in C", + -1, + NULL, NULL, NULL, NULL, NULL +}; /*turmit);\ + Py_RETURN_NONE;\ +} +#define TURMIT_PYOPDEF(NAME) {#NAME, (PyCFunction)CTurmit_op_##NAME, \ + METH_NOARGS, NULL} + +/** @brief Push a python integer on the CTurmit stack + * @param self the CTurmit instance + * @param PyObject* list of *args arguments. Should contain a single integer + * argument to be pushed on the stack + * @return Python None + * @throws TypeError if bad arguments + */ +PyObject* CTurmit__push(CTurmit* self, PyObject *args); +/**@brief Pop a python integer from the CTurmit stack + * @return a python integer returned from PyLong_FromUnsignedLongLong() + */ +PyObject* CTurmit__pop(CTurmit* self); +/* Adding method to call turmit_t operations on the stack + * @param self CTurmit* (see TURMIT_PYOP macro) the method takes no args + * except self + */ +TURMIT_PYOP(mem_sz) +TURMIT_PYOP(add) +TURMIT_PYOP(sub) +TURMIT_PYOP(bin_and) +TURMIT_PYOP(dup) +TURMIT_PYOP(lshift) +TURMIT_PYOP(mod) +TURMIT_PYOP(mul) +TURMIT_PYOP(div) +TURMIT_PYOP(bin_or) +TURMIT_PYOP(bin_xor) +TURMIT_PYOP(pop) +TURMIT_PYOP(swp) +TURMIT_PYOP(jmp) +TURMIT_PYOP(jz) +TURMIT_PYOP(jcmp) + +static PyMethodDef CTurmit_methods[] = { + {"_push", (PyCFunction)CTurmit__push, METH_KEYWORDS | METH_VARARGS, + "Push a value on the stack"}, + {"_pop", (PyCFunction)CTurmit__pop, METH_NOARGS, + "Pop a value from the stack"}, + TURMIT_PYOPDEF(mem_sz), + TURMIT_PYOPDEF(add), + TURMIT_PYOPDEF(sub), + TURMIT_PYOPDEF(bin_and), + TURMIT_PYOPDEF(dup), + TURMIT_PYOPDEF(lshift), + TURMIT_PYOPDEF(mod), + TURMIT_PYOPDEF(mul), + TURMIT_PYOPDEF(div), + TURMIT_PYOPDEF(bin_or), + TURMIT_PYOPDEF(bin_xor), + TURMIT_PYOPDEF(pop), + TURMIT_PYOPDEF(swp), + TURMIT_PYOPDEF(jmp), + TURMIT_PYOPDEF(jz), + TURMIT_PYOPDEF(jcmp), + {NULL} /* Sentinel */ +}; /*stack_sz; + old_blc = (old_sz / TURMIT_STACK_GROW) + 1; + + new_sz = SPOP(turmit); + new_sz %= TURMIT_STACK_MAX; + new_sz = new_sz<2?2:new_sz; + new_blc = (new_sz / TURMIT_STACK_GROW) + 1; + + if(new_blc != old_blc) + { + new = realloc(turmit->stack, + sizeof(turmit_int) * (new_blc * TURMIT_STACK_GROW)); + if(!new) { + perror("Unable to reallocate memory for stack"); + return; + } + turmit->stack = new; + } + turmit->stack_sz = new_sz; + turmit->stack_cur %= new_sz; + if(old_sz < new_sz) + { + bzero(turmit->stack + old_sz, + sizeof(turmit_int) * (new_sz - old_sz)); + } +} + +TURMIT_OP(add) +{ + turmit_int a = SPOP(turmit), b = SPOP(turmit); + SPUSH(turmit, a+b); +} + +TURMIT_OP(sub) +{ + turmit_int a = SPOP(turmit), b = SPOP(turmit); + if(turmit->flags & TURMIT_SIGNED) + { + fprintf(stderr, "Signed int not implemented\n"); + } + else + { + SPUSH(turmit, a>b?(turmit->int_max - a) + b:b - a); + } +} + +TURMIT_OP(bin_and) +{ + turmit_int a = SPOP(turmit), b = SPOP(turmit); + SPUSH(turmit, b & a); +} + +TURMIT_OP(dup) +{ + turmit_int a = SCUR(turmit); + SPUSH(turmit, a); +} + +TURMIT_OP(lshift) +{ + turmit_int a,b; + a = SPOP(turmit); b = SPOP(turmit); + SPUSH(turmit, b << a); +} + +TURMIT_OP(mod) +{ + turmit_int a,b; + a = SPOP(turmit); b = SPOP(turmit); + a = !a?0:b%a; + SPUSH(turmit, a); +} + +TURMIT_OP(mul) +{ + turmit_int a = SPOP(turmit), b = SPOP(turmit); + SPUSH(turmit, a*b); +} + +TURMIT_OP(div) +{ + turmit_int a, b; + a = SPOP(turmit); + b = SPOP(turmit); + SPUSH(turmit, a==0?0:b/a); +} + +TURMIT_OP(bin_or) +{ + turmit_int a = SPOP(turmit), b = SPOP(turmit); + SPUSH(turmit, a | b); +} + +TURMIT_OP(bin_xor) +{ + turmit_int a = SPOP(turmit), b = SPOP(turmit); + SPUSH(turmit, a ^ b); +} + +TURMIT_OP(pop) +{ + SPOP(turmit); +} + +TURMIT_OP(swp) +{ + turmit_int a, b; + a = SPOP(turmit); + b = SPOP(turmit); + SPUSH(turmit, a); + SPUSH(turmit, b); +} + +TURMIT_OP(jmp) +{ + turmit_int a; + a = SPOP(turmit); + TURMIT_JMP(turmit, a); +} + +TURMIT_OP(jz) +{ + turmit_int a, b; + a = SPOP(turmit); + b = SPOP(turmit); + if(b == 0) + { + TURMIT_JMP(turmit, a); + } +} + +TURMIT_OP(jcmp) +{ + turmit_int a, b, cnd, offset; + offset = SPOP(turmit); + cnd = SPOP(turmit); + b = SPOP(turmit); + a = SPOP(turmit); + if(((cnd & 7) == 0 && a != b) || (cnd > 6) || ((cnd & 1) && a == b) || + ((cnd & 7) > 2 && a < b) || ((cnd & 7) > 4 && a > b)) + { + TURMIT_JMP(turmit, offset); + } +} + +turmit_t *turmit_alloc(ssize_t stack_sz, ssize_t int_max, const char *expr, + unsigned char flags) +{ + turmit_t *res; + + res = malloc(sizeof(struct turmit_s)); + if(!res) + { + perror("Unable to allocate memory for turmit"); + return NULL; + } + return turmit_init(res, stack_sz, int_max, expr, flags); +} + +turmit_t *turmit_init(turmit_t *turmit, ssize_t stack_sz, ssize_t int_max, + const char *expr, unsigned char flags) +{ + turmit->expr = strndup(expr, TURMIT_EXPR_MAX_SZ); + turmit->flags = flags; + turmit->int_max = int_max; + turmit->op_expr = NULL; + turmit->op_expr_sz = 0; + turmit->op_cur = 0; + turmit->op_end = 0; + turmit->stack_sz = stack_sz; + turmit->stack_cur = turmit->stack_sz - 1; + turmit->err = 0; + turmit->err_str = NULL; + turmit->stack = malloc(sizeof(turmit_int) * + ((turmit->stack_sz / TURMIT_STACK_GROW)+1) * TURMIT_STACK_GROW); + + if(!turmit->stack) + { + perror("Unable to allocate turmit's stack's memory"); + return turmit; + } + bzero(turmit->stack, sizeof(turmit_int) * turmit->stack_sz); + + if(turmit->flags & TURMIT_AUTOCOMP) + { + turmit_compile(turmit); + } + return turmit; +} + +turmit_t *turmit_copy(turmit_t *turmit) +{ + turmit_t *res; + turmit_int stack_blck; + + res = malloc(sizeof(turmit_t)); + if(!res) + { + perror("Unable to allocate memory for copy"); + return NULL; + } + memcpy(res, turmit, sizeof(turmit_t)); + if(turmit->expr) + { + res->expr = strdup(turmit->expr); + } + if(turmit->stack) + { + stack_blck = (res->stack_sz / TURMIT_STACK_GROW) + 1; + res->stack = malloc( + sizeof(turmit_int) * stack_blck * TURMIT_STACK_GROW); + memcpy(res->stack, turmit->stack, + sizeof(turmit_int) * stack_blck * TURMIT_STACK_GROW); + } + if(turmit->op_expr) + { + res->op_expr = malloc(sizeof(turmit_op_t) * res->op_expr_sz); + if(!res->op_expr) + { + perror("Unable to allocate memory for expression"); + return res; + } + memcpy(res->op_expr, turmit->op_expr, + sizeof(turmit_op_t) * res->op_expr_sz); + } + return res; +} + +void turmit_clean(turmit_t *turmit) +{ + if(turmit->stack) { free(turmit->stack); } + if(turmit->err_str) { free(turmit->err_str); } + if(turmit->expr) { free(turmit->expr); } + if(turmit->op_expr) { free(turmit->op_expr); } + +} + +void turmit_free(turmit_t *turmit) +{ + turmit_clean(turmit); + free(turmit); +} + +turmit_int turmit_exec(turmit_t *turmit, const turmit_int args[5]) +{ + turmit_op_t *op; + + turmit->op_cur = 0; + turmit->op_end = 0; + while(!turmit->op_end && turmit->op_cur < turmit->op_expr_sz) + { + op = &(turmit->op_expr[turmit->op_cur]); + switch(op->value) + { + case TURMIT_SYM_OP: + op->op.op(turmit); + break; + case TURMIT_SYM_VAL: + SPUSH(turmit, op->op.val); + break; + case TURMIT_SYM_VAR: + SPUSH(turmit, args[op->op.var]); + break; + default: + // Null sym encountered. This sould NOT append + fprintf(stderr, "Warning NULL sym encountered\n"); + turmit->op_cur = turmit->op_expr_sz; + break; + } +//_turmit_stack_dump(turmit); + turmit->op_cur++; + } + return SCUR(turmit); +} + +int turmit_compile(turmit_t *turmit) +{ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmissing-braces" + turmit_sym_t tsym[] = TURMIT_OP_LST; +#pragma GCC diagnostic pop + char *cur, *endptr; + turmit_sym_t *isym; + ssize_t opcur; + turmit_op_t *pret; + long int iret; + int err; + int i; + + turmit->op_expr_sz = 64; + turmit->op_expr = malloc(sizeof(turmit_op_t) * turmit->op_expr_sz); + bzero(turmit->op_expr, sizeof(turmit_op_t) * turmit->op_expr_sz); + opcur = 0; + + cur = turmit->expr; + while(*cur != '\0') + { + if(opcur >= turmit->op_expr_sz - 1) + { //more space needed + turmit->op_expr_sz += 64; + pret = realloc(turmit->op_expr, + sizeof(turmit_op_t) * turmit->op_expr_sz); + if(!pret) + { + perror("Unable to allocate memory"); + goto turmit_compile_err; + } + bzero(pret + opcur - 1, + 64 * sizeof(turmit_op_t)); + turmit->op_expr = pret; + } + //Checking for separators + if(*cur == ' ' || *cur == '\t' || *cur == ' ') + { + cur++; + continue; + } + + //Checking for variables + for(i=0; TURMIT_VAR_L[i] != '\0'; i++) + { + if(tolower(*cur) == TURMIT_VAR_L[i]) + { + turmit->op_expr[opcur].op.var = i; + turmit->op_expr[opcur].value = TURMIT_SYM_VAR; + opcur++; + cur++; + break; + } + } + if(TURMIT_VAR_L[i] != '\0') { continue; } + + //Checking for values + //hex + if(strncasecmp("0x", cur, 2) == 0) + { + cur+= 2; + errno = 0; + iret = strtol(cur, &endptr, 16); + err = errno; + if(err || endptr == cur) + { + fprintf(stderr, "Invalid constant %s : %s\n", + cur-2, strerror(err)); + goto turmit_compile_err; + } + turmit->op_expr[opcur].op.val = iret; + turmit->op_expr[opcur].value = TURMIT_SYM_VAL; + opcur++; + cur = endptr; + continue; + } + //decimal + if(*cur <= '9' && *cur >= '0') + { + iret = strtol(cur, &endptr, 10); + err = errno; + if(err || endptr == cur) + { + fprintf(stderr, "Invalid constant %s : %s\n", + cur, strerror(err)); + goto turmit_compile_err; + } + turmit->op_expr[opcur].op.val = iret; + turmit->op_expr[opcur].value = TURMIT_SYM_VAL; + opcur++; + cur = endptr; + continue; + } + + //Checking for op + isym = tsym; + while(isym->str != NULL) + { + endptr = cur; + if(strncasecmp(isym->str, cur, strlen(isym->str)) == 0) + { + endptr = cur+strlen(isym->str); + } + else if(isym->alias && strncmp(cur, isym->alias, + strlen(isym->alias)) == 0) + { + endptr = cur+strlen(isym->alias); + } + if(cur != endptr) + { + cur = endptr; + turmit->op_expr[opcur].op.op = isym->op_fun; + turmit->op_expr[opcur].value = TURMIT_SYM_OP; + opcur++; + break; + } + isym++; + } + if(isym->str != NULL) + { + continue; + } + //unrecognized symbol :'( + turmit->err = 1; + if(turmit->err_str) { free(turmit->err_str); } + turmit->err_str = malloc(sizeof(char) * 256); + if(!turmit->err_str) + { + perror("Unable to allocate memory for error string"); + return -1; + } + snprintf(turmit->err_str, 256, + "Error compiling prog starting at : '%s'", cur); + goto turmit_compile_err; + } + if(!opcur) + { //empty expression + turmit->op_expr_sz = 0; + free(turmit->op_expr); + turmit->op_expr = NULL; + return 0; + } + turmit->op_expr_sz = opcur; + pret = realloc(turmit->op_expr, + sizeof(turmit_op_t) * turmit->op_expr_sz); + if(!pret) + { + perror("Unable to shrink op_expr"); + return 1; + } + else + { + turmit->op_expr = pret; + } + return 0; + + turmit_compile_err: + free(turmit->op_expr); + turmit->op_expr = NULL; + turmit->op_expr_sz = 0; + turmit->op_cur = 0; + return 1; +} + +void _turmit_stack_dump(turmit_t *turmit) +{ + int i; + fprintf(stderr, "Stack cur = %lu\n[", turmit->stack_cur); + for(i=0; istack_sz; i++) + { + fprintf(stderr,"%llu, ", turmit->stack[i]); + } + fprintf(stderr, "]\n"); +} + diff --git a/libs/turmit.h b/libs/turmit.h new file mode 100644 index 0000000..662fa85 --- /dev/null +++ b/libs/turmit.h @@ -0,0 +1,196 @@ +#ifndef _TURMIT_H_ +#define _TURMIT_H_ + +#include +#include +#include +#include +#include + +/**@Todo For the moment there is no way to track wether a variable is used + * or not in result processing */ + +typedef unsigned long long int turmit_int; +typedef struct turmit_op_s turmit_op_t; +typedef struct turmit_s turmit_t; +typedef struct turmit_sym_s turmit_sym_t; +typedef void (*turmit_op_f)(turmit_t*); + +#define SCUR(t) (t->stack[t->stack_cur]) +#define SDEC(t) (t->stack_cur = (t->stack_cur>0?t->stack_cur:t->stack_sz) -1) +#define SINC(t) (t->stack_cur = (t->stack_cur + 1) % t->stack_sz) +#define SPOP(t) (__SPOP(t)) +#define SPUSH(t, v) (t->stack[SINC(t)] = v % t->int_max) + +#define TURMIT_JMP(t, v) (((t->op_expr_sz - t->op_cur) <= v)?\ + (t->op_end=1):\ + (t->op_cur += (v>0?v-1:0) )) + +#define TURMIT_STACK_GROW 0xFF +#define TURMIT_STACK_MAX 0xFFF +#define TURMIT_EXPR_MAX_SZ 0xFFFFFFFF + +#define TURMIT_LAZY 0 +#define TURMIT_AUTOCOMP 1 +#define TURMIT_UNSIGNED 0 +#define TURMIT_SIGNED 2 + +#define TURMIT_OK 0 +#define TURMIT_ERROR -1 + +#define TURMIT_SYM_OP 1 +#define TURMIT_SYM_VAL 2 +#define TURMIT_SYM_VAR 3 + +#define TURMIT_VAR_L "xyrgb" + +#define TURMIT_OPSYM(NAME) {#NAME, NULL, turmit_op_##NAME} +#define TURMIT_OPSYMALIAS(NAME, alias) {#NAME, alias, turmit_op_##NAME} + +// OPSYMALIAS has to be first ! +#define TURMIT_OP_LST {\ + TURMIT_OPSYMALIAS(add, "+"),\ + TURMIT_OPSYMALIAS(sub, "-"),\ + TURMIT_OPSYMALIAS(mul, "*"),\ + TURMIT_OPSYMALIAS(div, "/"),\ + TURMIT_OPSYMALIAS(mod, "%"),\ + TURMIT_OPSYMALIAS(bin_or, "|"),\ + TURMIT_OPSYMALIAS(bin_and, "&"),\ + TURMIT_OPSYMALIAS(bin_xor, "^"),\ + TURMIT_OPSYMALIAS(lshift, "<<"),\ + TURMIT_OPSYM(mem_sz),\ + TURMIT_OPSYM(dup),\ + TURMIT_OPSYM(pop),\ + TURMIT_OPSYM(swp),\ + TURMIT_OPSYM(jmp),\ + TURMIT_OPSYM(jz),\ + TURMIT_OPSYM(jcmp),\ + NULL,\ +} + +struct turmit_sym_s +{ + char *str; + char *alias; + turmit_op_f op_fun; +}; + +union turmit_op_u +{ + turmit_op_f op; + turmit_int val; + short var; /*!< interpreted % 5 */ +}; + +struct turmit_op_s +{ + union turmit_op_u op; + /** 0 for NULL, 1 if operation pointer, 2 if value, 3 if variable */ + short value; +}; + +struct turmit_s +{ + char *expr; + unsigned char flags; + ssize_t int_max; + + turmit_op_t *op_expr; + ssize_t op_expr_sz; + ssize_t op_cur; + short op_end; + + ssize_t stack_sz; + turmit_int *stack; + ssize_t stack_cur; + + /**@brief 0 if no error + * @note only for compilation error for the moment + */ + char err; + char *err_str; +}; + +inline static turmit_int __SPOP(turmit_t *t) { + turmit_int r = t->stack[t->stack_cur]; + SDEC(t); + return r; +} + + +#define TURMIT_OP(NAME) void turmit_op_##NAME (turmit_t *turmit) + +/**@brief Allocate memory and initialise a turmit + * @param const char* The turmit expression + * @param unsigned char One of TURMIT_LAZY or TURMIT_AUTOCOMP. + * @return A pointer on the new turmit + */ +turmit_t *turmit_alloc(ssize_t stack_sz, ssize_t int_max, const char *expr, + unsigned char flags); + +/**@brief Initialize a turmit + * @param turmit_t* The turmit to initialize + * @param const char* The turmit expression + * @param unsigned char One of TURMIT_LAZY or TURMIT_AUTOCOMP. + * @return The pointer on the turmit + */ +turmit_t *turmit_init(turmit_t *turmit, ssize_t stack_sz, ssize_t int_max, + const char *expr, unsigned char flags); + +turmit_t *turmit_copy(turmit_t *turmit); + +void turmit_clean(turmit_t *turmit); + +/**@brief Free memory of a turmit + * @param turmit the turmit to free + */ +void turmit_free(turmit_t *turmit); + +/**@brief Exec a turmit expr + * @param turmit_t* Concerned turmit + * @param turmit_int* Arguments ( see @ref TURMIT_VAL_L ) + * @return Processed value + */ +turmit_int turmit_exec(turmit_t *turmit, const turmit_int args[5]); + +/**@brief Populate turmit_t.op_expr and set op_expr_sz + * @return 0 if no errors + */ +int turmit_compile(turmit_t *turmit); + +void _turmit_stack_dump(turmit_t *turmit); + +TURMIT_OP(mem_sz); + +TURMIT_OP(add); + +TURMIT_OP(sub); + +TURMIT_OP(bin_and); + +TURMIT_OP(dup); + +TURMIT_OP(lshift); + +TURMIT_OP(mod); + +TURMIT_OP(mul); + +TURMIT_OP(div); + +TURMIT_OP(bin_or); + +TURMIT_OP(bin_xor); + +TURMIT_OP(pop); + +TURMIT_OP(swp); + +TURMIT_OP(jmp); + +TURMIT_OP(jz); + +TURMIT_OP(jcmp); + +#endif + diff --git a/runtest.sh b/runtest.sh index 7cbe05e..b2e1c48 100755 --- a/runtest.sh +++ b/runtest.sh @@ -1,3 +1,3 @@ #!/bin/sh -python3 -m unittest $@ +PYTHONPATH=":`pwd`/libs" python3 -m unittest $@ diff --git a/tests/test_cturmit.py b/tests/test_cturmit.py new file mode 100644 index 0000000..e97340c --- /dev/null +++ b/tests/test_cturmit.py @@ -0,0 +1,319 @@ +import unittest +import inspect + +from cturmit import CTurmit as Turmit +from gte.rpnlib import _op_list + +class TurmitTestCase(unittest.TestCase): + + def test_init(self): + ''' Test Turmit class __init__ ''' + t = Turmit(42) + self.assertEqual(len(t._stack), 42) + self.assertEqual(t.shead, 0) + self.assertEqual(len(t._stack) - 1 , t._cur) + + def test_init_args(self): + ''' Test Turmit __init__ arguments ''' + t = Turmit() + t = Turmit(prog = 'G R B 44 MOD ADD 43 MOD ADD 42 MOD') + t = Turmit(prog = 'G R B ADD ADD 42 MOD', max_int = 0xFFFF, + stack_size=42) + + def test_call(self): + ''' Test Turmit.__call__ ''' + t = Turmit(prog = 'G R B ADD ADD 42 MOD', max_int = 0xFFFF, + stack_size=42) + r = t(x=1, y=2, r=255, g=255, b=255) + self.assertEqual(r, 9) + r = t(x=2, y=2, r=255, g=0, b=0) + self.assertEqual(r, 3) + + def test_copy(self): + t = Turmit(prog = 'G R B ADD ADD 42 MOD', max_int = 0xFFFF, + stack_size=42) + t2 = Turmit(turmit=t) + r = t2(x=1, y=2, r=255, g=255, b=255) + self.assertEqual(r, 9) + r = t2(x=2, y=2, r=255, g=0, b=0) + self.assertEqual(r, 3) + + + def test_init_badargs(self): + ''' Test Turmit __init__ bad arguments ''' + with self.assertRaises(RuntimeError): + t = Turmit(foobar=42) + with self.assertRaises(ValueError): + t = Turmit(prog='foobar') + + +class TurmitExprCompileTestCase(unittest.TestCase): + ''' Tests compilatin capabilities of cturmit ''' + + def test_values(self): + progs = ('1', '0x1', '9', '0x9', '0xf', '0xF', '0x1FFF', '0xFFF1', + '12345'); + for prog in progs: + t = Turmit(prog=prog) + + def test_var(self): + progs = ('x', 'y', 'r', 'g', 'b') + for prog in progs: + t = Turmit(prog=prog) + + +class TurmitOperationTestCase(unittest.TestCase): + + def test_push(self): + ''' Test Turmit _push() method ''' + t = Turmit() + t._push(1) + self.assertEqual(t.shead, 1) + t._push(2) + self.assertEqual(t.shead, 2) + + def _rpn(self, rpn, narg = 2): + if rpn not in _op_list: + raise unittest.SkipTest('"%s" not implemented' % rpn) + method, wrapped = _op_list[rpn] + if narg != len(inspect.signature(method).parameters) - 1: + self.fail('Expected %s to take %d arguments, but Turmit.%s() takes \ +%d arguments' % (rpn, narg, rpn, len(inspect.signature(method).parameters) - 1)) + + def test_add(self): + ''' Test turmit add method ''' + self._rpn('add') + + t = Turmit() + t._push(42) + t._push(8) + t.add() + self.assertEqual(t._cur, 0) + self.assertEqual(t.shead, 50) + + t._push(42) + t._push(8) + t.add() + self.assertEqual(t._cur, 1) + self.assertEqual(t.shead, 50) + + t.add() + + self.assertEqual(t._cur, 0) + self.assertEqual(t.shead, 100) + + def test_mem_sz(self): + ''' Test turmit mem_sz() operation ''' + self._rpn('mem_sz', 1) + + t = Turmit(8) + self.assertEqual(len(t._stack), 8) + + t._push(42) + t.mem_sz() + self.assertEqual(len(t._stack), 42) + self.assertEqual(t._cur, 7) + + t._push(42) + t._push(8) + t.mem_sz() + self.assertEqual(t._cur, 0) + self.assertEqual(t.shead, 42) + + def test_sub(self): + ''' Test turmit sub() method ''' + self._rpn('sub') + + t = Turmit(max_int=0xFFFF) + + t._push(50) + t._push(8) + + t.sub() + self.assertEqual(t._cur, 0) + self.assertEqual(t.shead, 42) + + t._push(50) + t._push(8) + t.sub() + self.assertEqual(t._cur, 1) + self.assertEqual(t.shead, 42) + + t.sub() + self.assertEqual(t._cur, 0) + self.assertEqual(t.shead, 0) + + t._push(8) + t._push(50) + t.sub() + self.assertEqual(t._cur, 1) + self.assertEqual(t.shead, 0xFFFF + (8 - 50)) + + def test_mul(self): + ''' Test turmit mul() method ''' + self._rpn('mul') + + t = Turmit() + + t._push(7) + t._push(6) + + t.mul() + self.assertEqual(t._cur, 0) + self.assertEqual(t.shead, 42) + + t._push(7) + t._push(6) + t.mul() + self.assertEqual(t._cur, 1) + self.assertEqual(t.shead, 42) + + t.mul() + self.assertEqual(t._cur, 0) + self.assertEqual(t.shead, 42 *42) + + def test_div(self): + ''' Test turmit division capabilities ''' + self._rpn('div') + + t = Turmit() + + t._push(43) + t._push(7) + + t.div() + self.assertEqual(t._cur, 0) + self.assertEqual(t.shead, 6) + + def test_mod(self): + ''' Test turmit mod() method ''' + self._rpn('mod') + + t = Turmit() + t._push(42) + t._push(4) + + t.mod() + self.assertEqual(t._cur, 0) + self.assertEqual(t.shead, 2) + + def test_pow(self): + ''' Test turmit pow() method ''' + self._rpn('pow') + + t = Turmit() + t._push(2) + t._push(4) + + t.pow() + self.assertEqual(t._cur, 0) + self.assertEqual(t.shead, 16) + + def test_sqrt(self): + ''' Test turmit sqrt() method ''' + self._rpn('sqrt', 1) + + t = Turmit() + t._push(42) + + t.sqrt() + + self.assertEqual(t._cur, 0) + self.assertEqual(t.shead, 6) + + def test_and(self): + ''' Test turmit binary and() method ''' + self._rpn('bin_and') + + t = Turmit() + t._push(10) + t._push(42) + + t.bin_and() + + self.assertEqual(t._cur, 0) + self.assertEqual(t.shead, 10) + + def test_or(self): + ''' Test turmit binary or() method ''' + self._rpn('bin_or') + + t = Turmit() + t._push(42) + t._push(7) + + t.bin_or() + + self.assertEqual(t._cur, 0) + self.assertEqual(t.shead, 47) + + def test_xor(self): + ''' Test turmit binary xor() method ''' + self._rpn('bin_xor') + + t = Turmit() + t._push(42) + t._push(11) + + t.bin_xor() + self.assertEqual(t._cur, 0) + self.assertEqual(t.shead, 33) + + def test_rshift(self): + ''' Test turmit rshift method ''' + self._rpn('rshift') + + t = Turmit() + t._push(42) + t._push(2) + + t.rshift() + self.assertEqual(t._cur, 0) + self.assertEqual(t.shead, 10) + + def test_lshift(self): + ''' Test turmit lshift method ''' + self._rpn('lshift') + + t = Turmit() + t._push(10) + t._push(2) + t.lshift() + self.assertEqual(t._cur, 0) + self.assertEqual(t.shead, 40) + + def test_dup(self): + ''' Test turmit dup() method ''' + self._rpn('dup', 1) + + t = Turmit() + t._push(42) + t.dup() + self.assertEqual(t._cur, 1) + self.assertEqual(t.shead, 42) + + def test_pop(self): + ''' Test turmit pop() method ''' + self._rpn('pop', 1) + + t = Turmit() + t._push(10) + t._push(2) + t.pop() + self.assertEqual(t._cur, 0) + self.assertEqual(t.shead, 10) + t.pop() + self.assertEqual(t._cur, len(t._stack) - 1) + + def test_swp(self): + ''' Test turmit swp() method ''' + self._rpn('swp', 2) + + t = Turmit() + t._push(42) + t._push(1337) + t.swp() + self.assertEqual(t._cur, 1) + self.assertEqual(t.shead, 42) + t._pop() + self.assertEqual(t.shead, 1337)