Skip to content

Commit 546ba55

Browse files
committed
Evaluation with quickjs works.
1 parent 2489ea3 commit 546ba55

File tree

18 files changed

+59101
-3
lines changed

18 files changed

+59101
-3
lines changed

Makefile

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,12 @@ test: install
55
install: build
66
python3 setup.py install --user
77

8-
build: Makefile module.c
8+
build: Makefile module.c third-party/quickjs.c third-party/quickjs.h
99
python3 setup.py build
1010

1111
format:
1212
python3 -m yapf -i -r --style .style.yapf .
1313
clang-format-7 -i module.c
14+
15+
clean:
16+
rm -rf build/

module.c

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,86 @@
11
#include <Python.h>
22

3+
#include "third-party/quickjs.h"
4+
35
static PyObject *test(PyObject *self, PyObject *args) {
46
return Py_BuildValue("i", 42);
57
}
68

79
struct module_state {};
810

11+
typedef struct {
12+
PyObject_HEAD JSRuntime *runtime;
13+
JSContext *context;
14+
} ContextData;
15+
16+
static PyObject *context_new(PyTypeObject *type, PyObject *args, PyObject *kwds) {
17+
ContextData *self;
18+
self = (ContextData *)type->tp_alloc(type, 0);
19+
if (self != NULL) {
20+
self->runtime = JS_NewRuntime();
21+
self->context = JS_NewContext(self->runtime);
22+
}
23+
return (PyObject *)self;
24+
}
25+
26+
static void context_dealloc(ContextData *self) {
27+
JS_FreeContext(self->context);
28+
JS_FreeRuntime(self->runtime);
29+
Py_TYPE(self)->tp_free((PyObject *)self);
30+
}
31+
32+
static PyObject *context_eval(ContextData *self, PyObject *args) {
33+
const char *code;
34+
if (!PyArg_ParseTuple(args, "s", &code)) {
35+
return NULL;
36+
}
37+
JSValue value = JS_Eval(self->context, code, strlen(code), "<input>", JS_EVAL_TYPE_GLOBAL);
38+
int tag = JS_VALUE_GET_TAG(value);
39+
PyObject *return_value = NULL;
40+
41+
if (tag == JS_TAG_INT) {
42+
return_value = Py_BuildValue("i", JS_VALUE_GET_INT(value));
43+
} else if (tag == JS_TAG_BOOL) {
44+
return_value = Py_BuildValue("O", JS_VALUE_GET_BOOL(value) ? Py_True : Py_False);
45+
} else if (tag == JS_TAG_NULL) {
46+
// None
47+
} else if (tag == JS_TAG_UNDEFINED) {
48+
// None
49+
} else if (tag == JS_TAG_UNINITIALIZED) {
50+
// None
51+
} else if (tag == JS_TAG_EXCEPTION) {
52+
// TODO: Raise exception.
53+
} else if (tag == JS_TAG_FLOAT64) {
54+
return_value = Py_BuildValue("d", JS_VALUE_GET_FLOAT64(value));
55+
} else if (tag == JS_TAG_STRING) {
56+
const char *cstring = JS_ToCString(self->context, value);
57+
return_value = Py_BuildValue("s", cstring);
58+
JS_FreeCString(self->context, cstring);
59+
} else {
60+
// TODO: Raise exception.
61+
}
62+
63+
JS_FreeValue(self->context, value);
64+
if (return_value == NULL) {
65+
Py_RETURN_NONE;
66+
}
67+
return return_value;
68+
}
69+
70+
static PyMethodDef context_methods[] = {
71+
{"eval", (PyCFunction)context_eval, METH_VARARGS, "Evaluates a Javascript string."},
72+
{NULL} /* Sentinel */
73+
};
74+
75+
static PyTypeObject Context = {PyVarObject_HEAD_INIT(NULL, 0).tp_name = "_quickjs.Context",
76+
.tp_doc = "Quickjs context",
77+
.tp_basicsize = sizeof(ContextData),
78+
.tp_itemsize = 0,
79+
.tp_flags = Py_TPFLAGS_DEFAULT,
80+
.tp_new = context_new,
81+
.tp_dealloc = (destructor)context_dealloc,
82+
.tp_methods = context_methods};
83+
984
static PyMethodDef myextension_methods[] = {{"test", (PyCFunction)test, METH_NOARGS, NULL},
1085
{NULL, NULL}};
1186

@@ -20,5 +95,16 @@ static struct PyModuleDef moduledef = {PyModuleDef_HEAD_INIT,
2095
NULL};
2196

2297
PyMODINIT_FUNC PyInit__quickjs(void) {
23-
return PyModule_Create(&moduledef);
98+
if (PyType_Ready(&Context) < 0) {
99+
return NULL;
100+
}
101+
102+
PyObject *module = PyModule_Create(&moduledef);
103+
if (module == NULL) {
104+
return NULL;
105+
}
106+
107+
Py_INCREF(&Context);
108+
PyModule_AddObject(module, "Context", (PyObject *)&Context);
109+
return module;
24110
}

quickjs/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,6 @@
33

44
def test():
55
return _quickjs.test()
6+
7+
8+
Context = _quickjs.Context

setup.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
from distutils.core import setup, Extension
22

3-
_quickjs = Extension('_quickjs', sources=['module.c'])
3+
_quickjs = Extension('_quickjs',
4+
define_macros=[('CONFIG_VERSION', '"2019-07-09"')],
5+
sources=[
6+
'module.c', 'third-party/quickjs.c', 'third-party/cutils.c',
7+
'third-party/libregexp.c', 'third-party/libunicode.c'
8+
])
49

510
setup(name='quickjs',
611
version='1.0',

test_quickjs.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,34 @@
66
class LoadModule(unittest.TestCase):
77
def test_42(self):
88
self.assertEqual(quickjs.test(), 42)
9+
10+
11+
class Context(unittest.TestCase):
12+
def setUp(self):
13+
self.context = quickjs.Context()
14+
15+
def test_eval_int(self):
16+
self.assertEqual(self.context.eval("40 + 2"), 42)
17+
18+
def test_eval_float(self):
19+
self.assertEqual(self.context.eval("40.0 + 2.0"), 42.0)
20+
21+
def test_eval_str(self):
22+
self.assertEqual(self.context.eval("'4' + '2'"), "42")
23+
24+
def test_eval_bool(self):
25+
self.assertEqual(self.context.eval("true || false"), True)
26+
27+
def test_eval_null(self):
28+
self.assertIsNone(self.context.eval("null"))
29+
30+
def test_eval_undefined(self):
31+
self.assertIsNone(self.context.eval("undefined"))
32+
33+
def test_wrong_type(self):
34+
with self.assertRaises(TypeError):
35+
self.assertEqual(self.context.eval(1), 42)
36+
37+
def test_context_between_calls(self):
38+
self.context.eval("x = 40; y = 2;")
39+
self.assertEqual(self.context.eval("x + y"), 42)

0 commit comments

Comments
 (0)