import logging from .rpnlib import * logger = logging.getLogger() class Turmit(object): ''' @brief Represent a turmit that act given an RPN expression with an infinite looping stack with variable stack size. ''' def __init__(self, stack_size=8, **kwargs): ''' @brief Instanciated a new Turmit with a programm and a memory stack @param stack_size int : stack_size @param prog str | RpnExpr | None : turmit programm if None generate it randomlt @param prog_size int : if prog is generated randomly it will make this size @param max_int int : maximum integer value + 1 @param signed_int bool : if True integers are signed @throw ValueError for bad option value @throw RuntimError for bad arguments ''' if stack_size < 2: msg = 'Stack size has to be >= 2 but %d given' % (stack_size) raise ValueError(msg) ## @brief List that represent a stack self._stack = [ 0 for _ in range(stack_size)] ## @brief Stack head index self._cur = stack_size - 1 ## @brief Stores turmit direction self._dir = 0 ## @brief Stores turmit program self._prog = None self._prog_sz = 42 ## @brief Stores maximum int value + 1 # @note This limit is handled by @ref rpnlib.RpnOp() self._max_int = 0x10000 ## @brief If True integers are signed # @note This limit is handled by @ref rpnlib.RpnOp() self._signed_int = True if 'max_int' in kwargs: v = kwargs['max_int'] if v < 1: raise ValueError('Maximum int have to be >= 1 but %d found' % v) self._max_int = kwargs['max_int'] del(kwargs['max_int']) if 'signed_int' in kwargs: self._signed_int = bool(kwargs['signed_int']) del(kwargs['signed_int']) if 'prog_size' in kwargs: v = kwargs['prog_size'] if v < 2: raise ValueError('Prog_size have to be >= 2 but %d found' % v) self._prog_sz = v del(kwargs['prog_size']) if 'prog' in kwargs: v = kwargs['prog'] if isinstance(v, str): v = RpnExpr.from_string(v) if isinstance(v, RpnExpr): self._prog = v else: msg = 'Str or RpnExpr expected but %s found' % (type(v)) raise TypeError(msg) del(kwargs['prog']) if len(kwargs) > 0: msg = 'Unexpected arguments : [%s]' % ', '.join(kwargs.keys()) raise RuntimeError(msg) if self._prog is None: self._prog = RpnExpr.random(self._prog_sz) self.__expr_fun = lambda *args, **kwargs: (0,) def __call__(self, **context): ''' @brief Exec the RPN expression and return the stack head @param context dict : dict with variable values (see @ref rpnlib._var_list) @return stack head after RPN ''' context = {k:int(context[k]) % self._max_int for k in context} if self.__expr_fun is None: self._compile() ret = self.__expr_fun(self, **context) if isinstance(ret, int): return ret #self.__ip = 0 self.__ip = ret[0] while self.__ip < len(self._prog): sym = self._prog[self.__ip] if sym.optype == RpnSymbol.VALUE: self._push(sym.value) elif sym.optype == RpnSymbol.VARIABLE: self._push(context[sym.value.lower()]) elif sym.optype == RpnSymbol.OPERATION: getattr(self, sym.value.lower())() self.__ip += 1 return self.shead def _compile(self): code = 'def expr_fun(self, x, y, r, g, b):\n' for ip, sym in enumerate(self._prog): if sym.optype == RpnSymbol.VALUE: code += ''' self._cur += 1 self._cur %%= len(self._stack) self._stack[self._cur] = %d ''' % sym.value elif sym.optype == RpnSymbol.VARIABLE: code += ''' self._cur += 1 self._cur %%= len(self._stack) self._stack[self._cur] = %s ''' % sym.value.lower() elif sym.value == 'add': code += ''' a = self._stack[self._cur] self._cur = (self._cur - 1) if self._cur > 0 else (len(self._stack) - 1) self._stack[self._cur] += a self._stack[self._cur] %= self._max_int ''' elif sym.value == 'sub': code += ''' a = self._stack[self._cur] self._cur = (self._cur - 1) if self._cur > 0 else (len(self._stack) - 1) self._stack[self._cur] = self._stack[self._cur] - a if self._stack[self._cur] < 0: self._stack[self._cur] += self._max_int ''' elif sym.value == 'mul': code += ''' a = self._stack[self._cur] self._cur = (self._cur - 1) if self._cur > 0 else (len(self._stack) - 1) self._stack[self._cur] *= a self._stack[self._cur] %= self._max_int ''' elif sym.value == 'div': code += ''' a = self._stack[self._cur] self._cur = (self._cur - 1) if self._cur > 0 else (len(self._stack) - 1) if a != 0: self._stack[self._cur] = self._stack[self._cur] // a ''' elif sym.value == 'mod': code += ''' a = self._stack[self._cur] self._cur = (self._cur - 1) if self._cur > 0 else (len(self._stack) - 1) if a != 0: self._stack[self._cur] = self._stack[self._cur] % a ''' elif sym.value == 'bin_and': code += ''' a = self._stack[self._cur] self._cur = (self._cur - 1) if self._cur > 0 else (len(self._stack) - 1) self._stack[self._cur] &= a ''' elif sym.value == 'bin_or': code += ''' a = self._stack[self._cur] self._cur = (self._cur - 1) if self._cur > 0 else (len(self._stack) - 1) self._stack[self._cur] |= a ''' elif sym.value == 'bin_xor': code += ''' a = self._stack[self._cur] self._cur = (self._cur - 1) if self._cur > 0 else (len(self._stack) - 1) self._stack[self._cur] ^= a ''' elif sym.value == 'lshift': code += ''' a = self._stack[self._cur] self._cur = (self._cur - 1) if self._cur > 0 else (len(self._stack) - 1) self._stack[self._cur] = (self._stack[self._cur] << a) % self._max_int ''' elif sym.value == 'rshift': code += ''' a = self._stack[self._cur] self._cur = (self._cur - 1) if self._cur > 0 else (len(self._stack) - 1) self._stack[self._cur] = self._stack[self._cur] >> a ''' elif sym.value == 'dup': code += ''' a = self._stack[self._cur] self._cur += 1 self._cur %= len(self._stack) self._stack[self._cur] = a ''' elif sym.value == 'pop': code += ''' self._cur = (self._cur - 1) if self._cur > 0 else (len(self._stack) - 1) ''' else: code += ''' return (%d,) ''' % ip code += ''' return self._stack[self._cur] ''' g = {} l = {} exec(code, g, l) self.__expr_fun = l['expr_fun'] @property def shead(self): ''' @brief Stack head attribute ''' return self._stack[self._cur] def _pop(self): ''' @brief Pops a value from the stack @note If stack head is 0, set the stack head to len(self._stack) - 1 @return popped value ''' res = self._stack[self._cur] if self._cur <= 0: self._cur = len(self._stack) - 1 else: self._cur -= 1 return res def _push(self, val): ''' @brief Pushes a value on the stack @note If no space left on the stack, the head is set to 0 @param val int : value to push ''' self._cur += 1 if self._cur >= len(self._stack) or abs(self._cur) >= len(self._stack): self._cur = 0 self._stack[self._cur] = self._cast_int(val) def _cast_int(self, val): ''' @brief Transform integer given Turmit max int and signed_int attr @param val int : integer to transform @return integer ''' val = int(val) if self._max_int is not None: val %= self._max_int if not self._signed_int and val < 0: val = self._max_int - val return val @RpnOp def mem_sz(self, new_sz): ''' @brief Update memory stack size ''' stksz = len(self._stack) new_sz %= 0xFFF if new_sz < 2: new_sz = 2 if new_sz > stksz: self._stack += [ 0 for _ in range(new_sz - stksz) ] elif new_sz < stksz: self._stack = self._stack[0:new_sz] if self._cur >= new_sz: self._cur = 0 @RpnOp def add(self, a, b): ''' @brief Adds one value to another @param a int : value @param b int : value to add to a @return a + b ''' return a + b @RpnOp def sub(self, a, b): ''' @brief Substitutes a value to another @param a int : value @param b int : value to substitute to a @return a - b ''' return a - b @RpnOp def bin_and(self, a, b): ''' @brief Binary and @param a int : value @param b int : value @return a & b ''' return a & b @RpnOp def dup(self, a): ''' @brief Duplicates stack head @param a int : value @return a ''' self._push(a) return a @RpnOp def lshift(self, a, b): ''' @brief Left shifts a of b @param a int : value @param b int : value @return a << b ''' return a << b @RpnOp def mod(self, a, b): ''' @brief Gives a modulo b @param a int : value @param b int : value @return a % b ''' return a % b @RpnOp def mul(self, a, b): ''' @brief Multiplies a with b @param a int : value @param b int : value @return a * b ''' return a * b @RpnOp def div(self, a, b): ''' @brief Divides a with b @param a int : value @param b int : value @return a // b ''' return a // b @RpnOp def bin_or(self, a, b): ''' @brief Binary or @param a int : value @param b int : value @return a | b ''' return a | b @RpnOp def bin_xor(self, a, b): ''' @brief Binary xor @param a int : value @param b int : value @return a ^ b ''' return a ^ b @RpnOp def pop(self, a): ''' @brief Pops a, head of the stack @param a int : value ''' pass @RpnOp def swp(self, a, b): ''' @brief Swap the two values on top of the stack @param a int : v1 @param b int : v2 ''' self._push(b) self._push(a) @RpnOp def jmp(self, offset): ''' @brief Increments IP @param offset int : jump offset symbols ''' self.__ip += offset @RpnOp def jz(self, a, offset): ''' @brief Increments IP if a == 0 @param a int : operand @param offset int : jump offset symbols ''' if a == 0: self.__ip += offset """ @RpnOp def jnz(self, a, offset): ''' @brief Increments IP if a != 0 @param a int : operand @param offset int : jump offset symbols ''' if a != 0: self.__ip += offset """ @RpnOp def jcmp(self, a, b, cnd, offset): ''' @brief Increments IP if a == b Condition type is encoded on an int : - 0b0000 : je - 0b0001 : jne - 0b0010 : jle - 0b0011 : jl - 0b0100 : jg - 0b0101 : jge - 0b0110 : jne - 0b0111 : jmp @param a int : operand @param b int : operand @param cnd int : condition type @param offset int : jump offset symbols ''' jmp = False if cnd & 1 == 0 and a == b or cnd & 1 and a != b or\ cnd & 0b10 and a < b or cnd & 0b100 and a > b: self.__ip += offset