Skip to content

Commit adf0ab2

Browse files
committed
implement JS object to python dict coercion
1 parent e81eb5c commit adf0ab2

5 files changed

Lines changed: 160 additions & 26 deletions

File tree

include/DictType.hh

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
#include "PyType.hh"
1616
#include "TypeEnum.hh"
1717

18+
#include <jsapi.h>
19+
1820
#include <Python.h>
1921

2022
#include <iostream>
@@ -26,7 +28,28 @@
2628
*/
2729
struct DictType : public PyType {
2830
public:
31+
DictType();
2932
DictType(PyObject *object);
33+
34+
/**
35+
* @brief Construct a new DictType object from a JSObject.
36+
*
37+
* @param cx - pointer to the JSContext
38+
* @param global - pointer to the global JSObject
39+
* @param jsObject - pointer to the JSObject to be coerced
40+
*/
41+
DictType(JSContext *cx, JS::Handle<JSObject *> global, JS::Handle<JS::Value> jsObject);
42+
43+
/**
44+
* @brief Construct a new DictType object from a JSObject, providing a map of JSObjects that have already been coerced to python dicts.
45+
*
46+
* @param cx - pointer to the JSContext
47+
* @param global - pointer to the global JSObject
48+
* @param jsObject - pointer to the JSObject to be coerced
49+
* @param subObjectsMap - map of JSObjects that have been coerced to PyObjects
50+
*/
51+
DictType(JSContext *cx, JS::Handle<JSObject *> global, JS::Handle<JS::Value> jsObject, std::unordered_map<const JS::Value *, PyObject *> &subObjectsMap);
52+
3053
const TYPE returnType = TYPE::DICT;
3154
/**
3255
* @brief The 'set' method for a python dictionary. Sets the approprite 'key' in the dictionary with the appropriate 'value'
@@ -48,6 +71,17 @@ public:
4871

4972
protected:
5073
virtual void print(std::ostream &os) const override;
74+
75+
private:
76+
/**
77+
* @brief Helper function for DictType constructor that keeps track of reference cycles
78+
*
79+
* @param cx - pointer to the JSContext
80+
* @param global - pointer to the global JSObject
81+
* @param jsObject - pointer to the JSObject to be coerced
82+
* @param subObjectsMap - map of JSObjects that have been coerced to PyObjects
83+
*/
84+
void init(JSContext *cx, JS::Handle<JSObject *> global, JS::Handle<JS::Value> jsObject, std::unordered_map<const JS::Value *, PyObject *> &subObject);
5185
};
5286

5387
#endif

include/modules/pythonmonkey/pythonmonkey.hh

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,14 @@ static void cleanup();
4343
*/
4444
void memoizePyTypeAndGCThing(PyType *pyType, JS::Handle<JS::Value> GCThing);
4545

46+
/**
47+
* @brief This function checks if a given GCThing is memoized, and returns the related python object if so, or NULL otherwise
48+
*
49+
* @param GCThing - The GCThing to be checked
50+
* @return PyType* - Pointer to related python object wrapped in a PyType, or NULL if GCThing is not memoized
51+
*/
52+
PyType *checkJSMemo(JS::Handle<JS::Value> GCThing);
53+
4654
/**
4755
* @brief Callback function passed to JS_SetGCCallback to handle PythonMonkey shared memory
4856
*

src/DictType.cc

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,98 @@
11
#include "include/DictType.hh"
22

3+
#include "include/modules/pythonmonkey/pythonmonkey.hh"
34
#include "include/PyType.hh"
45
#include "include/pyTypeFactory.hh"
56
#include "include/utilities.hh"
67

8+
#include <jsfriendapi.h>
9+
#include <jsapi.h>
10+
#include <js/Equality.h>
11+
712
#include <Python.h>
813

914
#include <string>
1015
#include <iostream>
1116

17+
typedef std::unordered_map<const JS::Value *, PyObject *>::iterator subObjectIterator;
18+
19+
DictType::DictType() {
20+
this->pyObject = PyDict_New();
21+
}
22+
1223
DictType::DictType(PyObject *object) : PyType(object) {}
1324

25+
DictType::DictType(JSContext *cx, JS::Handle<JSObject *> global, JS::Handle<JS::Value> jsObject) {
26+
std::unordered_map<const JS::Value *, PyObject *> subObjectMap;
27+
init(cx, global, jsObject, subObjectMap);
28+
}
29+
30+
DictType::DictType(JSContext *cx, JS::Handle<JSObject *> global, JS::Handle<JS::Value> jsObject, std::unordered_map<const JS::Value *, PyObject *> &subObjectsMap) {
31+
init(cx, global, jsObject, subObjectsMap);
32+
}
33+
34+
void DictType::init(JSContext *cx, JS::Handle<JSObject *> global, JS::Handle<JS::Value> jsObject, std::unordered_map<const JS::Value *, PyObject *> &subObjectsMap) {
35+
for (auto it: subObjectsMap) {
36+
bool *isEqual;
37+
JS::RootedValue rval(cx, *it.first);
38+
if (JS::StrictlyEqual(cx, rval, jsObject, isEqual) && *isEqual) { // if object has already been coerced, need to avoid reference cycle
39+
this->pyObject = it.second;
40+
Py_INCREF(this->pyObject);
41+
return;
42+
}
43+
}
44+
45+
this->pyObject = PyDict_New();
46+
subObjectsMap.insert({{jsObject.address(), this->pyObject}});
47+
48+
JS::RootedObject globalObject(cx, global);
49+
JS::Rooted<JSObject *> jsObjectObj(cx);
50+
JS_ValueToObject(cx, jsObject, &jsObjectObj);
51+
/* @TODO (Caleb Aikens)
52+
Need to consider consequences of key types
53+
Javascript keys can be Strings or Symbols (do we need to handle Symbol coercion?)
54+
Python keys can be any immutable type
55+
*/
56+
JS::RootedIdVector props(cx);
57+
if (!js::GetPropertyKeys(cx, jsObjectObj, JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS, &props)) {
58+
Py_DECREF(this->pyObject);
59+
this->pyObject = NULL;
60+
return;
61+
}
62+
63+
for (size_t i = 0; i < props.length(); i++) {
64+
JS::HandleId id = props[i];
65+
JS::RootedValue *value = new JS::RootedValue(cx);
66+
if (id.isString()) { // @TODO (Caleb Aikens) handle non-String keys (Symbols, Ints(?) and Magic)
67+
if (!JS_GetPropertyById(cx, jsObjectObj, id, value)) {
68+
Py_DECREF(this->pyObject);
69+
this->pyObject = NULL;
70+
return;
71+
}
72+
JS::RootedValue keyValue(cx);
73+
keyValue.setString(id.toString());
74+
PyType *pyKey = checkJSMemo(keyValue);
75+
PyType *pyVal = checkJSMemo(*value);
76+
if (!pyKey) {
77+
pyKey = pyTypeFactory(cx, &globalObject, &keyValue);
78+
}
79+
if (!pyVal && value->isObject()) {
80+
JS::Rooted<JSObject *> valueObj(cx);
81+
JS_ValueToObject(cx, *value, &valueObj);
82+
js::ESClass cls;
83+
JS::GetBuiltinClass(cx, valueObj, &cls);
84+
if (cls == js::ESClass::Object) { // generic non-boxing object, need to worry about reference cycles
85+
pyVal = new DictType(cx, global, *value, subObjectsMap);
86+
}
87+
}
88+
if (!pyVal) {
89+
pyVal = pyTypeFactory(cx, &globalObject, value);
90+
}
91+
PyDict_SetItem(this->pyObject, pyKey->getPyObject(), pyVal->getPyObject());
92+
}
93+
}
94+
}
95+
1496
void DictType::set(PyType *key, PyType *value) {
1597
PyDict_SetItem(this->pyObject, key->getPyObject(), value->getPyObject());
1698
}

src/modules/pythonmonkey/pythonmonkey.cc

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,20 @@ void memoizePyTypeAndGCThing(PyType *pyType, JS::Handle<JS::Value> GCThing) {
6767
}
6868
}
6969

70+
PyType *checkJSMemo(JS::Handle<JS::Value> GCThing) {
71+
JS::PersistentRooted<JS::Value> *RootedGCThing = new JS::PersistentRooted<JS::Value>(cx, GCThing);
72+
PyToGCIterator pyIt = PyTypeToGCThing.begin();
73+
while (pyIt != PyTypeToGCThing.end()) {
74+
for (JS::PersistentRooted<JS::Value> *rval: pyIt->second) {
75+
if (rval->address() == RootedGCThing->address()) {
76+
return pyIt->first;
77+
}
78+
}
79+
pyIt++;
80+
}
81+
return NULL;
82+
}
83+
7084
void handleSharedPythonMonkeyMemory(JSContext *cx, JSGCStatus status, JS::GCReason reason, void *data) {
7185
if (status == JSGCStatus::JSGC_BEGIN) {
7286
PyToGCIterator pyIt = PyTypeToGCThing.begin();

src/pyTypeFactory.cc

Lines changed: 22 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -54,28 +54,28 @@ PyType *pyTypeFactory(PyObject *object) {
5454
}
5555

5656
PyType *pyTypeFactory(JSContext *cx, JS::Rooted<JSObject *> *global, JS::Rooted<JS::Value> *rval) {
57-
PyType *returnValue = NULL;
5857
if (rval->isUndefined()) {
59-
returnValue = new NoneType();
58+
return new NoneType();
6059
}
6160
else if (rval->isNull()) {
62-
returnValue = new NullType();
61+
return new NullType();
6362
}
6463
else if (rval->isBoolean()) {
65-
returnValue = new BoolType(rval->toBoolean());
64+
return new BoolType(rval->toBoolean());
6665
}
6766
else if (rval->isNumber()) {
68-
returnValue = new FloatType(rval->toNumber());
67+
return new FloatType(rval->toNumber());
6968
}
7069
else if (rval->isString()) {
71-
returnValue = new StrType(cx, rval->toString());
72-
memoizePyTypeAndGCThing(returnValue, *rval); // TODO (Caleb Aikens) consider putting this in the StrType constructor
70+
StrType *s = new StrType(cx, rval->toString());
71+
memoizePyTypeAndGCThing(s, *rval); // TODO (Caleb Aikens) consider putting this in the StrType constructor
72+
return s;
7373
}
7474
else if (rval->isSymbol()) {
7575
printf("symbol type is not handled by PythonMonkey yet");
7676
}
7777
else if (rval->isBigInt()) {
78-
returnValue = new IntType(cx, rval->toBigInt());
78+
return new IntType(cx, rval->toBigInt());
7979
}
8080
else if (rval->isObject()) {
8181
JS::Rooted<JSObject *> obj(cx);
@@ -88,40 +88,38 @@ PyType *pyTypeFactory(JSContext *cx, JS::Rooted<JSObject *> *global, JS::Rooted<
8888
// TODO (Caleb Aikens): refactor using recursive call to `pyTypeFactory`
8989
JS::RootedValue unboxed(cx);
9090
js::Unbox(cx, obj, &unboxed);
91-
returnValue = new BoolType(unboxed.toBoolean());
92-
break;
91+
return new BoolType(unboxed.toBoolean());
9392
}
9493
case js::ESClass::Date: {
95-
JS::RootedValue unboxed(cx);
96-
js::Unbox(cx, obj, &unboxed);
97-
returnValue = new DateType(cx, obj);
98-
break;
94+
return new DateType(cx, obj);
9995
}
10096
case js::ESClass::Function: {
10197
PyObject *JSCxGlobalFuncTuple = Py_BuildValue("(lll)", (uint64_t)cx, (uint64_t)global, (uint64_t)rval);
10298
PyObject *pyFunc = PyCFunction_New(&callJSFuncDef, JSCxGlobalFuncTuple);
103-
returnValue = new FuncType(pyFunc);
104-
memoizePyTypeAndGCThing(returnValue, *rval); // TODO (Caleb Aikens) consider putting this in the FuncType constructor
105-
break;
99+
FuncType *f = new FuncType(pyFunc);
100+
memoizePyTypeAndGCThing(f, *rval); // TODO (Caleb Aikens) consider putting this in the FuncType constructor
101+
return f;
106102
}
107103
case js::ESClass::Number: {
108104
JS::RootedValue unboxed(cx);
109105
js::Unbox(cx, obj, &unboxed);
110-
returnValue = new FloatType(unboxed.toNumber());
111-
break;
106+
return new FloatType(unboxed.toNumber());
112107
}
113108
case js::ESClass::BigInt: {
114109
JS::RootedValue unboxed(cx);
115110
js::Unbox(cx, obj, &unboxed);
116-
returnValue = new IntType(cx, unboxed.toBigInt());
117-
break;
111+
return new IntType(cx, unboxed.toBigInt());
118112
}
119113
case js::ESClass::String: {
120114
JS::RootedValue unboxed(cx);
121115
js::Unbox(cx, obj, &unboxed);
122-
returnValue = new StrType(cx, unboxed.toString());
123-
memoizePyTypeAndGCThing(returnValue, *rval); // TODO (Caleb Aikens) consider putting this in the StrType constructor
124-
break;
116+
StrType *s = new StrType(cx, unboxed.toString());
117+
memoizePyTypeAndGCThing(s, *rval); // TODO (Caleb Aikens) consider putting this in the StrType constructor
118+
return s;
119+
}
120+
case js::ESClass::Object: {
121+
// this is a generic non-boxing object
122+
return new DictType(cx, *global, *rval);
125123
}
126124
default: {
127125
printf("objects of this type are not handled by PythonMonkey yet");
@@ -131,8 +129,6 @@ PyType *pyTypeFactory(JSContext *cx, JS::Rooted<JSObject *> *global, JS::Rooted<
131129
else if (rval->isMagic()) {
132130
printf("magic type is not handled by PythonMonkey yet");
133131
}
134-
135-
return returnValue;
136132
}
137133

138134
static PyObject *callJSFunc(PyObject *JSCxGlobalFuncTuple, PyObject *args) {

0 commit comments

Comments
 (0)