Commit e93cc2b1 authored by Michael Droettboom's avatar Michael Droettboom Committed by GitHub

Merge pull request #61 from iodide-project/more-proxy

Add a lot more jsproxy functionality
parents 4331456f 6b8f5e27
......@@ -42,23 +42,55 @@ Any of the types not listed above are shared between languages using proxies
that allow methods and some operators to be called on the object from the other
language.
### Javascript from Python
When passing a Javascript object to Python, an extension type is used to
delegate Python operations to the Javascript side. The following operations are
currently supported. (More should be possible in the future -- work in ongoing
to make this more complete):
| Python | Javascript |
|----------------|----------------|
| `repr(x)` | `x.toString()` |
| `x.foo` | `x.foo` |
| `x.foo = bar` | `x.foo = bar` |
| `x(...)` | `x(...)` |
| `x.foo(...)` | `x.foo(...)` |
| `X.new(...)` | `new X(...)` |
| `len(x)` | `x.length` |
| `x[foo]` | `x[foo]` |
| `x[foo] = bar` | `x[foo] = bar` |
| Python | Javascript |
|----------------|-----------------|
| `repr(x)` | `x.toString()` |
| `x.foo` | `x.foo` |
| `x.foo = bar` | `x.foo = bar` |
| `del x.foo` | `delete x.foo` |
| `x(...)` | `x(...)` |
| `x.foo(...)` | `x.foo(...)` |
| `X.new(...)` | `new X(...)` |
| `len(x)` | `x.length` |
| `x[foo]` | `x[foo]` |
| `x[foo] = bar` | `x[foo] = bar` |
| `del x[foo]` | `delete x[foo]` |
| `x == y` | `x == y` |
| `x.typeof` | `typeof x` |
One important difference between Python objects and Javascript objects is that
if you access a missing member in Python, an exception is raised. In Javascript,
it returns `undefined`. Since we can't make any assumptions about whether the
Javascript member is missing or simply set to `undefined`, Python mirrors the
Javascript behavior. For example:
```javascript
// Javascript
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
}
point = new Point(42, 43))
```
```python
# python
from js import point
assert point.y == 43
del point.y
assert point.y is None
```
### Python from Javascript
When passing a Python object to Javascript, the Javascript [Proxy
API](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy)
......
......@@ -100,6 +100,12 @@ EM_JS(void, hiwire_set_member_string, (int idobj, int ptrkey, int idval), {
jsobj[jskey] = jsval;
});
EM_JS(void, hiwire_delete_member_string, (int idobj, int ptrkey), {
var jsobj = Module.hiwire_get_value(idobj);
var jskey = UTF8ToString(ptrkey);
delete jsobj[jskey];
});
EM_JS(int, hiwire_get_member_int, (int idobj, int idx), {
var jsobj = Module.hiwire_get_value(idobj);
return Module.hiwire_new_value(jsobj[idx]);
......@@ -109,6 +115,25 @@ EM_JS(void, hiwire_set_member_int, (int idobj, int idx, int idval), {
Module.hiwire_get_value(idobj)[idx] = Module.hiwire_get_value(idval);
});
EM_JS(int, hiwire_get_member_obj, (int idobj, int ididx), {
var jsobj = Module.hiwire_get_value(idobj);
var jsidx = Module.hiwire_get_value(ididx);
return Module.hiwire_new_value(jsobj[jsidx]);
});
EM_JS(void, hiwire_set_member_obj, (int idobj, int ididx, int idval), {
var jsobj = Module.hiwire_get_value(idobj);
var jsidx = Module.hiwire_get_value(ididx);
var jsval = Module.hiwire_get_value(idval);
jsobj[jsidx] = jsval;
});
EM_JS(void, hiwire_delete_member_obj, (int idobj, int ididx), {
var jsobj = Module.hiwire_get_value(idobj);
var jsidx = Module.hiwire_get_value(ididx);
delete jsobj[jsidx];
});
EM_JS(void, hiwire_call, (int idfunc, int idargs), {
var jsfunc = Module.hiwire_get_value(idfunc);
var jsargs = Module.hiwire_get_value(idargs);
......@@ -146,3 +171,33 @@ EM_JS(int, hiwire_is_function, (int idobj), {
EM_JS(int, hiwire_to_string, (int idobj), {
return Module.hiwire_new_value(Module.hiwire_get_value(idobj).toString());
});
EM_JS(int, hiwire_typeof, (int idobj), {
return Module.hiwire_new_value(typeof Module.hiwire_get_value(idobj));
});
#define MAKE_OPERATOR(name, op) \
EM_JS(int, hiwire_##name, (int ida, int idb), { \
return (Module.hiwire_get_value(ida) op Module.hiwire_get_value(idb)) ? 1 : 0; \
});
MAKE_OPERATOR(less_than, <);
MAKE_OPERATOR(less_than_equal, <=);
MAKE_OPERATOR(equal, ==);
MAKE_OPERATOR(not_equal, !=);
MAKE_OPERATOR(greater_than, >);
MAKE_OPERATOR(greater_than_equal, >=);
EM_JS(int, hiwire_next, (int idobj), {
var jsobj = Module.hiwire_get_value(idobj);
if (jsobj.next === undefined) {
return -1;
}
return Module.hiwire_new_value(jsobj.next());
});
EM_JS(int, hiwire_nonzero, (int idobj), {
var jsobj = Module.hiwire_get_value(idobj);
return (jsobj != 0) ? 1 : 0;
});
......@@ -179,6 +179,14 @@ hiwire_get_member_string(int idobj, int ptrname);
void
hiwire_set_member_string(int idobj, int ptrname, int idval);
/**
* Delete an object member by string.
*
* The string is a char* to null-terminated UTF8.
*/
void
hiwire_delete_member_string(int idobj, int ptrname);
/**
* Get an object member by integer.
*
......@@ -194,11 +202,32 @@ hiwire_get_member_int(int idobj, int idx);
*
* The integer is a C integer, not an id reference to a Javascript integer.
*
* Returns: New reference
*/
void
hiwire_set_member_int(int idobj, int idx, int idval);
/**
* Get an object member by object.
*
* Returns: New reference
*/
int
hiwire_get_member_obj(int idobj, int ididx);
/**
* Set an object member by object.
*
*/
void
hiwire_set_member_obj(int idobj, int ididx, int idval);
/**
* Delete an object member by object.
*
*/
void
hiwire_delete_member_obj(int idobj, int ididx);
/**
* Call a function
*
......@@ -255,4 +284,62 @@ hiwire_is_function(int idobj);
int
hiwire_to_string(int idobj);
/**
* Gets the "typeof" string for a value.
*
* Returns: New reference to Javascript string
*/
int
hiwire_typeof(int idobj);
/**
* Returns non-zero if a < b.
*/
int
hiwire_less_than(int ida, int idb);
/**
* Returns non-zero if a <= b.
*/
int
hiwire_less_than_equal(int ida, int idb);
/**
* Returns non-zero if a == b.
*/
int
hiwire_equal(int ida, int idb);
/**
* Returns non-zero if a != b.
*/
int
hiwire_not_equal(int idx, int idb);
/**
* Returns non-zero if a > b.
*/
int
hiwire_greater_than(int ida, int idb);
/**
* Returns non-zero if a >= b.
*/
int
hiwire_greater_than_equal(int ida, int idb);
/**
* Calls the `next` function on an iterator.
*
* Returns: -1 if `next` function is undefined.
*/
int
hiwire_next(int idobj);
/**
* Returns 1 if the value is non-zero.
*
*/
int hiwire_nonzero(int idobj);
#endif /* HIWIRE_H */
......@@ -49,6 +49,12 @@ JsProxy_GetAttr(PyObject* o, PyObject* attr_name)
if (strncmp(key, "new", 4) == 0) {
Py_DECREF(str);
return PyObject_GenericGetAttr(o, attr_name);
} else if (strncmp(key, "typeof", 7) == 0) {
Py_DECREF(str);
int idval = hiwire_typeof(self->js);
PyObject* result = js2python(idval);
hiwire_decref(idval);
return result;
}
int idresult = hiwire_get_member_string(self->js, (int)key);
......@@ -74,9 +80,14 @@ JsProxy_SetAttr(PyObject* o, PyObject* attr_name, PyObject* pyvalue)
return -1;
}
char* key = PyUnicode_AsUTF8(attr_name_py_str);
int idvalue = python2js(pyvalue);
hiwire_set_member_string(self->js, (int)key, idvalue);
hiwire_decref(idvalue);
if (pyvalue == NULL) {
hiwire_delete_member_string(self->js, (int)key);
} else {
int idvalue = python2js(pyvalue);
hiwire_set_member_string(self->js, (int)key, idvalue);
hiwire_decref(idvalue);
}
Py_DECREF(attr_name_py_str);
return 0;
......@@ -104,6 +115,84 @@ JsProxy_Call(PyObject* o, PyObject* args, PyObject* kwargs)
return pyresult;
}
static PyObject*
JsProxy_RichCompare(PyObject *a, PyObject *b, int op) {
JsProxy* aproxy = (JsProxy*)a;
if (!JsProxy_Check(b)) {
switch (op) {
case Py_EQ:
Py_RETURN_FALSE;
case Py_NE:
Py_RETURN_TRUE;
default:
return Py_NotImplemented;
}
}
int result;
int ida = python2js(a);
int idb = python2js(b);
switch (op) {
case Py_LT:
result = hiwire_less_than(ida, idb);
break;
case Py_LE:
result = hiwire_less_than_equal(ida, idb);
break;
case Py_EQ:
result = hiwire_equal(ida, idb);
break;
case Py_NE:
result = hiwire_not_equal(ida, idb);
break;
case Py_GT:
result = hiwire_greater_than(ida, idb);
break;
case Py_GE:
result = hiwire_greater_than_equal(ida, idb);
break;
}
hiwire_decref(ida);
hiwire_decref(idb);
if (result) {
Py_RETURN_TRUE;
} else {
Py_RETURN_FALSE;
}
}
static PyObject*
JsProxy_GetIter(PyObject *o)
{
Py_INCREF(o);
return o;
}
static PyObject*
JsProxy_IterNext(PyObject *o)
{
JsProxy* self = (JsProxy*)o;
int idresult = hiwire_next(self->js);
if (idresult == -1) {
return NULL;
}
int iddone = hiwire_get_member_string(idresult, (int)"done");
int done = hiwire_nonzero(iddone);
hiwire_decref(iddone);
if (done) {
return NULL;
}
int idvalue = hiwire_get_member_string(idresult, (int)"value");
PyObject* pyvalue = js2python(idvalue);
hiwire_decref(idvalue);
return pyvalue;
}
static PyObject*
JsProxy_New(PyObject* o, PyObject* args, PyObject* kwargs)
{
......@@ -135,38 +224,39 @@ JsProxy_length(PyObject* o)
}
PyObject*
JsProxy_item(PyObject* o, Py_ssize_t idx)
JsProxy_subscript(PyObject* o, PyObject* pyidx)
{
JsProxy* self = (JsProxy*)o;
int idresult = hiwire_get_member_int(self->js, idx);
int ididx = python2js(pyidx);
int idresult = hiwire_get_member_obj(self->js, ididx);
hiwire_decref(ididx);
PyObject* pyresult = js2python(idresult);
hiwire_decref(idresult);
return pyresult;
}
int
JsProxy_ass_item(PyObject* o, Py_ssize_t idx, PyObject* value)
JsProxy_ass_subscript(PyObject* o, PyObject* pyidx, PyObject* pyvalue)
{
JsProxy* self = (JsProxy*)o;
int idvalue = python2js(value);
hiwire_set_member_int(self->js, idx, idvalue);
hiwire_decref(idvalue);
int ididx = python2js(pyidx);
if (pyvalue == NULL) {
hiwire_delete_member_obj(self->js, ididx);
} else {
int idvalue = python2js(pyvalue);
hiwire_set_member_obj(self->js, ididx, idvalue);
hiwire_decref(idvalue);
}
hiwire_decref(ididx);
return 0;
}
// clang-format off
static PySequenceMethods JsProxy_SequenceMethods = {
static PyMappingMethods JsProxy_MappingMethods = {
JsProxy_length,
NULL,
NULL,
JsProxy_item,
NULL,
JsProxy_ass_item,
NULL,
NULL,
NULL,
NULL
JsProxy_subscript,
JsProxy_ass_subscript,
};
// clang-format on
......@@ -183,10 +273,13 @@ static PyTypeObject JsProxyType = {
.tp_call = JsProxy_Call,
.tp_getattro = JsProxy_GetAttr,
.tp_setattro = JsProxy_SetAttr,
.tp_richcompare = JsProxy_RichCompare,
.tp_flags = Py_TPFLAGS_DEFAULT,
.tp_doc = "A proxy to make a Javascript object behave like a Python object",
.tp_methods = JsProxy_Methods,
.tp_as_sequence = &JsProxy_SequenceMethods,
.tp_as_mapping = &JsProxy_MappingMethods,
.tp_iter = JsProxy_GetIter,
.tp_iternext = JsProxy_IterNext,
.tp_repr = JsProxy_Repr
};
......
......@@ -172,6 +172,57 @@ def test_jsproxy(selenium):
assert selenium.run(
"from js import ImageData\n"
"ImageData.new(64, 64)")
assert selenium.run(
"from js import ImageData\n"
"ImageData.typeof") == 'function'
selenium.run_js(
"class Point {\n"
" constructor(x, y) {\n"
" this.x = x;\n"
" this.y = y;\n"
" }\n"
"}\n"
"window.TEST = new Point(42, 43);")
assert selenium.run(
"from js import TEST\n"
"del TEST.y\n"
"TEST.y\n") is None
selenium.run_js(
"class Point {\n"
" constructor(x, y) {\n"
" this.x = x;\n"
" this.y = y;\n"
" }\n"
"}\n"
"window.TEST = new Point(42, 43);")
assert selenium.run(
"from js import TEST\n"
"del TEST['y']\n"
"TEST['y']\n") is None
assert selenium.run(
"from js import TEST\n"
"TEST == TEST\n")
assert selenium.run(
"from js import TEST\n"
"TEST != 'foo'\n")
def test_jsproxy_iter(selenium):
selenium.run_js(
"function makeIterator(array) {\n"
" var nextIndex = 0;\n"
" return {\n"
" next: function() {\n"
" return nextIndex < array.length ?\n"
" {value: array[nextIndex++], done: false} :\n"
" {done: true};\n"
" }\n"
" };\n"
"}\n"
"window.ITER = makeIterator([1, 2, 3]);")
assert selenium.run(
"from js import ITER\n"
"list(ITER)") == [1, 2, 3]
def test_open_url(selenium):
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment