"""Framsticks as a Python module. Static FramScript objects are available inside the module under their well known names (frams.Simulator, frams.GenePools, etc.) These objects and all values passed to and from Framsticks are instances of frams.ExtValue. Python values are automatically converted to Framstics data types. Use frams.ExtValue._makeInt()/_makeDouble()/_makeString()/_makeNull() for explicit conversions. Simple values returned from Framsticks can be converted to their natural Python counterparts using _value() (or forced to a specific type with _int()/_double()/_string()). All non-Framsticks Python attributes start with '_' to avoid conflicts with Framsticks attributes. Framsticks names that are Python reserved words are prefixed with 'x' (currently just Simulator.ximport). For sample usage, see frams-test.py and FramsticksLib.py. For interfaces in other languages (e.g. using the Framsticks library in your C++ code), see ../cpp/frams/frams-objects.h """ import ctypes, re, sys, os c_api = None # will be initialized in init(). Global because ExtValue uses it. class ExtValue(object): """All Framsticks objects and values are instances of this class. Read the documentation of the 'frams' module for more information.""" _reInsideParens = re.compile('\((.*)\)') _reservedWords = ['import'] # this list is scanned during every attribute access, only add what is really clashing with Framsticks properties _reservedXWords = ['x' + word for word in _reservedWords] _encoding = 'utf-8' def __init__(self, arg=None, dontinit=False): if dontinit: return if isinstance(arg, int): self._initFromInt(arg) elif isinstance(arg, str): self._initFromString(arg) elif isinstance(arg, float): self._initFromDouble(arg) elif arg == None: self._initFromNull() else: raise ArgumentError("Can't make ExtValue from '" + str(arg) + "'") def __del__(self): c_api.extFree(self.__ptr) def _initFromNull(self): self.__ptr = c_api.extFromNull() def _initFromInt(self, v): self.__ptr = c_api.extFromInt(v) def _initFromDouble(self, v): self.__ptr = c_api.extFromDouble(v) def _initFromString(self, v): self.__ptr = c_api.extFromString(ExtValue._cstringFromPython(v)) @classmethod def _makeNull(cls, v): e = ExtValue(None, True) e._initFromNull() return e @classmethod def _makeInt(cls, v): e = ExtValue(None, True) e._initFromInt(v) return e @classmethod def _makeDouble(cls, v): e = ExtValue(None, True) e._initFromDouble(v) return e @classmethod def _makeString(cls, v): e = ExtValue(None, True) e._initFromString(v) return e @classmethod def _rootObject(cls): e = ExtValue(None, True) e.__ptr = c_api.rootObject() return e @classmethod def _stringFromC(cls, cptr): return cptr.decode(ExtValue._encoding) @classmethod def _cstringFromPython(cls, s): return ctypes.c_char_p(s.encode(ExtValue._encoding)) def _type(self): return c_api.extType(self.__ptr) def _class(self): cls = c_api.extClass(self.__ptr) if cls == None: return None else: return ExtValue._stringFromC(cls) def _value(self): t = self._type() if t == 0: return None elif t == 1: return self._int() elif t == 2: return self._double() elif t == 3: return self._string() else: return self def _int(self): return c_api.extIntValue(self.__ptr) def _double(self): return c_api.extDoubleValue(self.__ptr) def _string(self): return ExtValue._stringFromC(c_api.extStringValue(self.__ptr)) def __str__(self): return self._string() def __dir__(self): ids = dir(type(self)) if self._type() == 4: for i in range(c_api.extPropCount(self.__ptr)): name = ExtValue._stringFromC(c_api.extPropId(self.__ptr, i)) if name in ExtValue._reservedWords: name = 'x' + name ids.append(name) return ids def __getattr__(self, key): if key[0] == '_': return self.__dict__[key] if key in ExtValue._reservedXWords: key = key[1:] prop_i = c_api.extPropFind(self.__ptr, ExtValue._cstringFromPython(key)) if prop_i < 0: raise AttributeError('no ' + str(key) + ' in ' + str(self)) t = ExtValue._stringFromC(c_api.extPropType(self.__ptr, prop_i)) if t[0] == 'p': arg_types = ExtValue._reInsideParens.search(t) if arg_types: arg_types = arg_types.group(1).split(',') # anyone wants to add argument type validation using param type declarations? def fun(*args): ext_args = [] ext_pointers = [] for a in args: if isinstance(a, ExtValue): ext = a else: ext = ExtValue(a) ext_args.append(ext) ext_pointers.append(ext.__ptr) ret = ExtValue(None, True) args_array = (ctypes.c_void_p * len(args))(*ext_pointers) ret.__ptr = c_api.extPropCall(self.__ptr, prop_i, len(args), args_array) return ret return fun else: ret = ExtValue(None, True) ret.__ptr = c_api.extPropGet(self.__ptr, prop_i) return ret def __setattr__(self, key, value): if key[0] == '_': self.__dict__[key] = value else: if key in ExtValue._reservedXWords: key = key[1:] prop_i = c_api.extPropFind(self.__ptr, ExtValue._cstringFromPython(key)) if prop_i < 0: raise AttributeError("No '" + str(key) + "' in '" + str(self) + "'") if not isinstance(value, ExtValue): value = ExtValue(value) c_api.extPropSet(self.__ptr, prop_i, value.__ptr) def __getitem__(self, key): return self.get(key) def __setitem__(self, key, value): return self.set(key, value) def __len__(self): try: return self.size._int() except: return 0 def __iter__(self): class It(object): def __init__(self, container, frams_it): self.container = container self.frams_it = frams_it def __iter__(self): return self def __next__(self): if self.frams_it.next._int() != 0: return self.frams_it.value else: raise StopIteration() return It(self, self.iterator) def init(*args): """ Initializes the connection to Framsticks dll/so. Python programs do not have to know the Framstics path but if they know, just pass the path as the first argument. Similarly '-dPATH' and '-DPATH' needed by Framsticks are optional and derived from the first path, unless they are specified as args in init(). '-LNAME' is the optional library name (full name including the file name extension), default is 'frams-objects.dll/.so' depending on the platform. All other arguments are passed to Framsticks and not interpreted by this function. """ # goals: frams_d = None frams_D = None lib_path = None lib_name = 'frams-objects.so' if os.name == 'posix' else 'frams-objects.dll' initargs = [] for a in args: if a[:2] == '-d': frams_d = a elif a[:2] == '-D': frams_D = a elif a[:2] == '-L': lib_name = a[2:] elif lib_path is None: lib_path = a else: initargs.append(a) if lib_path is None: # TODO: use environment variable and/or the zip distribution we are in when the path is not specified in arg # for now just assume the current dir is Framsticks lib_path = '.' original_dir = os.getcwd() os.chdir(lib_path) # because under Windows, frams-objects.dll requires other dll's which reside in the same directory, so we must change current dir for them to be found while loading the main dll abs_data = os.path.abspath('data') # use absolute path for -d and -D so python is free to cd anywhere without confusing Framsticks # for the hypothetical case without lib_path the abs_data must be obtained from somewhere else if frams_d is None: frams_d = '-d' + abs_data if frams_D is None: frams_D = '-D' + abs_data initargs.insert(0, frams_d) initargs.insert(0, frams_D) initargs.insert(0, 'dummy.exe') # as an offset, 0th arg is by convention app name global c_api # access global variable if lib_path is not None and os.name == 'posix': lib_name = './' + lib_name # currently we always have lib_path (even if it is incorrect) but hypothetically it could work with lib_path==None and load .so from some default system path without './' try: c_api = ctypes.CDLL(lib_name) except OSError as e: print("*** Could not find or open '%s' from '%s'.\n*** Did you provide proper arguments and is this file readable?\n" % (lib_name, os.getcwd())) raise os.chdir(original_dir) # restore current working dir after loading the library so Framsticks sees the expected directory c_api.init.argtypes = [ctypes.c_int, ctypes.POINTER(ctypes.c_char_p)] c_api.init.restype = None c_api.extFree.argtypes = [ctypes.c_void_p] c_api.extFree.restype = None c_api.extType.argtypes = [ctypes.c_void_p] c_api.extType.restype = ctypes.c_int c_api.extFromNull.argtypes = [] c_api.extFromNull.restype = ctypes.c_void_p c_api.extFromInt.argtypes = [ctypes.c_int] c_api.extFromInt.restype = ctypes.c_void_p c_api.extFromDouble.argtypes = [ctypes.c_double] c_api.extFromDouble.restype = ctypes.c_void_p c_api.extFromString.argtypes = [ctypes.c_char_p] c_api.extFromString.restype = ctypes.c_void_p c_api.extIntValue.argtypes = [ctypes.c_void_p] c_api.extIntValue.restype = ctypes.c_int c_api.extDoubleValue.argtypes = [ctypes.c_void_p] c_api.extDoubleValue.restype = ctypes.c_double c_api.extStringValue.argtypes = [ctypes.c_void_p] c_api.extStringValue.restype = ctypes.c_char_p c_api.extClass.argtypes = [ctypes.c_void_p] c_api.extClass.restype = ctypes.c_char_p c_api.extPropCount.argtypes = [ctypes.c_void_p] c_api.extPropCount.restype = ctypes.c_int c_api.extPropId.argtypes = [ctypes.c_void_p, ctypes.c_int] c_api.extPropId.restype = ctypes.c_char_p c_api.extPropType.argtypes = [ctypes.c_void_p, ctypes.c_int] c_api.extPropType.restype = ctypes.c_char_p c_api.extPropFind.argtypes = [ctypes.c_void_p, ctypes.c_char_p] c_api.extPropFind.restype = ctypes.c_int c_api.extPropGet.argtypes = [ctypes.c_void_p, ctypes.c_int] c_api.extPropGet.restype = ctypes.c_void_p c_api.extPropSet.argtypes = [ctypes.c_void_p, ctypes.c_int, ctypes.c_void_p] c_api.extPropSet.restype = ctypes.c_int c_api.extPropCall.argtypes = [ctypes.c_void_p, ctypes.c_int, ctypes.c_int, ctypes.c_void_p] c_api.extPropCall.restype = ctypes.c_void_p c_api.rootObject.argtypes = [] c_api.rootObject.restype = ctypes.c_void_p c_args = (ctypes.c_char_p * len(initargs))(*list(a.encode(ExtValue._encoding) for a in initargs)) c_api.init(len(initargs), c_args) Root = ExtValue._rootObject() for n in dir(Root): if n[0].isalpha(): attr = getattr(Root, n) if isinstance(attr, ExtValue): attr = attr._value() setattr(sys.modules[__name__], n, attr) print('Using Framsticks version: ' + str(Simulator.version_string)) print('Home (writable) dir : ' + home_dir) print('Resources dir : ' + res_dir) print()