1"""Mapping between Python objects, C objects, and R objects."""
2
3# TODO: rename the module with a prefix _ to indicate that this should
4#   not be used outside of rpy2's own code
5
6from typing import Callable
7from typing import Dict
8from typing import Optional
9from typing import Type
10from typing import Union
11from rpy2.rinterface_lib import openrlib
12from rpy2.rinterface_lib import _rinterface_capi as _rinterface
13
14ffi = openrlib.ffi
15
16_R_RPY2_MAP = {}  # type: Dict[int, Type]
17
18
19class DummyMissingRpy2Map(object):
20    def __init__(self, *args, **kwargs):
21        raise NotImplementedError('The default object mapper class is no set.')
22
23
24_R_RPY2_DEFAULT_MAP: Type[
25    Union[DummyMissingRpy2Map, '_rinterface.SupportsSEXP']
26] = DummyMissingRpy2Map
27
28# TODO: shouldn't the second type strictly inherit from an rpy2
29# R object ?
30_PY_RPY2_MAP = {}  # type: Dict[Type, Callable]
31
32
33def _cdata_to_rinterface(cdata):
34    scaps = _rinterface.SexpCapsule(cdata)
35    t = cdata.sxpinfo.type
36    if t in _R_RPY2_MAP:
37        cls = _R_RPY2_MAP[t]
38    else:
39        cls = _R_RPY2_DEFAULT_MAP
40    return cls(scaps)
41
42
43def _cdata_res_to_rinterface(function):
44    def _(*args, **kwargs):
45        cdata = function(*args, **kwargs)
46        # TODO: test cdata is of the expected CType
47        return _cdata_to_rinterface(cdata)
48    return _
49
50
51def _sexpcapsule_to_rinterface(scaps: '_rinterface.SexpCapsule'):
52    cls = _R_RPY2_MAP.get(scaps.typeof, _R_RPY2_DEFAULT_MAP)
53    return cls(scaps)
54
55
56# TODO: The name of the function is misleading, I think. Consider changing it.
57def _python_to_cdata(obj):
58    t = type(obj)
59    if t in _PY_RPY2_MAP:
60        cls = _PY_RPY2_MAP[t]
61    else:
62        raise ValueError(obj)
63        # cls = _PY_RPY2_DEFAULT_MAP
64    cdata = cls(obj)
65    return cdata
66
67
68# TODO: can scalars in R's C API be used ?
69def _int_to_sexp(val: int):
70    rlib = openrlib.rlib
71    # TODO: test value is not too large for R's ints
72    s = rlib.Rf_protect(rlib.Rf_allocVector(rlib.INTSXP, 1))
73    openrlib.SET_INTEGER_ELT(s, 0, val)
74    rlib.Rf_unprotect(1)
75    return s
76
77
78def _bool_to_sexp(val: bool):
79    # TODO: test value is not too large for R's ints
80    rlib = openrlib.rlib
81    s = rlib.Rf_protect(rlib.Rf_allocVector(rlib.LGLSXP, 1))
82    openrlib.SET_LOGICAL_ELT(s, 0, int(val))
83    rlib.Rf_unprotect(1)
84    return s
85
86
87def _float_to_sexp(val: float):
88    rlib = openrlib.rlib
89    s = rlib.Rf_protect(rlib.Rf_allocVector(rlib.REALSXP, 1))
90    openrlib.SET_REAL_ELT(s, 0, val)
91    rlib.Rf_unprotect(1)
92    return s
93
94
95def _complex_to_sexp(val: complex):
96    rlib = openrlib.rlib
97    s = rlib.Rf_protect(rlib.Rf_allocVector(rlib.CPLXSXP, 1))
98    openrlib.SET_COMPLEX_ELT(
99        s, 0,
100        val
101    )
102    rlib.Rf_unprotect(1)
103    return s
104
105
106# Default encoding for converting R string back to Python
107# As defined in R_API.h, possible values are
108#   CE_NATIVE = 0,
109#   CE_UTF8   = 1,
110#   CE_LATIN1 = 2,
111#   CE_BYTES  = 3,
112#   CE_SYMBOL = 5,
113#   CE_ANY    = 99
114
115# Default encoding for converting R strings to Python
116_R_ENC_PY = {None: 'ascii'}
117
118
119def _str_to_cchar(s: str, encoding: str = 'utf-8'):
120    # TODO: use isString and installTrChar
121    b = s.encode(encoding)
122    return ffi.new('char[]', b)
123
124
125def _cchar_to_str(c, encoding: str) -> str:
126    # TODO: use isString and installTrChar
127    s = ffi.string(c).decode(encoding)
128    return s
129
130
131def _cchar_to_str_with_maxlen(c, maxlen: int, encoding: str) -> str:
132    # TODO: use isString and installTrChar
133    s = ffi.string(c, maxlen).decode(encoding)
134    return s
135
136
137def _str_to_charsxp(val: Optional[str]):
138    rlib = openrlib.rlib
139    if val is None:
140        s = rlib.R_NaString
141    else:
142        cchar = _str_to_cchar(val, encoding='utf-8')
143        s = rlib.Rf_mkCharCE(cchar, openrlib.rlib.CE_UTF8)
144    return s
145
146
147def _str_to_sexp(val: str):
148    rlib = openrlib.rlib
149    s = rlib.Rf_protect(rlib.Rf_allocVector(rlib.STRSXP, 1))
150    charval = _str_to_charsxp(val)
151    rlib.SET_STRING_ELT(s, 0, charval)
152    rlib.Rf_unprotect(1)
153    return s
154
155
156def _str_to_symsxp(val: str):
157    rlib = openrlib.rlib
158    cchar = _str_to_cchar(val)
159    s = rlib.Rf_install(cchar)
160    return s
161
162
163_PY_R_MAP = {}  # type: Dict[Type, Union[Callable, None, bool]]
164
165
166# TODO: Do special values such as NAs need to be mapped into a SEXP when
167#   a scalar ?
168def _get_cdata(obj):
169    cls = _PY_R_MAP.get(type(obj))
170    if cls is False:
171        cdata = obj
172    elif cls is None:
173        try:
174            cdata = obj.__sexp__._cdata
175        except AttributeError:
176            raise ValueError('Not an rpy2 R object and unable '
177                             'to map it into one: %s' % repr(obj))
178    else:
179        cdata = cls(obj)
180    return cdata
181