44#include "emscripten.h"
55#include "error_handling.h"
66
7+ #define Py_ENTER ()
8+ #define Py_EXIT ()
9+
710#define HAS_CONTAINS (1 << 0)
811#define HAS_GET (1 << 1)
912#define HAS_LENGTH (1 << 2)
@@ -14,6 +17,11 @@ EM_JS_VAL(JsVal, pyproxy_new, (PyObject * ptrobj), {
1417 return Module .pyproxy_new (ptrobj );
1518});
1619
20+ EM_JS (int , PyProxy_Check , (JsVal val ), {
21+ return API .isPyProxy (val );
22+ });
23+
24+
1725EMSCRIPTEN_KEEPALIVE JsVal
1826_pyproxy_str (PyObject * pyobj )
1927{
@@ -84,8 +92,150 @@ pyproxy_getflags(PyObject* pyobj)
8492 return type_getflags (obj_type );
8593}
8694
87- #define Py_ENTER ()
88- #define Py_EXIT ()
95+ EMSCRIPTEN_KEEPALIVE int
96+ _pyproxy_hasattr (PyObject * pyobj , JsVal jskey )
97+ {
98+ PyObject * pykey = NULL ;
99+ int result = -1 ;
100+
101+ pykey = js2python (jskey );
102+ FAIL_IF_NULL (pykey );
103+ result = PyObject_HasAttr (pyobj , pykey );
104+
105+ finally :
106+ Py_CLEAR (pykey );
107+ return result ;
108+ }
109+
110+ /* Specialized version of _PyObject_GenericGetAttrWithDict
111+ specifically for the LOAD_METHOD opcode.
112+
113+ Return 1 if a method is found, 0 if it's a regular attribute
114+ from __dict__ or something returned by using a descriptor
115+ protocol.
116+
117+ `method` will point to the resolved attribute or NULL. In the
118+ latter case, an error will be set.
119+ */
120+ int
121+ _PyObject_GetMethod (PyObject * obj , PyObject * name , PyObject * * method );
122+
123+ EM_JS (JsVal , proxy_cache_get , (JsVal proxyCache , PyObject * descr ), {
124+ const proxy = proxyCache .get (descr );
125+ if (!proxy ) {
126+ return Module .error ;
127+ }
128+ // Okay found a proxy. Is it alive?
129+ if (pyproxyIsAlive (proxy )) {
130+ return proxy ;
131+ } else {
132+ // It's dead, tidy up
133+ proxyCache .delete (descr );
134+ return Module .error ;
135+ }
136+ })
137+
138+ // clang-format off
139+ EM_JS (void ,
140+ proxy_cache_set ,
141+ (JsVal proxyCache , PyObject * descr , JsVal proxy ), {
142+ proxyCache .set (descr , proxy );
143+ })
144+ // clang-format on
145+
146+ EMSCRIPTEN_KEEPALIVE JsVal
147+ _pyproxy_getattr (PyObject * pyobj , JsVal key , JsVal proxyCache )
148+ {
149+ bool success = false;
150+ PyObject * pykey = NULL ;
151+ PyObject * pydescr = NULL ;
152+ PyObject * pyresult = NULL ;
153+ JsVal result = JS_ERROR ;
154+
155+ pykey = js2python (key );
156+ FAIL_IF_NULL (pykey );
157+ // If it's a method, we use the descriptor pointer as the cache key rather
158+ // than the actual bound method. This allows us to reuse bound methods from
159+ // the cache.
160+ // _PyObject_GetMethod will return true and store a descriptor into pydescr if
161+ // the attribute we are looking up is a method, otherwise it will return false
162+ // and set pydescr to the actual attribute (in particular, I believe that it
163+ // will resolve other types of getter descriptors automatically).
164+ int is_method = _PyObject_GetMethod (pyobj , pykey , & pydescr );
165+ FAIL_IF_NULL (pydescr );
166+ JsVal cached_proxy = proxy_cache_get (proxyCache , pydescr ); /* borrowed */
167+ if (!JsvError_Check (cached_proxy )) {
168+ result = cached_proxy ;
169+ goto success ;
170+ }
171+ if (PyErr_Occurred ()) {
172+ FAIL ();
173+ }
174+ if (is_method ) {
175+ pyresult =
176+ Py_TYPE (pydescr )-> tp_descr_get (pydescr , pyobj , (PyObject * )Py_TYPE (pyobj ));
177+ FAIL_IF_NULL (pyresult );
178+ } else {
179+ pyresult = pydescr ;
180+ Py_INCREF (pydescr );
181+ }
182+ result = python2js (pyresult );
183+ if (PyProxy_Check (result )) {
184+ // If a getter returns a different object every time, this could potentially
185+ // fill up the cache with a lot of junk. If this is a problem, the user will
186+ // have to manually destroy the attributes.
187+ proxy_cache_set (proxyCache , pydescr , result );
188+ }
189+
190+ success :
191+ success = true;
192+ finally :
193+ Py_CLEAR (pykey );
194+ Py_CLEAR (pydescr );
195+ Py_CLEAR (pyresult );
196+ if (!success ) {
197+ if (PyErr_ExceptionMatches (PyExc_AttributeError )) {
198+ PyErr_Clear ();
199+ }
200+ }
201+ return result ;
202+ };
203+
204+ EMSCRIPTEN_KEEPALIVE int
205+ _pyproxy_setattr (PyObject * pyobj , JsVal key , JsVal value )
206+ {
207+ bool success = false;
208+ PyObject * pykey = NULL ;
209+ PyObject * pyval = NULL ;
210+
211+ pykey = js2python (key );
212+ FAIL_IF_NULL (pykey );
213+ pyval = js2python (value );
214+ FAIL_IF_NULL (pyval );
215+ FAIL_IF_MINUS_ONE (PyObject_SetAttr (pyobj , pykey , pyval ));
216+
217+ success = true;
218+ finally :
219+ Py_CLEAR (pykey );
220+ Py_CLEAR (pyval );
221+ return success ? 0 : -1 ;
222+ }
223+
224+ EMSCRIPTEN_KEEPALIVE int
225+ _pyproxy_delattr (PyObject * pyobj , JsVal idkey )
226+ {
227+ bool success = false;
228+ PyObject * pykey = NULL ;
229+
230+ pykey = js2python (idkey );
231+ FAIL_IF_NULL (pykey );
232+ FAIL_IF_MINUS_ONE (PyObject_DelAttr (pyobj , pykey ));
233+
234+ success = true;
235+ finally :
236+ Py_CLEAR (pykey );
237+ return success ? 0 : -1 ;
238+ }
89239
90240EMSCRIPTEN_KEEPALIVE int
91241_pyproxy_contains (PyObject * pyobj , JsVal idkey )
0 commit comments