def make_extptr(obj, tag, protected): if protected is None: cdata_protected = openrlib.rlib.R_NilValue else: try: cdata_protected = protected.__sexp__._cdata except AttributeError: raise TypeError('Argument protected must inherit from %s' % type(Sexp)) ptr = _rinterface.ffi.new_handle(obj) with memorymanagement.rmemory() as rmemory: cdata = rmemory.protect( openrlib.rlib.R_MakeExternalPtr(ptr, tag, cdata_protected)) openrlib.rlib.R_RegisterCFinalizer( cdata, (_rinterface._capsule_finalizer_c if _rinterface._capsule_finalizer_c else _rinterface._capsule_finalizer)) res = _rinterface.SexpCapsuleWithPassenger(cdata, obj, ptr) return res
def unserialize(cdata: FFI.CData, cdata_env: FFI.CData) -> FFI.CData: """Unserialize an R string to an R object. Note that the R object returned is *not* protected from the R garbage collection.""" rlib = openrlib.rlib with memorymanagement.rmemory() as rmemory: sym_unserialize = rmemory.protect( rlib.Rf_install(conversion._str_to_cchar('unserialize'))) func_unserialize = rmemory.protect( _findfun(sym_unserialize, rlib.R_BaseEnv)) r_call = rmemory.protect(rlib.Rf_lang2(func_unserialize, cdata)) error_occured = ffi.new('int *', 0) res = rlib.R_tryEval(r_call, cdata_env, error_occured) if error_occured[0]: raise embedded.RRuntimeError(_geterrmessage()) return res
def __getitem__(self, key: str) -> typing.Any: if not isinstance(key, str): raise TypeError('The key must be a non-empty string.') elif not len(key): raise ValueError('The key must be a non-empty string.') with memorymanagement.rmemory() as rmemory: key_cchar = conversion._str_to_cchar(key) symbol = rmemory.protect( openrlib.rlib.Rf_install(key_cchar) ) res = rmemory.protect( _rinterface.findvar_in_frame_wrap( self.__sexp__._cdata, symbol ) ) # TODO: move check of R_UnboundValue to _rinterface if res == openrlib.rlib.R_UnboundValue: raise KeyError("'%s' not found" % key) return res
def build_rcall(rfunction, args=[], kwargs=[]): rlib = openrlib.rlib with memorymanagement.rmemory() as rmemory: rcall = rmemory.protect(rlib.Rf_allocList(len(args) + len(kwargs) + 1)) _SET_TYPEOF(rcall, rlib.LANGSXP) rlib.SETCAR(rcall, rfunction) item = rlib.CDR(rcall) for val in args: cdata = rmemory.protect(conversion._get_cdata(val)) rlib.SETCAR(item, cdata) item = rlib.CDR(item) for key, val in kwargs: if key is not None: _assert_valid_slotname(key) rlib.SET_TAG(item, rlib.Rf_install(conversion._str_to_cchar(key))) cdata = rmemory.protect(conversion._get_cdata(val)) rlib.SETCAR(item, cdata) item = rlib.CDR(item) return rcall
def __setitem__(self, key: str, value) -> None: # TODO: move body to _rinterface-level function if not (isinstance(key, str) and len(key)): raise ValueError('The key must be a non-empty string.') if (self.__sexp__._cdata == openrlib.rlib.R_BaseEnv) or \ (self.__sexp__._cdata == openrlib.rlib.R_EmptyEnv): raise ValueError('Cannot remove variables from the base or ' 'empty environments.') # TODO: call to Rf_duplicate needed ? with memorymanagement.rmemory() as rmemory: symbol = rmemory.protect( openrlib.rlib.Rf_install(conversion._str_to_cchar(key)) ) cdata = rmemory.protect(conversion._get_cdata(value)) cdata_copy = rmemory.protect( openrlib.rlib.Rf_duplicate(cdata) ) openrlib.rlib.Rf_defineVar(symbol, cdata_copy, self.__sexp__._cdata)
def from_iterable(cls, iterable, populate_func=None, set_elt=None, cast_value=None) -> VT: """Create an R vector/array from an iterable.""" if not embedded.isready(): raise embedded.RNotReadyError('Embedded R is not ready to use.') if populate_func is None: populate_func = _populate_r_vector if set_elt is None: set_elt = cls._R_SET_VECTOR_ELT if cast_value is None: cast_value = cls._CAST_IN n = len(iterable) with memorymanagement.rmemory() as rmemory: r_vector = rmemory.protect( openrlib.rlib.Rf_allocVector(cls._R_TYPE, n)) populate_func(iterable, r_vector, set_elt, cast_value) return r_vector
def find(self, key: str, wantfun: bool = False) -> sexp.Sexp: """Find an item, starting with this R environment. Raises a `KeyError` if the key cannot be found. This method is called `find` because it is somewhat different from the method :meth:`get` in Python mappings such :class:`dict`. This is looking for a key across enclosing environments, returning the first key found.""" if not isinstance(key, str): raise TypeError('The key must be a non-empty string.') elif not len(key): raise ValueError('The key must be a non-empty string.') with memorymanagement.rmemory() as rmemory: symbol = rmemory.protect( openrlib.rlib.Rf_install(conversion._str_to_cchar(key)) ) if wantfun: # One would expect this to be like # res = _rinterface._findfun(symbol, self.__sexp__._cdata) # but R's findfun will segfault if the symbol is not in # the environment. :/ rho = self while rho.rid != emptyenv.rid: res = _rinterface._findVarInFrame(symbol, rho.__sexp__._cdata) if _rinterface._TYPEOF(res) in (openrlib.rlib.CLOSXP, openrlib.rlib.BUILTINSXP): break # TODO: move check of R_UnboundValue to _rinterface ? res = openrlib.rlib.R_UnboundValue rho = rho.enclos else: res = _rinterface._findvar(symbol, self.__sexp__._cdata) # TODO: move check of R_UnboundValue to _rinterface ? if res == openrlib.rlib.R_UnboundValue: raise KeyError("'%s' not found" % key) return res
def rclass_get(scaps: _rinterface.CapsuleBase) -> StrSexpVector: rlib = openrlib.rlib with memorymanagement.rmemory() as rmemory: classes = rmemory.protect( rlib.Rf_getAttrib(scaps._cdata, rlib.R_ClassSymbol)) if rlib.Rf_length(classes) == 0: dim = rmemory.protect( rlib.Rf_getAttrib(scaps._cdata, rlib.R_DimSymbol)) ndim = rlib.Rf_length(dim) if ndim > 0: if ndim == 2: classname = 'matrix' else: classname = 'array' else: typeof = RTYPES(scaps.typeof) classname = _DEFAULT_RCLASS_NAMES.get(typeof, str(typeof)) classes = StrSexpVector.from_iterable([classname]) else: classes = conversion._cdata_to_rinterface(classes) return classes
def __delitem__(self, key: str) -> None: # Testing that key is a non-empty string is implicitly # performed when checking that the key is in the environment. if key not in self: raise KeyError("'%s' not found" % key) if self.__sexp__ == baseenv.__sexp__: raise ValueError('Values from the R base environment ' 'cannot be removed.') # TODO: also check it is not R_EmpyEnv or R_BaseNamespace if self.is_locked(): ValueError('Cannot remove an item from a locked ' 'environment.') with memorymanagement.rmemory() as rmemory: key_cdata = rmemory.protect( openrlib.rlib.Rf_mkString(conversion._str_to_cchar(key)) ) _rinterface._remove(key_cdata, self.__sexp__._cdata, openrlib.rlib.Rf_ScalarLogical( openrlib.rlib.FALSE))
def from_memoryview(cls, mview: memoryview) -> VT: """Create an R vector/array from a memoryview. The memoryview must be contiguous, and the C representation for the vector must be compatible between R and Python. If not the case, a :class:`ValueError` exception with will be raised.""" if not embedded.isready(): raise embedded.RNotReadyError('Embedded R is not ready to use.') if not mview.contiguous: raise ValueError('The memory view must be contiguous.') if ((mview.itemsize != cls._R_SIZEOF_ELT) or not hasattr(cls, '_NP_TYPESTR') or not (cls._NP_TYPESTR == '|u1' or cls._NP_TYPESTR.endswith(mview.format))): msg = ( 'Incompatible C type sizes. ' 'The R array type is {r_type} with {r_size} byte{r_size_pl} ' 'per item ' 'while the Python array type is {py_type} with {py_size} ' 'byte{py_size_pl} per item.'.format( r_type=cls._R_TYPE, r_size=cls._R_SIZEOF_ELT, r_size_pl='s' if cls._R_SIZEOF_ELT > 1 else '', py_type=mview.format, py_size=mview.itemsize, py_size_pl='s' if mview.itemsize > 1 else '')) raise ValueError(msg) r_vector = None n = len(mview) with memorymanagement.rmemory() as rmemory: r_vector = rmemory.protect( openrlib.rlib.Rf_allocVector(cls._R_TYPE, n)) dest_ptr = cls._R_GET_PTR(r_vector) src_ptr = _rinterface.ffi.from_buffer(mview) nbytes = n * mview.itemsize _rinterface.ffi.memmove(dest_ptr, src_ptr, nbytes) return r_vector
def rcall(self, keyvals, environment: SexpEnvironment): """Call/evaluate an R function. Args: - keyvals: a sequence of key/value (name/parameter) pairs. A name/parameter that is None will indicated an unnamed parameter. Like in R, keys/names do not have to be unique, partial matching can be used, and named/unnamed parameters can occur at any position in the sequence. - environment: a R environment in which to evaluate the function. """ # TODO: check keyvals are pairs ? assert isinstance(environment, SexpEnvironment) error_occured = _rinterface.ffi.new('int *', 0) with memorymanagement.rmemory() as rmemory: call_r = rmemory.protect( _rinterface.build_rcall(self.__sexp__._cdata, [], keyvals)) res = rmemory.protect( openrlib.rlib.R_tryEval(call_r, environment.__sexp__._cdata, error_occured)) if error_occured[0]: raise embedded.RRuntimeError(_rinterface._geterrmessage()) return res
def from_memoryview(cls, mview: memoryview) -> VT: """Create an R vector/array from a memoryview. The memoryview must be contiguous, and the C representation for the vector must be compatible between R and Python. If not the case, a :class:`ValueError` exception with will be raised.""" if not embedded.isready(): raise embedded.RNotReadyError('Embedded R is not ready to use.') if not mview.contiguous: raise ValueError('The memory view must be contiguous.') if not cls._check_C_compatible(mview): cls._raise_incompatible_C_size(mview) r_vector = None n = len(mview) with memorymanagement.rmemory() as rmemory: r_vector = rmemory.protect( openrlib.rlib.Rf_allocVector(cls._R_TYPE, n)) dest_ptr = cls._R_GET_PTR(r_vector) src_ptr = _rinterface.ffi.from_buffer(mview) nbytes = n * mview.itemsize _rinterface.ffi.memmove(dest_ptr, src_ptr, nbytes) return r_vector