import collections import inspect import random _op_list = collections.OrderedDict() _var_list = collections.OrderedDict() _op_alias = collections.OrderedDict() _op_alias['add'] = '+' _op_alias['sub'] = '-' _op_alias['mul'] = '*' _op_alias['div'] = '/' _op_alias['mod'] = '%' _op_alias['bin_and'] = '&' _op_alias['bin_or'] = '|' _op_alias['bin_xor'] = '^' _op_alias['lshift'] = '<<' _op_alias['rshift'] = '>>' _op_alias['pow'] = '**' _var_list['x'] = 0 _var_list['y'] = 0 _var_list['r'] = 0 _var_list['g'] = 0 _var_list['b'] = 0 def RpnOpNoMod(method): return RpnOp(method, True) def RpnOp(method, nomod = False): ''' @brief Decorator for RPN operation that autodetect argument count Autodetect argument count and pop them from the stack. Then attempt to push values from result as an array. If it fails result is push as it. @warning if result is None nothing is push ''' narg = len(inspect.signature(method).parameters)-1 def wrapped(self): args = [ self._pop() for _ in range(narg) ] determinist = True if not nomod: for arg in args: if isinstance(arg, IntVar): determinist = False break args.reverse() try: res = method(self, *args) except ZeroDivisionError: res = None if res is None: return determinist try: for topush in res: if not isinstance(topush, int): raise ValueError('Turmit.%s() returned a list containing a\ %s : %s' % (method.__name__, type(topush), topush)) topush = self._cast_int(topush) self._push(topush) except TypeError: if not isinstance(res, int): raise ValueError('Turmit.%s() returned a list containing a\ %s : %s' % (method.__name__, type(res), res)) self._push(res) return determinist _op_list[method.__name__] = (method, wrapped) return wrapped class RpnExpr(list): ''' @brief Extended list that only contains RpnSymbol ''' def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) for i, item in enumerate(self): if not isinstance(item, RpnSymbol): raise TypeError('Item %d is %s but RpnSymbol expected' % (i, item)) def __setitem__(self, idx, val): if not isinstance(val, RpnSymbol): raise TypeError('RpnSymbol expected but got %s' % type(val)) super().__setitem__(idx, val) def __str__(self, small=True): if small: return ' '.join([_op_alias[str(sym).lower()] if str(sym).lower() in _op_alias else str(sym) for sym in self]) return ' '.join([str(sym) for sym in self]) def __eq__(self, b): if len(self) != len(b): return False for i in range(len(self)): if self[i] != b[i]: return False return True @classmethod def random(cls, sz): return cls([RpnSymbol.random() for _ in range(sz)]) @classmethod def from_string(cls, val): ''' @brief generate an expr from a string @param val str : expression @return RpnExpr ''' return cls([RpnSymbol.from_string(s.strip()) for s in val.split() if len(s.strip()) > 0]) class RpnSymbol(object): ''' @brief Designed to handle operation and operand for Turmit expr ''' OPERATION = 0x0 VALUE = 0x1 VARIABLE = 0x3 def __init__(self, value, optype = VALUE): ''' @brief RpnSymbol constructor @param value int : positiv integer moded given optype @param optype int : one of @ref OPERATION @ref VALUE @ref VARIABLE ''' self.optype = optype self.value = value err = False err_type = 'Unknown' if optype == self.OPERATION: if isinstance(value, str): if self.value.lower() in _op_alias.values(): idx = list(_op_alias.values()).index(self.value.lower()) self.value = list(_op_alias.keys())[idx] if not value in _op_list: err = True err_type = 'operation' else: self.value = value else: self.value = list(_op_list.keys())[value % len(_op_list)] self.value = self.value.lower() elif optype == self.VARIABLE: if isinstance(value, str): if not value in _var_list: err = True err_type = 'variable' else: self.value = value else: self.value = list(_var_list.keys())[value % len(_var_list)] self.value = self.value.lower() if err: msg = 'Invalid %s : "%s"' % (err_type, value.upper()) def __str__(self, small=True): ''' @brief Return a string representation of current symbol ''' if self.optype == self.OPERATION: return _op_list[self.value][0].__name__.upper() elif self.optype == self.VALUE: if small: return '0x%X' % self.value else: return '0x%04X' % self.value else: return self.value.upper() def __repr__(self): if self.optype == self.OPERATION: optype = 'OPE' elif self.optype == self.VALUE: optype = 'VAL' elif self.optype == self.VARIABLE: optype = 'VAR' name = '%s.%s' % (self.__class__.__module__, self.__class__.__name__) return '<%s %s(%s)>' % (name, optype, self.value) def __eq__(self, b): if not isinstance(b, RpnSymbol): return False return b.optype == self.optype and b.value == self.value def __copy__(self): return self.__class__(self.value, self.optype) @classmethod def from_string(cls, val): try: val = int(val, base=0) return cls(val, cls.VALUE) except Exception: pass if val.lower() in _op_list.keys() or val.lower() in _op_alias.values(): return cls(val, cls.OPERATION) elif val.lower() in _var_list.keys(): return cls(val, cls.VARIABLE) raise ValueError('Unrecognized symbol "%s"' % val) @classmethod def random(cls, optype=None): ''' @brief Return a randomly generated symbol ''' if optype is None: optype = [cls.OPERATION, cls.VALUE, cls.VARIABLE] optype = optype[random.randint(0,2)] if optype == cls.VALUE: vals = [(0,0xF), (0, 0xFF), (0,0xFFFF)] return cls(random.randint(*vals[random.randint(0,2)]), optype) else: return cls(random.randint(0, 0xFFFF), optype) @classmethod def rand_op(cls): ''' @return Random operation ''' return cls.random(cls.OPERATION) @classmethod def rand_var(cls): ''' @retrun Random variable ''' return cls.random(cls.VARIABLE) @classmethod def rand_value(cls): ''' @return Random value ''' return cls.random(cls.VALUE) class IntVar(int): pass