Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions include/DictType.hh
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
#include "PyType.hh"
#include "TypeEnum.hh"

#include <jsapi.h>

#include <Python.h>

#include <iostream>
Expand All @@ -26,7 +28,28 @@
*/
struct DictType : public PyType {
public:
DictType();
DictType(PyObject *object);

/**
* @brief Construct a new DictType object from a JSObject.
*
* @param cx - pointer to the JSContext
* @param global - pointer to the global JSObject
* @param jsObject - pointer to the JSObject to be coerced
*/
DictType(JSContext *cx, JS::Handle<JSObject *> global, JS::Handle<JS::Value> jsObject);

/**
* @brief Construct a new DictType object from a JSObject, providing a map of JSObjects that have already been coerced to python dicts.
*
* @param cx - pointer to the JSContext
* @param global - pointer to the global JSObject
* @param jsObject - pointer to the JSObject to be coerced
* @param subObjectsMap - map of JSObjects that have been coerced to PyObjects
*/
DictType(JSContext *cx, JS::Handle<JSObject *> global, JS::Handle<JS::Value> jsObject, std::unordered_map<const JS::Value *, PyObject *> &subObjectsMap);

const TYPE returnType = TYPE::DICT;
/**
* @brief The 'set' method for a python dictionary. Sets the approprite 'key' in the dictionary with the appropriate 'value'
Expand All @@ -48,6 +71,17 @@ public:

protected:
virtual void print(std::ostream &os) const override;

private:
/**
* @brief Helper function for DictType constructor that keeps track of reference cycles
*
* @param cx - pointer to the JSContext
* @param global - pointer to the global JSObject
* @param jsObject - pointer to the JSObject to be coerced
* @param subObjectsMap - map of JSObjects that have been coerced to PyObjects
*/
void init(JSContext *cx, JS::Handle<JSObject *> global, JS::Handle<JS::Value> jsObject, std::unordered_map<const JS::Value *, PyObject *> &subObject);
};

#endif
8 changes: 8 additions & 0 deletions include/modules/pythonmonkey/pythonmonkey.hh
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,14 @@ static void cleanup();
*/
void memoizePyTypeAndGCThing(PyType *pyType, JS::Handle<JS::Value> GCThing);

/**
* @brief This function checks if a given GCThing is memoized, and returns the related python object if so, or NULL otherwise
*
* @param GCThing - The GCThing to be checked
* @return PyType* - Pointer to related python object wrapped in a PyType, or NULL if GCThing is not memoized
*/
PyType *checkJSMemo(JS::Handle<JS::Value> GCThing);

/**
* @brief Callback function passed to JS_SetGCCallback to handle PythonMonkey shared memory
*
Expand Down
82 changes: 82 additions & 0 deletions src/DictType.cc
Original file line number Diff line number Diff line change
@@ -1,16 +1,98 @@
#include "include/DictType.hh"

#include "include/modules/pythonmonkey/pythonmonkey.hh"
#include "include/PyType.hh"
#include "include/pyTypeFactory.hh"
#include "include/utilities.hh"

#include <jsfriendapi.h>
#include <jsapi.h>
#include <js/Equality.h>

#include <Python.h>

#include <string>
#include <iostream>

typedef std::unordered_map<const JS::Value *, PyObject *>::iterator subObjectIterator;

DictType::DictType() {
this->pyObject = PyDict_New();
}

DictType::DictType(PyObject *object) : PyType(object) {}

DictType::DictType(JSContext *cx, JS::Handle<JSObject *> global, JS::Handle<JS::Value> jsObject) {
std::unordered_map<const JS::Value *, PyObject *> subObjectMap;
init(cx, global, jsObject, subObjectMap);
}

DictType::DictType(JSContext *cx, JS::Handle<JSObject *> global, JS::Handle<JS::Value> jsObject, std::unordered_map<const JS::Value *, PyObject *> &subObjectsMap) {
init(cx, global, jsObject, subObjectsMap);
}

void DictType::init(JSContext *cx, JS::Handle<JSObject *> global, JS::Handle<JS::Value> jsObject, std::unordered_map<const JS::Value *, PyObject *> &subObjectsMap) {
for (auto it: subObjectsMap) {
bool *isEqual;
JS::RootedValue rval(cx, *it.first);
if (JS::StrictlyEqual(cx, rval, jsObject, isEqual) && *isEqual) { // if object has already been coerced, need to avoid reference cycle
this->pyObject = it.second;
Py_INCREF(this->pyObject);
return;
}
}

this->pyObject = PyDict_New();
subObjectsMap.insert({{jsObject.address(), this->pyObject}});

JS::RootedObject globalObject(cx, global);
JS::Rooted<JSObject *> jsObjectObj(cx);
JS_ValueToObject(cx, jsObject, &jsObjectObj);
/* @TODO (Caleb Aikens)
Need to consider consequences of key types
Javascript keys can be Strings or Symbols (do we need to handle Symbol coercion?)
Python keys can be any immutable type
*/
JS::RootedIdVector props(cx);
if (!js::GetPropertyKeys(cx, jsObjectObj, JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS, &props)) {
Py_DECREF(this->pyObject);
this->pyObject = NULL;
return;
}

for (size_t i = 0; i < props.length(); i++) {
JS::HandleId id = props[i];
JS::RootedValue *value = new JS::RootedValue(cx);
if (id.isString()) { // @TODO (Caleb Aikens) handle non-String keys (Symbols, Ints(?) and Magic)
if (!JS_GetPropertyById(cx, jsObjectObj, id, value)) {
Py_DECREF(this->pyObject);
this->pyObject = NULL;
return;
}
JS::RootedValue keyValue(cx);
keyValue.setString(id.toString());
PyType *pyKey = checkJSMemo(keyValue);
PyType *pyVal = checkJSMemo(*value);
if (!pyKey) {
pyKey = pyTypeFactory(cx, &globalObject, &keyValue);
}
if (!pyVal && value->isObject()) {
JS::Rooted<JSObject *> valueObj(cx);
JS_ValueToObject(cx, *value, &valueObj);
js::ESClass cls;
JS::GetBuiltinClass(cx, valueObj, &cls);
if (cls == js::ESClass::Object) { // generic non-boxing object, need to worry about reference cycles
pyVal = new DictType(cx, global, *value, subObjectsMap);
}
}
if (!pyVal) {
pyVal = pyTypeFactory(cx, &globalObject, value);
}
PyDict_SetItem(this->pyObject, pyKey->getPyObject(), pyVal->getPyObject());
}
}
}

void DictType::set(PyType *key, PyType *value) {
PyDict_SetItem(this->pyObject, key->getPyObject(), value->getPyObject());
}
Expand Down
14 changes: 14 additions & 0 deletions src/modules/pythonmonkey/pythonmonkey.cc
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,20 @@ void memoizePyTypeAndGCThing(PyType *pyType, JS::Handle<JS::Value> GCThing) {
}
}

PyType *checkJSMemo(JS::Handle<JS::Value> GCThing) {
JS::PersistentRooted<JS::Value> *RootedGCThing = new JS::PersistentRooted<JS::Value>(cx, GCThing);
PyToGCIterator pyIt = PyTypeToGCThing.begin();
while (pyIt != PyTypeToGCThing.end()) {
for (JS::PersistentRooted<JS::Value> *rval: pyIt->second) {
if (rval->address() == RootedGCThing->address()) {
return pyIt->first;
}
}
pyIt++;
}
return NULL;
}

void handleSharedPythonMonkeyMemory(JSContext *cx, JSGCStatus status, JS::GCReason reason, void *data) {
if (status == JSGCStatus::JSGC_BEGIN) {
PyToGCIterator pyIt = PyTypeToGCThing.begin();
Expand Down
48 changes: 22 additions & 26 deletions src/pyTypeFactory.cc
Original file line number Diff line number Diff line change
Expand Up @@ -54,28 +54,28 @@ PyType *pyTypeFactory(PyObject *object) {
}

PyType *pyTypeFactory(JSContext *cx, JS::Rooted<JSObject *> *global, JS::Rooted<JS::Value> *rval) {
PyType *returnValue = NULL;
if (rval->isUndefined()) {
returnValue = new NoneType();
return new NoneType();
}
else if (rval->isNull()) {
returnValue = new NullType();
return new NullType();
}
else if (rval->isBoolean()) {
returnValue = new BoolType(rval->toBoolean());
return new BoolType(rval->toBoolean());
}
else if (rval->isNumber()) {
returnValue = new FloatType(rval->toNumber());
return new FloatType(rval->toNumber());
}
else if (rval->isString()) {
returnValue = new StrType(cx, rval->toString());
memoizePyTypeAndGCThing(returnValue, *rval); // TODO (Caleb Aikens) consider putting this in the StrType constructor
StrType *s = new StrType(cx, rval->toString());
memoizePyTypeAndGCThing(s, *rval); // TODO (Caleb Aikens) consider putting this in the StrType constructor
return s;
}
else if (rval->isSymbol()) {
printf("symbol type is not handled by PythonMonkey yet");
}
else if (rval->isBigInt()) {
returnValue = new IntType(cx, rval->toBigInt());
return new IntType(cx, rval->toBigInt());
}
else if (rval->isObject()) {
JS::Rooted<JSObject *> obj(cx);
Expand All @@ -88,40 +88,38 @@ PyType *pyTypeFactory(JSContext *cx, JS::Rooted<JSObject *> *global, JS::Rooted<
// TODO (Caleb Aikens): refactor using recursive call to `pyTypeFactory`
JS::RootedValue unboxed(cx);
js::Unbox(cx, obj, &unboxed);
returnValue = new BoolType(unboxed.toBoolean());
break;
return new BoolType(unboxed.toBoolean());
}
case js::ESClass::Date: {
JS::RootedValue unboxed(cx);
js::Unbox(cx, obj, &unboxed);
returnValue = new DateType(cx, obj);
break;
return new DateType(cx, obj);
}
case js::ESClass::Function: {
PyObject *JSCxGlobalFuncTuple = Py_BuildValue("(lll)", (uint64_t)cx, (uint64_t)global, (uint64_t)rval);
PyObject *pyFunc = PyCFunction_New(&callJSFuncDef, JSCxGlobalFuncTuple);
returnValue = new FuncType(pyFunc);
memoizePyTypeAndGCThing(returnValue, *rval); // TODO (Caleb Aikens) consider putting this in the FuncType constructor
break;
FuncType *f = new FuncType(pyFunc);
memoizePyTypeAndGCThing(f, *rval); // TODO (Caleb Aikens) consider putting this in the FuncType constructor
return f;
}
case js::ESClass::Number: {
JS::RootedValue unboxed(cx);
js::Unbox(cx, obj, &unboxed);
returnValue = new FloatType(unboxed.toNumber());
break;
return new FloatType(unboxed.toNumber());
}
case js::ESClass::BigInt: {
JS::RootedValue unboxed(cx);
js::Unbox(cx, obj, &unboxed);
returnValue = new IntType(cx, unboxed.toBigInt());
break;
return new IntType(cx, unboxed.toBigInt());
}
case js::ESClass::String: {
JS::RootedValue unboxed(cx);
js::Unbox(cx, obj, &unboxed);
returnValue = new StrType(cx, unboxed.toString());
memoizePyTypeAndGCThing(returnValue, *rval); // TODO (Caleb Aikens) consider putting this in the StrType constructor
break;
StrType *s = new StrType(cx, unboxed.toString());
memoizePyTypeAndGCThing(s, *rval); // TODO (Caleb Aikens) consider putting this in the StrType constructor
return s;
}
case js::ESClass::Object: {
// this is a generic non-boxing object
return new DictType(cx, *global, *rval);
}
default: {
printf("objects of this type are not handled by PythonMonkey yet");
Expand All @@ -131,8 +129,6 @@ PyType *pyTypeFactory(JSContext *cx, JS::Rooted<JSObject *> *global, JS::Rooted<
else if (rval->isMagic()) {
printf("magic type is not handled by PythonMonkey yet");
}

return returnValue;
}

static PyObject *callJSFunc(PyObject *JSCxGlobalFuncTuple, PyObject *args) {
Expand Down
20 changes: 19 additions & 1 deletion tests/python/test_pythonmonkey_eval.py
Original file line number Diff line number Diff line change
Expand Up @@ -576,4 +576,22 @@ def concatenate(a, b):
for j in range(length2):
codepoint = random.randint(0x0000, 0xFFFF)
string2 += chr(codepoint)
assert caller(concatenate, string1, string2) == string1 + string2
assert caller(concatenate, string1, string2) == string1 + string2

def test_eval_objects():
pyObj = pm.eval("Object({a:1.0})")
assert pyObj == {'a':1.0}

def test_eval_objects_subobjects():
pyObj = pm.eval("Object({a:1.0, b:{c:2.0}})")

assert pyObj.a == 1.0
assert pyObj.b == {'c': 2.0}
assert pyObj.b.c == 2.0

def test_eval_objects_cycle():
pyObj = pm.eval("Object({a:1.0, b:2.0, recursive: function() { this.recursive = this; return this; }}.recursive())")

assert pyObj.a == 1.0
assert pyObj.b == 2.0
assert pyObj.recursive == pyObj