Skip to content

Commit 387286b

Browse files
Merge pull request #216 from Distributive-Network/philippe/fix/201
proper class structure for proxies: refactoring. Missing Dict-Object …
2 parents b1e4b45 + 8dc4f4b commit 387286b

18 files changed

+613
-288
lines changed

LICENSE

Lines changed: 170 additions & 2 deletions
Large diffs are not rendered by default.

README.md

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@
33
![Testing Suite](https://github.com/Kings-Distributed-Systems/PythonMonkey/actions/workflows/tests.yaml/badge.svg)
44

55
## About
6-
[PythonMonkey](https://pythonmonkey.io) is a Mozilla [SpiderMonkey](https://firefox-source-docs.mozilla.org/js/index.html) JavaScript engine embedded into the Python VM,
7-
using the Python engine to provide the JS host environment.
6+
[PythonMonkey](https://pythonmonkey.io) is a Mozilla [SpiderMonkey](https://firefox-source-docs.mozilla.org/js/index.html) JavaScript engine embedded into the Python Runtime,
7+
using the Python engine to provide the Javascript host environment.
88

9-
This product is in an early stage, approximately 80% to MVP as of July 2023. It is under active development by [Distributive](https://distributive.network/).
9+
We feature JavaScript Array and Object methods on Python List and Dictionaries using the cPython C API, and the inverse using the Mozilla Firefox Spidermonkey JavaScript C++ API.
10+
11+
This product is in an intermediate stage, approximately 90% to MVP as of January 2024. It is under active development by [Distributive](https://distributive.network/).
1012
External contributions and feedback are welcome and encouraged.
1113

1214
### tl;dr
@@ -24,16 +26,17 @@ js_eval("console.log")('hello, world')
2426
- Make writing code in either JS or Python a developer preference
2527
- Use JavaScript libraries from Python
2628
- Use Python libraries from JavaScript
27-
- Same process runs both JS and Python VMs - no serialization, pipes, etc
29+
- The same process runs both JavaScript and Python VirtualMachines - no serialization, pipes, etc
30+
- Python Lists and Dicts behave as Javacript Arrays and Objects, and vice-versa, fully adapting to the given context.
2831

2932
### Data Interchange
30-
- Strings share immutable backing stores whenever possible (when allocating engine choses UCS-2 or Latin-1 internal string representation) to keep memory consumption under control, and to make it possible to move very large strings between JS and Python library code without memory-copy overhead.
33+
- Strings share immutable backing stores whenever possible (when allocating engine choses UCS-2 or Latin-1 internal string representation) to keep memory consumption under control, and to make it possible to move very large strings between JavaScript and Python library code without memory-copy overhead.
3134
- TypedArrays share mutable backing stores.
32-
- JS objects are represented by Python dicts
35+
- JS objects are represented by Python dicts through a Dict subclass for optimal compatibility. Similarly for JS arrays and Python lists.
3336
- JS Date objects are represented by Python datetime.datetime objects
3437
- Intrinsics (boolean, number, null, undefined) are passed by value
3538
- JS Functions are automatically wrapped so that they behave like Python functions, and vice-versa
36-
- Python Lists are represented by JS true Arrays
39+
- Python Lists are represented by JS true arrays and support all Array methods through a JS API Proxy. Similarly for Python Dicts and JS objects.
3740

3841
### Roadmap
3942
- [done] JS instrinsics coerce to Python intrinsics
@@ -49,14 +52,13 @@ js_eval("console.log")('hello, world')
4952
- [done] Python `require` function, returns a coerced dict of module exports
5053
- [done] Python functions coerce to JS function wrappers
5154
- [done] CommonJS module system .py loader, loads Python modules for use by JS
52-
- JS object->Python dict coercion supports inherited-property lookup (via __getattribute__?)
5355
- [done] Python host environment supplies event loop, including EventEmitter, setTimeout, etc.
54-
- [done] Python host environment supplies XMLHttpRequest (other project?)
55-
- Python host environment supplies basic subsets of NodeJS's fs, path, process, etc, modules; as-needed by dcp-client (other project?)
56+
- [done] Python host environment supplies XMLHttpRequest
57+
- Python host environment supplies basic subsets of NodeJS's fs, path, process, etc, modules; as-needed by dcp-client
5658
- [done] Python TypedArrays coerce to JS TypeArrays
5759
- [done] JS TypedArrays coerce to Python TypeArrays
58-
- [done] JS Arrays coerce to Python Lists, providing all List methods implemented on Arrays
59-
- [done] Python List coerce to JS Arrays, providing all Array methods implemented on Lists
60+
- [done] Python lists coerce to JS Arrays
61+
- [done] JS arrays coerce to Python lists
6062

6163
## Build Instructions
6264

@@ -229,6 +231,8 @@ Where shared backing store is not possible, PythonMonkey will automatically emit
229231
the "real" data structure as its value authority. Only immutable intrinsics are copied. This means
230232
that if you update an object in JavaScript, the corresponding Dict in Python will be updated, etc.
231233

234+
JavaScript Array and Object methods are implemented on Python List and Dictionaries, and vice-versa.
235+
232236
| Python Type | JavaScript Type |
233237
|:------------|:----------------|
234238
| String | string

include/PyBaseProxyHandler.hh

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/**
2+
* @file PyBaseProxyHandler.hh
3+
* @author Caleb Aikens ([email protected]) and Philippe Laporte ([email protected])
4+
* @brief Structs for creating JS proxy objects.
5+
* @version 0.1
6+
* @date 2023-04-20
7+
*
8+
* Copyright (c) 2023-2024 Distributive Corp.
9+
*
10+
*/
11+
12+
#ifndef PythonMonkey_PyBaseProxy_
13+
#define PythonMonkey_PyBaseProxy_
14+
15+
#include <jsapi.h>
16+
#include <jsfriendapi.h>
17+
#include <js/Conversions.h>
18+
#include <js/Proxy.h>
19+
20+
#include <Python.h>
21+
22+
/**
23+
* @brief base class for PyDictProxyHandler and PyListProxyHandler
24+
*/
25+
struct PyBaseProxyHandler : public js::BaseProxyHandler {
26+
public:
27+
PyBaseProxyHandler(PyObject *pyObj, const void *family) : js::BaseProxyHandler(family), pyObject(pyObj) {};
28+
PyObject *pyObject; // @TODO (Caleb Aikens) Consider putting this in a private slot
29+
30+
bool getPrototypeIfOrdinary(JSContext *cx, JS::HandleObject proxy, bool *isOrdinary, JS::MutableHandleObject protop) const override final;
31+
bool preventExtensions(JSContext *cx, JS::HandleObject proxy, JS::ObjectOpResult &result) const override final;
32+
bool isExtensible(JSContext *cx, JS::HandleObject proxy, bool *extensible) const override final;
33+
};
34+
35+
enum ProxySlots {PyObjectSlot};
36+
37+
typedef struct {
38+
const char *name; /* The name of the method */
39+
JSNative call; /* The C function that implements it */
40+
uint16_t nargs; /* The argument count for the method */
41+
} JSMethodDef;
42+
43+
/**
44+
* @brief Convert jsid to a PyObject to be used as dict keys
45+
*/
46+
PyObject *idToKey(JSContext *cx, JS::HandleId id);
47+
48+
/**
49+
* @brief Convert Python dict key to jsid
50+
*/
51+
bool keyToId(PyObject *key, JS::MutableHandleId idp);
52+
53+
bool idToIndex(JSContext *cx, JS::HandleId id, Py_ssize_t *index);
54+
55+
#endif
Lines changed: 16 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,26 @@
11
/**
2-
* @file PyProxyHandler.hh
2+
* @file PyDictProxyHandler.hh
33
* @author Caleb Aikens ([email protected]) and Philippe Laporte ([email protected])
4-
* @brief Structs for creating JS proxy objects. Used by DictType for object coercion and by ListType for List coercion
5-
* @version 0.1
4+
* @brief Structs for creating JS proxy objects. Used by DictType for object coercion
65
* @date 2023-04-20
76
*
87
* Copyright (c) 2023-2024 Distributive Corp.
98
*
109
*/
1110

12-
#ifndef PythonMonkey_PyProxy_
13-
#define PythonMonkey_PyProxy_
11+
#ifndef PythonMonkey_PyDictProxy_
12+
#define PythonMonkey_PyDictProxy_
1413

15-
#include <jsapi.h>
16-
#include <js/Proxy.h>
14+
#include "PyBaseProxyHandler.hh"
1715

18-
#include <Python.h>
19-
20-
/**
21-
* @brief base class for PyProxyHandler and PyListProxyHandler
22-
*/
23-
struct PyBaseProxyHandler : public js::BaseProxyHandler {
24-
public:
25-
PyBaseProxyHandler(PyObject *pyObj, const void *family) : js::BaseProxyHandler(family), pyObject(pyObj) {};
26-
PyObject *pyObject; // @TODO (Caleb Aikens) Consider putting this in a private slot
27-
28-
bool getPrototypeIfOrdinary(JSContext *cx, JS::HandleObject proxy, bool *isOrdinary, JS::MutableHandleObject protop) const override final;
29-
bool preventExtensions(JSContext *cx, JS::HandleObject proxy, JS::ObjectOpResult &result) const override final;
30-
bool isExtensible(JSContext *cx, JS::HandleObject proxy, bool *extensible) const override final;
31-
};
32-
33-
enum ProxySlots {PyObjectSlot};
34-
35-
typedef struct {
36-
const char *name; /* The name of the method */
37-
JSNative call; /* The C function that implements it */
38-
uint16_t nargs; /* The argument count for the method */
39-
} JSMethodDef;
4016

4117
/**
4218
* @brief This struct is the ProxyHandler for JS Proxy Objects pythonmonkey creates to handle coercion from python dicts to JS Objects
4319
*
4420
*/
45-
struct PyProxyHandler : public PyBaseProxyHandler {
21+
struct PyDictProxyHandler : public PyBaseProxyHandler {
4622
public:
47-
PyProxyHandler(PyObject *pyObj) : PyBaseProxyHandler(pyObj, &family) {};
23+
PyDictProxyHandler(PyObject *pyObj) : PyBaseProxyHandler(pyObj, &family) {};
4824
static const char family;
4925

5026
/**
@@ -72,7 +48,6 @@ public:
7248
JS::ObjectOpResult &result) const override;
7349
/**
7450
* @brief [[HasProperty]]
75-
*
7651
* @param cx - pointer to JSContext
7752
* @param proxy - The proxy object who's propery we wish to check
7853
* @param id - key value of the property to check
@@ -82,20 +57,6 @@ public:
8257
*/
8358
bool has(JSContext *cx, JS::HandleObject proxy, JS::HandleId id,
8459
bool *bp) const override;
85-
/**
86-
* @brief [[Get]]
87-
*
88-
* @param cx pointer to JSContext
89-
* @param proxy - The proxy object who's property we wish to check
90-
* @param receiver @TODO (Caleb Aikens) read ECMAScript docs about this
91-
* @param id - Key of the property we wish to get
92-
* @param vp - out-paramter for the gotten property
93-
* @return true - call succeeded
94-
* @return false - call failed and an exception has been raised
95-
*/
96-
bool get(JSContext *cx, JS::HandleObject proxy,
97-
JS::HandleValue receiver, JS::HandleId id,
98-
JS::MutableHandleValue vp) const override;
9960
/**
10061
* @brief [[Set]]
10162
*
@@ -116,7 +77,10 @@ public:
11677
*
11778
* @param cx - pointer to JSContext
11879
* @param proxy - The proxy object who's keys we output
119-
* @param props - out-parameter of object IDs
80+
* @param props - out-parameter of object IDsoverride;
81+
82+
// @TODO (Caleb Aikens) The following are Spidermonkey-unique extensions, need to read into them more
83+
/**
12084
* @return true - call succeeded
12185
* @return false - call failed and an exception has been raised
12286
*/
@@ -140,7 +104,10 @@ public:
140104
* @brief @TODO (Caleb Aikens) read up on what this trap does exactly
141105
*
142106
* @param cx - pointer to JSContext
143-
* @param proxy - The proxy object who's keys we output
107+
* @param proxy - The proxy object who's keys we outputoverride;
108+
109+
// @TODO (Caleb Aikens) The following are Spidermonkey-unique extensions, need to read into them more
110+
/**
144111
* @param props - out-parameter of object IDs
145112
* @return true - call succeeded
146113
* @return false - call failed and an exception has been raised
@@ -169,47 +136,4 @@ public:
169136
bool getBuiltinClass(JSContext *cx, JS::HandleObject proxy, js::ESClass *cls) const override;
170137
};
171138

172-
/**
173-
* @brief This struct is the ProxyHandler for JS Proxy Objects pythonmonkey creates
174-
* to handle coercion from python lists to JS Array objects
175-
*/
176-
struct PyListProxyHandler : public PyBaseProxyHandler {
177-
public:
178-
PyListProxyHandler(PyObject *pyObj) : PyBaseProxyHandler(pyObj, &family) {};
179-
static const char family;
180-
181-
/**
182-
* @brief Handles python object reference count when JS Proxy object is finalized
183-
*
184-
* @param gcx pointer to JS::GCContext
185-
* @param proxy the proxy object being finalized
186-
*/
187-
void finalize(JS::GCContext *gcx, JSObject *proxy) const override;
188-
189-
bool getOwnPropertyDescriptor(
190-
JSContext *cx, JS::HandleObject proxy, JS::HandleId id,
191-
JS::MutableHandle<mozilla::Maybe<JS::PropertyDescriptor>> desc
192-
) const override;
193-
194-
bool defineProperty(
195-
JSContext *cx, JS::HandleObject proxy, JS::HandleId id,
196-
JS::Handle<JS::PropertyDescriptor> desc, JS::ObjectOpResult &result
197-
) const override;
198-
199-
bool ownPropertyKeys(JSContext *cx, JS::HandleObject proxy, JS::MutableHandleIdVector props) const override;
200-
bool delete_(JSContext *cx, JS::HandleObject proxy, JS::HandleId id, JS::ObjectOpResult &result) const override;
201-
bool isArray(JSContext *cx, JS::HandleObject proxy, JS::IsArrayAnswer *answer) const override;
202-
bool getBuiltinClass(JSContext *cx, JS::HandleObject proxy, js::ESClass *cls) const override;
203-
};
204-
205-
/**
206-
* @brief Convert jsid to a PyObject to be used as dict keys
207-
*/
208-
PyObject *idToKey(JSContext *cx, JS::HandleId id);
209-
210-
/**
211-
* @brief Convert Python dict key to jsid
212-
*/
213-
bool keyToId(PyObject *key, JS::MutableHandleId idp);
214-
215-
#endif
139+
#endif

include/PyListProxyHandler.hh

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/**
2+
* @file PyListProxyHandler.hh
3+
* @author Philippe Laporte ([email protected])
4+
* @brief Structs for creating JS proxy objects. Used by ListType for List coercion
5+
* @version 0.1
6+
* @date 2023-12-01
7+
*
8+
* Copyright (c) 2023-2024 Distributive Corp.
9+
*
10+
*/
11+
12+
#ifndef PythonMonkey_PyListProxy_
13+
#define PythonMonkey_PyListProxy_
14+
15+
#include "PyBaseProxyHandler.hh"
16+
17+
18+
/**
19+
* @brief This struct is the ProxyHandler for JS Proxy Objects pythonmonkey creates
20+
* to handle coercion from python lists to JS Array objects
21+
*/
22+
struct PyListProxyHandler : public PyBaseProxyHandler {
23+
public:
24+
PyListProxyHandler(PyObject *pyObj) : PyBaseProxyHandler(pyObj, &family) {};
25+
static const char family;
26+
27+
/**
28+
* @brief Handles python object reference count when JS Proxy object is finalized
29+
*
30+
* @param gcx pointer to JS::GCContext
31+
* @param proxy the proxy object being finalized
32+
*/
33+
void finalize(JS::GCContext *gcx, JSObject *proxy) const override;
34+
35+
bool getOwnPropertyDescriptor(
36+
JSContext *cx, JS::HandleObject proxy, JS::HandleId id,
37+
JS::MutableHandle<mozilla::Maybe<JS::PropertyDescriptor>> desc
38+
) const override;
39+
40+
bool defineProperty(
41+
JSContext *cx, JS::HandleObject proxy, JS::HandleId id,
42+
JS::Handle<JS::PropertyDescriptor> desc, JS::ObjectOpResult &result
43+
) const override;
44+
45+
bool ownPropertyKeys(JSContext *cx, JS::HandleObject proxy, JS::MutableHandleIdVector props) const override;
46+
bool delete_(JSContext *cx, JS::HandleObject proxy, JS::HandleId id, JS::ObjectOpResult &result) const override;
47+
bool isArray(JSContext *cx, JS::HandleObject proxy, JS::IsArrayAnswer *answer) const override;
48+
bool getBuiltinClass(JSContext *cx, JS::HandleObject proxy, js::ESClass *cls) const override;
49+
};
50+
51+
#endif

setup.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,9 @@ $POETRY_BIN self add 'poetry-dynamic-versioning[plugin]'
3939
echo "Done installing dependencies"
4040

4141
echo "Downloading spidermonkey source code"
42-
wget -c -q https://ftp.mozilla.org/pub/firefox/releases/115.1.0esr/source/firefox-115.1.0esr.source.tar.xz
42+
wget -c -q https://ftp.mozilla.org/pub/firefox/releases/115.7.0esr/source/firefox-115.7.0esr.source.tar.xz
4343
mkdir -p firefox-source
44-
tar xf firefox-115.1.0esr.source.tar.xz -C firefox-source --strip-components=1 # strip the root folder
44+
tar xf firefox-115.7.0esr.source.tar.xz -C firefox-source --strip-components=1 # strip the root folder
4545
echo "Done downloading spidermonkey source code"
4646

4747
echo "Building spidermonkey"

src/JSArrayProxy.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
#include "include/modules/pythonmonkey/pythonmonkey.hh"
1818
#include "include/jsTypeFactory.hh"
1919
#include "include/pyTypeFactory.hh"
20-
#include "include/PyProxyHandler.hh"
20+
#include "include/PyBaseProxyHandler.hh"
2121

2222
#include <jsapi.h>
2323
#include <jsfriendapi.h>

src/JSObjectItemsProxy.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
#include "include/modules/pythonmonkey/pythonmonkey.hh"
1919
#include "include/jsTypeFactory.hh"
2020
#include "include/pyTypeFactory.hh"
21-
#include "include/PyProxyHandler.hh"
21+
#include "include/PyBaseProxyHandler.hh"
2222

2323
#include <jsapi.h>
2424
#include <jsfriendapi.h>

src/JSObjectIterProxy.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
#include "include/pyTypeFactory.hh"
1919

20-
#include "include/PyProxyHandler.hh"
20+
#include "include/PyDictProxyHandler.hh"
2121

2222
#include <jsapi.h>
2323

src/JSObjectKeysProxy.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
#include "include/modules/pythonmonkey/pythonmonkey.hh"
1919
#include "include/jsTypeFactory.hh"
2020
#include "include/pyTypeFactory.hh"
21-
#include "include/PyProxyHandler.hh"
21+
#include "include/PyDictProxyHandler.hh"
2222

2323
#include <jsapi.h>
2424
#include <jsfriendapi.h>

0 commit comments

Comments
 (0)