Skip to content

Commit 4156e73

Browse files
committed
Add a Object type holding JS objects.
1 parent 3c00d43 commit 4156e73

File tree

3 files changed

+82
-16
lines changed

3 files changed

+82
-16
lines changed

module.c

Lines changed: 74 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,54 @@
22

33
#include "third-party/quickjs.h"
44

5-
static PyObject* JSException = NULL;
5+
static PyObject *JSException = NULL;
6+
static PyObject *quickjs_to_python(JSContext *context, JSValue value);
67

7-
static PyObject* quickjs_to_python(JSContext* context, JSValue value) {
8-
int tag = JS_VALUE_GET_TAG(value);
8+
//
9+
// Object type
10+
//
11+
12+
typedef struct {
13+
PyObject_HEAD;
14+
JSContext *context;
15+
JSValue object;
16+
} ObjectData;
17+
18+
static PyObject *object_new(PyTypeObject *type, PyObject *args, PyObject *kwds) {
19+
ObjectData *self;
20+
self = (ObjectData *)type->tp_alloc(type, 0);
21+
if (self != NULL) {
22+
self->context = NULL;
23+
}
24+
return (PyObject *)self;
25+
}
26+
27+
static void object_dealloc(ObjectData *self) {
28+
Py_TYPE(self)->tp_free((PyObject *)self);
29+
}
30+
31+
static PyObject *object_call(ObjectData *self, PyObject *args) {
32+
if (self->context) {
33+
JS_FreeValue(self->context, self->object);
34+
}
35+
Py_RETURN_NONE;
36+
}
37+
38+
static PyMethodDef object_methods[] = {
39+
{"call", (PyCFunction)object_call, METH_VARARGS, "Calls a JS function."}, {NULL} /* Sentinel */
40+
};
41+
42+
static PyTypeObject Object = {PyVarObject_HEAD_INIT(NULL, 0).tp_name = "_quickjs.Object",
43+
.tp_doc = "Quickjs object",
44+
.tp_basicsize = sizeof(ObjectData),
45+
.tp_itemsize = 0,
46+
.tp_flags = Py_TPFLAGS_DEFAULT,
47+
.tp_new = object_new,
48+
.tp_dealloc = (destructor)object_dealloc,
49+
.tp_methods = object_methods};
50+
51+
static PyObject *quickjs_to_python(JSContext *context, JSValue value) {
52+
int tag = JS_VALUE_GET_TAG(value);
953
PyObject *return_value = NULL;
1054

1155
if (tag == JS_TAG_INT) {
@@ -19,21 +63,27 @@ static PyObject* quickjs_to_python(JSContext* context, JSValue value) {
1963
} else if (tag == JS_TAG_UNINITIALIZED) {
2064
return_value = Py_None;
2165
} else if (tag == JS_TAG_EXCEPTION) {
22-
JSValue exception = JS_GetException(context);
23-
JSValue error_string = JS_ToString(context, exception);
24-
const char *cstring = JS_ToCString(context, error_string);
25-
PyErr_Format(JSException, "%s", cstring);
66+
JSValue exception = JS_GetException(context);
67+
JSValue error_string = JS_ToString(context, exception);
68+
const char *cstring = JS_ToCString(context, error_string);
69+
PyErr_Format(JSException, "%s", cstring);
2670
JS_FreeCString(context, cstring);
27-
JS_FreeValue(context, error_string);
28-
JS_FreeValue(context, exception);
71+
JS_FreeValue(context, error_string);
72+
JS_FreeValue(context, exception);
2973
} else if (tag == JS_TAG_FLOAT64) {
3074
return_value = Py_BuildValue("d", JS_VALUE_GET_FLOAT64(value));
3175
} else if (tag == JS_TAG_STRING) {
3276
const char *cstring = JS_ToCString(context, value);
3377
return_value = Py_BuildValue("s", cstring);
3478
JS_FreeCString(context, cstring);
79+
} else if (tag == JS_TAG_OBJECT) {
80+
return_value = PyObject_CallObject((PyObject *)&Object, NULL);
81+
ObjectData *object = (ObjectData *)return_value;
82+
object->context = context;
83+
object->object = value;
84+
return return_value;
3585
} else {
36-
// TODO: Raise exception.
86+
PyErr_Format(PyExc_ValueError, "Unknown quickjs tag: %d", tag);
3787
}
3888

3989
JS_FreeValue(context, value);
@@ -49,6 +99,9 @@ static PyObject *test(PyObject *self, PyObject *args) {
4999

50100
struct module_state {};
51101

102+
//
103+
// Context type
104+
//
52105
typedef struct {
53106
PyObject_HEAD JSRuntime *runtime;
54107
JSContext *context;
@@ -76,7 +129,7 @@ static PyObject *context_eval(ContextData *self, PyObject *args) {
76129
return NULL;
77130
}
78131
JSValue value = JS_Eval(self->context, code, strlen(code), "<input>", JS_EVAL_TYPE_GLOBAL);
79-
return quickjs_to_python(self->context, value);
132+
return quickjs_to_python(self->context, value);
80133
}
81134

82135
static PyMethodDef context_methods[] = {
@@ -110,19 +163,24 @@ PyMODINIT_FUNC PyInit__quickjs(void) {
110163
if (PyType_Ready(&Context) < 0) {
111164
return NULL;
112165
}
166+
if (PyType_Ready(&Object) < 0) {
167+
return NULL;
168+
}
113169

114170
PyObject *module = PyModule_Create(&moduledef);
115171
if (module == NULL) {
116172
return NULL;
117173
}
118174

119-
JSException = PyErr_NewException("_quickjs.JSException", NULL, NULL);
120-
if (JSException == NULL) {
121-
return NULL;
122-
}
175+
JSException = PyErr_NewException("_quickjs.JSException", NULL, NULL);
176+
if (JSException == NULL) {
177+
return NULL;
178+
}
123179

124180
Py_INCREF(&Context);
125181
PyModule_AddObject(module, "Context", (PyObject *)&Context);
126-
PyModule_AddObject(module, "JSException", JSException);
182+
Py_INCREF(&Object);
183+
PyModule_AddObject(module, "Object", (PyObject *)&Object);
184+
PyModule_AddObject(module, "JSException", JSException);
127185
return module;
128186
}

quickjs/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@ def test():
66

77

88
Context = _quickjs.Context
9+
Object = _quickjs.Object
910
JSException = _quickjs.JSException

test_quickjs.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,13 @@ def test_function(self):
4646
""")
4747
self.assertEqual(self.context.eval("special(2)"), 42)
4848

49+
def test_function_is_object(self):
50+
f = self.context.eval("""
51+
a = function(x) {
52+
return 40 + x;
53+
}
54+
""")
55+
self.assertIsInstance(f, quickjs.Object)
4956

5057
def test_error(self):
5158
with self.assertRaisesRegex(quickjs.JSException, "ReferenceError: missing is not defined"):

0 commit comments

Comments
 (0)