An embedded javascript interpreter

This commit is contained in:
Kovid Goyal 2015-06-18 15:37:18 +05:30
parent b48d401ac2
commit 655c9499fd
13 changed files with 78661 additions and 0 deletions

View File

@ -4,6 +4,10 @@ License: GPL-3
The full text of the GPL is distributed as in The full text of the GPL is distributed as in
/usr/share/common-licenses/GPL-3 on Debian systems. /usr/share/common-licenses/GPL-3 on Debian systems.
Files: src/duktape/*
Copyright: Various
License: MIT
Files: src/unrar/* Files: src/unrar/*
Copyright: Various Copyright: Various
License: Non-free License: Non-free

View File

@ -67,6 +67,12 @@ if iswindows:
extensions = [ extensions = [
Extension('dukpy',
['duktape/%s.c' % x for x in 'context conversions proxy module duktape/duktape'.split()],
headers=['duktape/dukpy.h', 'duktape/duktape/duktape.h'],
optimize_level=2,
),
Extension('hunspell', Extension('hunspell',
['hunspell/'+x for x in ['hunspell/'+x for x in
'affentry.cxx affixmgr.cxx csutil.cxx dictmgr.cxx filemgr.cxx hashmgr.cxx hunspell.cxx phonet.cxx replist.cxx suggestmgr.cxx'.split() 'affentry.cxx affixmgr.cxx csutil.cxx dictmgr.cxx filemgr.cxx hashmgr.cxx hunspell.cxx phonet.cxx replist.cxx suggestmgr.cxx'.split()

View File

@ -147,6 +147,7 @@ class Plugins(collections.Mapping):
'matcher', 'matcher',
'tokenizer', 'tokenizer',
'certgen', 'certgen',
'dukpy',
] ]
if iswindows: if iswindows:
plugins.extend(['winutil', 'wpd', 'winfonts']) plugins.extend(['winutil', 'wpd', 'winfonts'])

View File

@ -168,6 +168,12 @@ def test_icu():
test_build() test_build()
print ('ICU OK!') print ('ICU OK!')
def test_dukpy():
print ('Testing dukpy')
from duktape.tests import test_build
test_build()
print ('dukpy OK!')
def test_wpd(): def test_wpd():
wpd = plugins['wpd'][0] wpd = plugins['wpd'][0]
try: try:
@ -226,6 +232,7 @@ def test():
if iswindows: if iswindows:
test_dlls() test_dlls()
test_plugins() test_plugins()
test_dukpy()
test_lxml() test_lxml()
test_ssl() test_ssl()
test_sqlite() test_sqlite()

42
src/duktape/__init__.py Normal file
View File

@ -0,0 +1,42 @@
#!/usr/bin/env python2
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
from __future__ import (unicode_literals, division, absolute_import,
print_function)
__copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
__all__ = ['dukpy', 'Context', 'undefined']
import errno, os
from functools import partial
from calibre.constants import plugins
dukpy, err = plugins['dukpy']
if err:
raise RuntimeError('Failed to load dukpy with error: %s' % err)
del err
Context_, undefined = dukpy.Context, dukpy.undefined
def load_file(base_dirs, name):
for b in base_dirs:
try:
return open(os.path.join(b, name), 'rb').read().decode('utf-8')
except EnvironmentError as e:
if e.errno != errno.ENOENT:
raise
raise EnvironmentError('No module named: %s found in the base directories: %s' % (name, os.pathsep.join(base_dirs)))
def Context(base_dirs=()):
ans = Context_()
if not base_dirs:
base_dirs = (os.getcwdu(),)
ans.g.Duktape.load_file = partial(load_file, base_dirs or (os.getcwdu(),))
ans.eval('''
console = { log: function() { print(Array.prototype.join.call(arguments, ' ')); } };
Duktape.modSearch = function (id, require, exports, module) {
return Duktape.load_file(id);
}
''')
return ans

231
src/duktape/context.c Normal file
View File

@ -0,0 +1,231 @@
#include "dukpy.h"
#include <structmember.h>
static void DukContext_init_internal(DukContext *self)
{
/* heap_stash[(void *)self->ctx] = (void *)self */
duk_push_heap_stash(self->ctx);
duk_push_pointer(self->ctx, self->ctx);
duk_push_pointer(self->ctx, self);
duk_put_prop(self->ctx, -3);
duk_pop(self->ctx);
}
static int DukContext_init(DukContext *self, PyObject *args, PyObject *kw)
{
(void)args;
(void)kw;
self->heap_manager = NULL; /* We manage the heap */
self->ctx = duk_create_heap_default();
if (!self->ctx) {
PyErr_SetString(PyExc_MemoryError, "Failed to create duktape heap");
return -1;
}
/* heap_stash.heap = (void *)self */
duk_push_heap_stash(self->ctx);
duk_push_pointer(self->ctx, self);
duk_put_prop_string(self->ctx, -2, "heap");
duk_pop(self->ctx);
DukContext_init_internal(self);
return 0;
}
static PyObject *DukContext_new_global_env(DukContext *self, PyObject *args)
{
DukContext *new_context;
(void)args;
new_context = PyObject_New(DukContext, &DukContext_Type);
if (new_context == NULL)
return NULL;
new_context->heap_manager = self->heap_manager ? self->heap_manager : self;
Py_INCREF(self);
/* heap_stash[(void *)new_context] = new_context->ctx (thread object) */
duk_push_heap_stash(self->ctx);
duk_push_pointer(self->ctx, new_context);
duk_push_thread_new_globalenv(self->ctx);
new_context->ctx = duk_get_context(self->ctx, -1);
duk_put_prop(self->ctx, -3);
duk_pop(self->ctx);
DukContext_init_internal(new_context);
return (PyObject *)new_context;
}
static void DukContext_dealloc(DukContext *self)
{
if (!self->heap_manager) {
duk_destroy_heap(self->ctx);
} else {
/* Use heap manager's ctx because self->ctx is destroyed */
duk_context *ctx = self->heap_manager->ctx;
duk_push_heap_stash(ctx);
/* delete heap_stash[(void *)self->ctx] */
duk_push_pointer(ctx, self->ctx);
duk_del_prop(ctx, -2);
/* delete heap_stash[(void *)self] */
duk_push_pointer(ctx, self);
duk_del_prop(ctx, -2);
duk_pop(ctx);
Py_DECREF(self->heap_manager);
}
Py_TYPE(self)->tp_free((PyObject *)self);
}
static PyObject *DukContext_eval(DukContext *self, PyObject *args, PyObject *kw)
{
const char *code;
int noresult = 0;
PyObject *result = NULL, *temp = NULL;
static char *keywords[] = {"code", "noreturn", NULL};
if (!PyArg_ParseTupleAndKeywords(args, kw, "s|O:eval", keywords,
&code, &temp)) {
return NULL;
}
if (temp && PyObject_IsTrue(temp)) noresult = 1;
if (duk_peval_string(self->ctx, code) != 0) {
PyErr_Format(PyExc_SyntaxError, "%s",
duk_safe_to_string(self->ctx, -1));
return NULL;
}
if (noresult) {
duk_pop(self->ctx);
Py_RETURN_NONE;
}
result = duk_to_python(self->ctx, -1);
duk_pop(self->ctx);
return result;
}
static PyObject *DukContext_eval_file(DukContext *self, PyObject *args, PyObject *kw)
{
const char *path;
int noresult = 0;
PyObject *result = NULL, *temp = NULL;
static char *keywords[] = {"path", "noreturn", NULL};
if (!PyArg_ParseTupleAndKeywords(args, kw, "s|O:eval_file", keywords,
&path, &temp)) {
return NULL;
}
if (temp && PyObject_IsTrue(temp)) noresult = 1;
if (duk_peval_file(self->ctx, path) != 0) {
PyErr_Format(PyExc_SyntaxError,
"%s:%s", path, duk_safe_to_string(self->ctx, -1));
return NULL;
}
if (noresult) {
duk_pop(self->ctx);
Py_RETURN_NONE;
}
result = duk_to_python(self->ctx, -1);
duk_pop(self->ctx);
return result;
}
DukContext *DukContext_get(duk_context *ctx)
{
DukContext *context;
/* Read DukContext from heap_stash[(void *)ctx] */
duk_push_heap_stash(ctx);
duk_push_pointer(ctx, ctx);
duk_get_prop(ctx, -2);
context = duk_get_pointer(ctx, -1);
duk_pop_n(ctx, 2);
/* context is NULL for uncached contexts */
return context;
}
static PyMethodDef DukContext_methods[] = {
{"eval", (PyCFunction)DukContext_eval,
METH_VARARGS | METH_KEYWORDS, "Evaluate code"},
{"eval_file", (PyCFunction)DukContext_eval_file,
METH_VARARGS | METH_KEYWORDS, "Evaluate a file"},
{"new_global_env", (PyCFunction)DukContext_new_global_env,
METH_NOARGS, "Return a new context with a fresh global object"},
{NULL}
};
PyObject *DukContext_get_global(DukContext *self, void *closure)
{
DukObject *global;
(void)closure;
duk_push_global_object(self->ctx);
global = DukObject_from_DukContext(self, -1);
duk_pop(self->ctx);
return (PyObject *)global;
}
static PyGetSetDef DukContext_getset[] = {
{"g", (getter)DukContext_get_global, NULL, "The global object", NULL},
{NULL}
};
PyTypeObject DukContext_Type = {
PyVarObject_HEAD_INIT(NULL, 0)
"dukpy.Context", /* tp_name */
sizeof(DukContext), /* tp_basicsize */
0, /* tp_itemsize */
(destructor)DukContext_dealloc, /* tp_dealloc */
0, /* tp_print */
0, /* tp_getattr */
0, /* tp_setattr */
0, /* tp_reserved */
0, /* tp_repr */
0, /* tp_as_number */
0, /* tp_as_sequence */
0, /* tp_as_mapping */
0, /* tp_hash */
0, /* tp_call */
0, /* tp_str */
0, /* tp_getattro */
0, /* tp_setattro */
0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT, /* tp_flags */
"Duktape context", /* tp_doc */
0, /* tp_traverse */
0, /* tp_clear */
0, /* tp_richcompare */
0, /* tp_weaklistoffset */
0, /* tp_iter */
0, /* tp_iternext */
DukContext_methods, /* tp_methods */
0, /* tp_members */
DukContext_getset, /* tp_getset */
0, /* tp_base */
0, /* tp_dict */
0, /* tp_descr_get */
0, /* tp_descr_set */
0, /* tp_dictoffset */
(initproc)DukContext_init /* tp_init */
};

286
src/duktape/conversions.c Normal file
View File

@ -0,0 +1,286 @@
#include "dukpy.h"
static int get_repr(PyObject *value, char *buf, int bufsz) {
PyObject *temp = NULL, *repr = NULL;
memset(buf, 0, bufsz);
if (!value) return 0;
repr = PyObject_Repr(value);
if (repr && !PyBytes_Check(repr)) {
temp = PyUnicode_AsUTF8String(repr);
Py_DECREF(repr); repr = temp;
}
if (!repr) return 0;
strncpy(buf, PyBytes_AS_STRING(repr), bufsz - 1);
Py_DECREF(repr);
return 1;
}
static duk_ret_t python_function_caller(duk_context *ctx)
{
PyObject *func, *args, *result;
duk_idx_t nargs, i;
static char buf1[200], buf2[1024];
nargs = duk_get_top(ctx);
duk_push_current_function(ctx);
duk_get_prop_string(ctx, -1, "\xff" "py_object");
func = duk_get_pointer(ctx, -1);
args = PyTuple_New(nargs);
if (!args)
return DUK_RET_ALLOC_ERROR;
for (i = 0; i < nargs; i++) {
PyObject *arg = duk_to_python(ctx, i);
if (arg == NULL)
return DUK_RET_TYPE_ERROR;
PyTuple_SET_ITEM(args, i, arg);
}
result = PyObject_Call(func, args, NULL);
if (!result) {
get_repr(func, buf1, 200);
if (!PyErr_Occurred())
duk_error(ctx, DUK_ERR_ERROR, "Python function (%s) failed", buf1);
PyObject *ptype = NULL, *pval = NULL, *tb = NULL;
PyErr_Fetch(&ptype, &pval, &tb);
if (!get_repr(pval, buf2, 1024)) get_repr(ptype, buf2, 1024);
Py_XDECREF(ptype); Py_XDECREF(pval); Py_XDECREF(tb);
PyErr_Clear(); /* In case there was an error in get_repr() */
duk_error(ctx, DUK_ERR_ERROR, "Python function (%s) failed with error: %s", buf1, buf2);
}
python_to_duk(ctx, result);
Py_DECREF(result);
return 1;
}
static duk_ret_t python_object_decref(duk_context *ctx) {
int deleted = 0;
duk_get_prop_string(ctx, 0, "\xff""deleted");
deleted = duk_to_boolean(ctx, -1);
duk_pop(ctx);
if (!deleted) {
duk_get_prop_string(ctx, 0, "\xff""py_object");
Py_XDECREF(duk_get_pointer(ctx, -1));
duk_pop(ctx);
// Mark as deleted
duk_push_boolean(ctx, 1);
duk_put_prop_string(ctx, 0, "\xff""deleted");
}
return 0;
}
int python_to_duk(duk_context *ctx, PyObject *value)
{
/* Python to duktape conversion. If successful, leaves the
converted value on the top of the stack and returns 0.
Otherwise, raises a Python exception and returns -1.
*/
#if PY_MAJOR_VERSION < 3
int ret;
#endif
static char buf[200];
if (value == Duk_undefined) {
duk_push_undefined(ctx);
}
else if (value == Py_None) {
/* Map None to null */
duk_push_null(ctx);
}
else if (value == Py_True) {
duk_push_true(ctx);
}
else if (value == Py_False) {
duk_push_false(ctx);
}
else if (Py_TYPE(value) == &DukObject_Type) {
DukObject_push((DukObject *)value, ctx);
}
else if (PyUnicode_Check(value)) {
/* Unicode string */
#ifdef PyUnicode_AsUTF8AndSize
char *str;
Py_ssize_t len;
str = PyUnicode_AsUTF8AndSize(value, &len);
if (str == NULL)
return -1;
duk_push_lstring(ctx, str, len);
#else
PyObject *utf8str = PyUnicode_AsUTF8String(value);
if (utf8str == NULL)
return -1;
duk_push_lstring(ctx, PyBytes_AS_STRING(utf8str), PyBytes_GET_SIZE(utf8str));
Py_DECREF(utf8str);
#endif
}
#if PY_MAJOR_VERSION < 3
else if (PyBytes_Check(value)) {
/* Happens in python 2 for attribute access, for example*/
PyObject *urepr = PyUnicode_FromObject(value);
if (urepr == NULL)
return -1;
ret = python_to_duk(ctx, urepr);
Py_DECREF(urepr);
return ret;
}
#endif
else if (PyLong_Check(value)) {
double val = PyLong_AsDouble(value);
if (PyErr_Occurred())
return -1;
duk_push_number(ctx, val);
}
#if PY_MAJOR_VERSION < 3
else if (PyInt_Check(value)) {
double val = (double)PyInt_AsLong(value);
duk_push_number(ctx, val);
}
#endif
else if (PyFloat_Check(value)) {
double val = PyFloat_AsDouble(value);
if (PyErr_Occurred())
return -1;
duk_push_number(ctx, val);
}
else if (PyDict_Check(value)) {
PyObject *key, *val;
Py_ssize_t pos = 0;
duk_push_object(ctx);
while (PyDict_Next(value, &pos, &key, &val)) {
if (python_to_duk(ctx, key) == -1) {
/* Pop the object */
duk_pop(ctx);
return -1;
}
if (python_to_duk(ctx, val) == -1) {
/* Pop the key and the object */
duk_pop_n(ctx, 2);
return -1;
}
duk_put_prop(ctx, -3);
}
}
else if (PyList_Check(value)) {
PyObject *val;
Py_ssize_t i, len;
duk_push_array(ctx);
len = PyList_Size(value);
for (i = 0; i < len; i++) {
val = PyList_GetItem(value, i);
if (python_to_duk(ctx, val) == -1) {
/* Pop the array */
duk_pop(ctx);
return -1;
}
duk_put_prop_index(ctx, -2, (duk_uarridx_t)i);
}
}
else if (PyCallable_Check(value)) {
// Store the callable
duk_push_c_function(ctx, python_function_caller, DUK_VARARGS);
duk_push_pointer(ctx, value);
Py_INCREF(value);
duk_put_prop_string(ctx, -2, "\xff" "py_object");
// Store a boolean flag to mark the object as deleted because the destructor may be called several times
duk_push_boolean(ctx, 0);
duk_put_prop_string(ctx, -2, "\xff""deleted");
// Store the function destructor
duk_push_c_function(ctx, python_object_decref, 1);
duk_set_finalizer(ctx, -2);
}
else {
if(get_repr(value, buf, 200))
PyErr_Format(PyExc_TypeError, "%s is not coercible", buf);
return -1;
}
return 0;
}
PyObject *duk_to_python(duk_context *ctx, duk_idx_t index)
{
/* Duktape to Python conversion. If successful, returns a pointer
to a new PyObject reference. If not, raises a Python exception
and returns NULL.
*/
duk_idx_t index_n = duk_normalize_index(ctx, index);
PyObject *result;
if (duk_is_undefined(ctx, index_n)) {
Py_INCREF(Duk_undefined);
return Duk_undefined;
}
else if (duk_is_null(ctx, index_n)) {
Py_RETURN_NONE;
}
else if (duk_is_boolean(ctx, index_n)) {
if (duk_get_boolean(ctx, index_n))
Py_RETURN_TRUE;
else
Py_RETURN_FALSE;
}
else if (duk_is_number(ctx, index_n)) {
double number, temp;
number = duk_get_number(ctx, index_n);
if (modf(number, &temp) == 0) {
/* No fractional part */
return PyLong_FromDouble(number);
} else {
/* Has fractional part */
return PyFloat_FromDouble(number);
}
}
else if (duk_is_string(ctx, index_n)) {
const char *str;
duk_size_t len;
/* Duplicate the string because it's replaced by duk_to_lstring() */
duk_dup(ctx, index_n);
str = duk_to_lstring(ctx, -1, &len);
result = PyUnicode_DecodeUTF8(str, len, NULL);
/* Pop the duplicate */
duk_pop(ctx);
return result;
}
else if (duk_is_array(ctx, index_n)) {
return (PyObject *)DukArray_from_ctx(ctx, index_n);
}
else if (duk_is_function(ctx, index_n)) {
return (PyObject *)DukFunction_from_ctx(ctx, index_n);
}
else if (duk_is_object(ctx, index_n)) {
/* Other objects than arrays or functions */
return (PyObject *)DukObject_from_ctx(ctx, index_n);
}
else if (duk_check_type(ctx, index_n, DUK_TYPE_BUFFER)) {
PyErr_SetString(PyExc_TypeError, "'buffer' is not coercible");
return NULL;
}
else if (duk_check_type(ctx, index_n, DUK_TYPE_POINTER)) {
PyErr_SetString(PyExc_TypeError, "'pointer' is not coercible");
return NULL;
}
/* Not reached */
return NULL;
}

72
src/duktape/dukpy.h Normal file
View File

@ -0,0 +1,72 @@
#ifndef DUKPY_H
#define DUKPY_H
#include <Python.h>
#include "duktape/duktape.h"
typedef struct DukContext_ DukContext;
typedef struct DukObject_ DukObject;
typedef struct DukEnum_ DukEnum;
/* module.c */
PyObject DukUndefined;
#define Duk_undefined (&DukUndefined)
/* context.c */
struct DukContext_ {
PyObject_HEAD
duk_context *ctx;
DukContext *heap_manager;
};
PyTypeObject DukContext_Type;
DukContext *DukContext_get(duk_context *ctx);
/* proxy.c */
struct DukObject_ {
PyObject_HEAD
DukContext *context;
DukObject *parent;
};
PyTypeObject DukObject_Type;
PyTypeObject DukArray_Type;
PyTypeObject DukFunction_Type;
PyTypeObject DukEnum_Type;
DukObject *DukObject_from_DukContext(DukContext *context, duk_idx_t index);
DukObject *DukObject_from_ctx(duk_context *ctx, duk_idx_t index);
int DukObject_push(DukObject *self, duk_context *ctx);
DukObject *DukArray_from_ctx(duk_context *ctx, duk_idx_t index);
DukObject *DukFunction_from_ctx(duk_context *ctx, duk_idx_t index);
typedef enum {
DUKENUM_KEYS,
DUKENUM_VALUES,
DUKENUM_PAIRS
} dukenum_mode_t;
struct DukEnum_ {
PyObject_HEAD
DukObject base;
dukenum_mode_t mode;
};
DukEnum *DukEnum_from_DukContext(DukContext *context, dukenum_mode_t mode);
/* conversions.c */
int python_to_duk(duk_context *ctx, PyObject *value);
PyObject *duk_to_python(duk_context *ctx, duk_idx_t index);
#endif /* DUKPY_H */

72672
src/duktape/duktape/duktape.c Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

139
src/duktape/module.c Normal file
View File

@ -0,0 +1,139 @@
#include "dukpy.h"
/* ARGSUSED */
static PyObject *
undefined_repr(PyObject *op)
{
#if PY_MAJOR_VERSION < 3
return PyBytes_FromString("undefined");
#else
return PyUnicode_FromString("undefined");
#endif
}
/* ARGUSED */
static void
undefined_dealloc(PyObject* ignore)
{
/* This should never get called, but we also don't want to SEGV if
* we accidentally decref undef out of existence.
*/
Py_FatalError("deallocating undefined");
}
static PyTypeObject DukUndefined_Type = {
#if PY_MAJOR_VERSION < 3
PyVarObject_HEAD_INIT(NULL, 0)
#else
PyVarObject_HEAD_INIT(&PyType_Type, 0)
#endif
"UndefinedType",
0,
0,
undefined_dealloc,
0, /*tp_print*/
0, /*tp_getattr*/
0, /*tp_setattr*/
0, /*tp_compare*/
undefined_repr, /*tp_repr*/
0, /*tp_as_number*/
0, /*tp_as_sequence*/
0, /*tp_as_mapping*/
};
PyObject DukUndefined = {
_PyObject_EXTRA_INIT
1, &DukUndefined_Type
};
#if PY_MAJOR_VERSION >= 3
static struct PyModuleDef moduledef = {
PyModuleDef_HEAD_INIT,
"dukpy", /* m_name */
NULL, /* m_doc */
0, /* m_size */
NULL, /* m_methods */
NULL, /* m_reload */
NULL, /* m_traverse */
NULL, /* m_clear */
NULL /* m_free */
};
#endif
PyMODINIT_FUNC
#if PY_MAJOR_VERSION >= 3
PyInit_dukpy(void)
#else
initdukpy(void)
#endif
{
PyObject *mod;
#if PY_MAJOR_VERSION < 3
DukUndefined_Type.ob_type = &PyType_Type;
#endif
if (PyType_Ready(&DukUndefined_Type) < 0)
#if PY_MAJOR_VERSION >= 3
return NULL;
#else
return;
#endif
DukContext_Type.tp_new = PyType_GenericNew;
if (PyType_Ready(&DukContext_Type) < 0)
#if PY_MAJOR_VERSION >= 3
return NULL;
#else
return;
#endif
DukObject_Type.tp_new = PyType_GenericNew;
if (PyType_Ready(&DukObject_Type) < 0)
#if PY_MAJOR_VERSION >= 3
return NULL;
#else
return;
#endif
DukArray_Type.tp_new = PyType_GenericNew;
if (PyType_Ready(&DukArray_Type) < 0)
#if PY_MAJOR_VERSION >= 3
return NULL;
#else
return;
#endif
DukFunction_Type.tp_new = PyType_GenericNew;
if (PyType_Ready(&DukFunction_Type) < 0)
#if PY_MAJOR_VERSION >= 3
return NULL;
#else
return;
#endif
DukEnum_Type.tp_new = PyType_GenericNew;
if (PyType_Ready(&DukEnum_Type) < 0)
#if PY_MAJOR_VERSION >= 3
return NULL;
#else
return;
#endif
#if PY_MAJOR_VERSION >= 3
mod = PyModule_Create(&moduledef);
#else
mod = Py_InitModule3("dukpy", NULL, "Python bindings for duktape");
#endif
if (mod != NULL) {
Py_INCREF(&DukContext_Type);
PyModule_AddObject(mod, "Context", (PyObject *)&DukContext_Type);
Py_INCREF(Duk_undefined);
PyModule_AddObject(mod, "undefined", (PyObject *)Duk_undefined);
}
#if PY_MAJOR_VERSION >= 3
return mod;
#endif
}

568
src/duktape/proxy.c Normal file
View File

@ -0,0 +1,568 @@
#include "dukpy.h"
/* DukObject */
static void DukObject_INIT(DukObject *self, DukContext *context,
duk_idx_t index)
{
duk_context *ctx = context->ctx;
duk_idx_t index_n = duk_normalize_index(ctx, index);
Py_INCREF(context);
self->context = context;
self->parent = NULL;
/* heap_stash[(void *)self] = proxied_value */
duk_push_heap_stash(ctx);
duk_push_pointer(ctx, self);
duk_dup(ctx, index_n);
duk_put_prop(ctx, -3);
duk_pop(ctx);
}
static void DukObject_DESTRUCT(DukObject *self)
{
duk_context *ctx = self->context->ctx;
/* delete heap_stash[(void *)self] */
duk_push_heap_stash(ctx);
duk_push_pointer(ctx, self);
duk_del_prop(ctx, -2);
duk_pop(ctx);
Py_XDECREF(self->parent);
Py_DECREF(self->context);
}
DukObject *DukObject_from_DukContext(DukContext *context, duk_idx_t index)
{
DukObject *self;
self = PyObject_New(DukObject, &DukObject_Type);
if (self == NULL)
return NULL;
DukObject_INIT(self, context, index);
return self;
}
DukObject *DukObject_from_ctx(duk_context *ctx, duk_idx_t index)
{
DukContext *context = DukContext_get(ctx);
if (!context) {
PyErr_Format(PyExc_RuntimeError, "Unknown context %p", ctx);
return NULL;
}
return DukObject_from_DukContext(context, index);
}
static void DukObject_dealloc(DukObject *self)
{
DukObject_DESTRUCT(self);
Py_TYPE(self)->tp_free((PyObject *)self);
}
int DukObject_push(DukObject *self, duk_context *ctx)
{
/* Push the proxied value to given context's stack */
duk_push_heap_stash(ctx);
duk_push_pointer(ctx, self);
duk_get_prop(ctx, -2);
duk_replace(ctx, -2);
return 0;
}
#define PUSH(p) DukObject_push((p), (p)->context->ctx)
static PyObject *DukObject_getattr(DukObject *self, PyObject *name)
{
duk_context *ctx = self->context->ctx;
PyObject *value;
/* Look up normal attributes first */
if (!(value = PyObject_GenericGetAttr((PyObject *)self, name))) {
if (!PyErr_ExceptionMatches(PyExc_AttributeError))
return NULL;
PyErr_Clear();
} else {
return value;
}
/* Not found, query the duktape object */
PUSH(self);
if (python_to_duk(ctx, name) != 0) {
duk_pop(ctx);
return NULL;
}
duk_get_prop(ctx, -2);
value = duk_to_python(ctx, -1);
duk_pop_n(ctx, 2);
if (Py_TYPE(value) == &DukFunction_Type) {
/* Set parent link for method calls */
Py_INCREF(self);
((DukObject *)value)->parent = self;
}
return value;
}
static int DukObject_setattr(DukObject *self, PyObject *name, PyObject *value)
{
duk_context *ctx = self->context->ctx;
PUSH(self);
if (python_to_duk(ctx, name) != 0) {
duk_pop(ctx);
return -1;
}
if (python_to_duk(ctx, value) != 0) {
duk_pop_n(ctx, 2);
return -1;
}
duk_put_prop(ctx, -3);
duk_pop(ctx);
return 0;
}
static PyObject *DukObject_make_enum(DukObject *self, dukenum_mode_t mode)
{
duk_context *ctx = self->context->ctx;
PyObject *result;
PUSH(self);
duk_enum(ctx, -1, 0);
result = (PyObject *)DukEnum_from_DukContext(self->context, mode);
duk_pop(ctx);
return result;
}
static PyObject *DukObject_iter(DukObject *self)
{
return DukObject_make_enum(self, DUKENUM_KEYS);
}
static PyObject *DukObject_keys(DukObject *self, PyObject *args)
{
(void)args;
return DukObject_make_enum(self, DUKENUM_KEYS);
}
static PyObject *DukObject_values(DukObject *self, PyObject *args)
{
(void)args;
return DukObject_make_enum(self, DUKENUM_VALUES);
}
static PyObject *DukObject_items(DukObject *self, PyObject *args)
{
(void)args;
return DukObject_make_enum(self, DUKENUM_PAIRS);
}
static PyMappingMethods DukObject_as_mapping = {
NULL,
(binaryfunc)DukObject_getattr,
(objobjargproc)DukObject_setattr
};
static PyMethodDef DukObject_methods[] = {
{"keys", (PyCFunction)DukObject_keys, METH_NOARGS,
"Iterate over object keys"},
{"values", (PyCFunction)DukObject_values, METH_NOARGS,
"Iterate over object values"},
{"items", (PyCFunction)DukObject_items, METH_NOARGS,
"Iterate over key-value pairs"},
{NULL}
};
PyTypeObject DukObject_Type = {
PyVarObject_HEAD_INIT(NULL, 0)
"Object proxy", /* tp_name */
sizeof(DukObject), /* tp_basicsize */
0, /* tp_itemsize */
(destructor)DukObject_dealloc, /* tp_dealloc */
0, /* tp_print */
0, /* tp_getattr */
0, /* tp_setattr */
0, /* tp_reserved */
0, /* tp_repr */
0, /* tp_as_number */
0, /* tp_as_sequence */
&DukObject_as_mapping, /* tp_as_mapping */
0, /* tp_hash */
0, /* tp_call */
0, /* tp_str */
(getattrofunc)DukObject_getattr, /* tp_getattro */
(setattrofunc)DukObject_setattr, /* tp_setattro */
0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT, /* tp_flags */
"Duktape object proxy", /* tp_doc */
0, /* tp_traverse */
0, /* tp_clear */
0, /* tp_richcompare */
0, /* tp_weaklistoffset */
(getiterfunc)DukObject_iter, /* tp_iter */
0, /* tp_iternext */
DukObject_methods, /* tp_methods */
0, /* tp_members */
0, /* tp_getset */
0, /* tp_base */
0, /* tp_dict */
0, /* tp_descr_get */
0, /* tp_descr_set */
0, /* tp_dictoffset */
0, /* tp_init */
0, /* tp_alloc */
0 /* tp_new */
};
/* DukArray */
DukObject *DukArray_from_ctx(duk_context *ctx, duk_idx_t index)
{
DukObject *self;
DukContext *context = DukContext_get(ctx);
if (!context) {
PyErr_Format(PyExc_RuntimeError, "Unknown context %p", ctx);
return NULL;
}
self = PyObject_New(DukObject, &DukArray_Type);
if (self == NULL)
return NULL;
DukObject_INIT(self, context, index);
return self;
}
Py_ssize_t DukArray_length(DukObject *self)
{
duk_context *ctx = self->context->ctx;
duk_size_t len;
PUSH(self);
len = duk_get_length(ctx, -1);
duk_pop(ctx);
return (Py_ssize_t)len;
}
static PyObject *DukArray_getitem(DukObject *self, Py_ssize_t i)
{
duk_context *ctx = self->context->ctx;
PyObject *result;
PUSH(self);
duk_get_prop_index(ctx, -1, (duk_uarridx_t)i);
result = duk_to_python(ctx, -1);
if (!result)
duk_pop(ctx);
else
duk_pop_n(ctx, 2);
#if 0
if (result == Duk_undefined) {
Py_DECREF(result);
PyErr_Format(PyExc_IndexError, "%R has no index %li", self, i);
return NULL;
}
#endif
return result;
}
static int DukArray_setitem(DukObject *self, Py_ssize_t i, PyObject *value)
{
duk_context *ctx = self->context->ctx;
PUSH(self);
if (value) {
/* self[i] = value */
if (python_to_duk(ctx, value) == -1) {
duk_pop(ctx);
return -1;
}
duk_put_prop_index(ctx, -2, (duk_uarridx_t)i);
} else {
/* del self[i]
Note that this always succeeds, even if the index doesn't
exist.
*/
duk_del_prop_index(ctx, -1, (duk_uarridx_t)i);
duk_pop(ctx);
}
return 0;
}
static PyObject *DukArray_iter(DukObject *self)
{
duk_context *ctx = self->context->ctx;
PyObject *result;
PUSH(self);
duk_enum(ctx, -1, DUK_ENUM_ARRAY_INDICES_ONLY);
result = (PyObject *)DukEnum_from_DukContext(self->context, DUKENUM_VALUES);
duk_pop(ctx);
return result;
}
static PySequenceMethods DukArray_as_sequence = {
(lenfunc)DukArray_length, /* sq_length */
NULL, /* sq_concat */
NULL, /* sq_repeat */
(ssizeargfunc)DukArray_getitem, /* sq_item */
NULL, /* unused */
(ssizeobjargproc)DukArray_setitem, /* sq_ass_item */
NULL, /* sq_contains */
NULL, /* sq_inplace_concat */
NULL, /* sq_inplace_repeat */
};
PyTypeObject DukArray_Type = {
PyVarObject_HEAD_INIT(NULL, 0)
"Array proxy", /* tp_name */
sizeof(DukObject), /* tp_basicsize */
0, /* tp_itemsize */
(destructor)DukObject_dealloc, /* tp_dealloc */
0, /* tp_print */
0, /* tp_getattr */
0, /* tp_setattr */
0, /* tp_reserved */
0, /* tp_repr */
0, /* tp_as_number */
&DukArray_as_sequence, /* tp_as_sequence */
0, /* tp_as_mapping */
0, /* tp_hash */
0, /* tp_call */
0, /* tp_str */
(getattrofunc)DukObject_getattr, /* tp_getattro */
(setattrofunc)DukObject_setattr, /* tp_setattro */
0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT, /* tp_flags */
"Duktape array proxy" , /* tp_doc */
0, /* tp_traverse */
0, /* tp_clear */
0, /* tp_richcompare */
0, /* tp_weaklistoffset */
(getiterfunc)DukArray_iter /* tp_iter */
};
/* DukFunction */
DukObject *DukFunction_from_ctx(duk_context *ctx, duk_idx_t index)
{
DukObject *self;
DukContext *context = DukContext_get(ctx);
if (!context) {
PyErr_Format(PyExc_RuntimeError, "Unknown context %p", ctx);
return NULL;
}
self = PyObject_New(DukObject, &DukFunction_Type);
if (self == NULL)
return NULL;
DukObject_INIT(self, context, index);
return self;
}
PyObject* DukFunction_call(DukObject *self, PyObject *args, PyObject *kw)
{
duk_context *ctx = self->context->ctx;
Py_ssize_t nargs, i;
int return_none = 0;
PyObject *result;
/* NULL if no parent */
PyObject *this = (PyObject *)self->parent;
if (kw) {
PyObject *temp;
temp = PyDict_GetItemString(kw, "this");
if (temp)
this = temp;
temp = PyDict_GetItemString(kw, "return_none");
if (temp)
return_none = PyObject_IsTrue(temp);
}
nargs = PyTuple_Size(args);
/* Push the function */
PUSH(self);
if (this) {
/* Push the "this" binding */
if (python_to_duk(ctx, this) == -1) {
duk_pop(ctx);
return NULL;
}
}
/* Push args */
for (i = 0; i < nargs; i++) {
PyObject *arg = PyTuple_GetItem(args, i);
if (python_to_duk(ctx, arg) == -1) {
duk_pop_n(ctx, 1 + (this ? 1 : 0) + (duk_idx_t)i);
return NULL;
}
}
if (this)
duk_call_method(ctx, (duk_idx_t)nargs);
else
duk_call(ctx, (duk_idx_t)nargs);
if (return_none) {
/* Always return None. This saves converting the function's
return value. */
duk_pop(ctx);
Py_RETURN_NONE;
} else {
result = duk_to_python(ctx, -1);
duk_pop(ctx);
return result;
}
}
PyTypeObject DukFunction_Type = {
PyVarObject_HEAD_INIT(NULL, 0)
"Function proxy", /* tp_name */
sizeof(DukObject), /* tp_basicsize */
0, /* tp_itemsize */
(destructor)DukObject_dealloc, /* tp_dealloc */
0, /* tp_print */
0, /* tp_getattr */
0, /* tp_setattr */
0, /* tp_reserved */
0, /* tp_repr */
0, /* tp_as_number */
0, /* tp_as_sequence */
0, /* tp_as_mapping */
0, /* tp_hash */
(ternaryfunc)DukFunction_call, /* tp_call */
0, /* tp_str */
(getattrofunc)DukObject_getattr, /* tp_getattro */
(setattrofunc)DukObject_setattr, /* tp_setattro */
0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT, /* tp_flags */
"Duktape function proxy" /* tp_doc */
};
/* DukEnum */
DukEnum *DukEnum_from_DukContext(DukContext *context, dukenum_mode_t mode)
{
DukEnum *self;
self = PyObject_New(DukEnum, &DukEnum_Type);
if (self == NULL)
return NULL;
DukObject_INIT(&self->base, context, -1);
self->mode = mode;
return self;
}
static void DukEnum_dealloc(DukEnum *self)
{
DukObject_DESTRUCT(&self->base);
Py_TYPE(self)->tp_free((PyObject *)self);
}
static PyObject *DukEnum_iter(DukEnum *self)
{
Py_INCREF(self);
return (PyObject *)self;
}
static PyObject *DukEnum_iternext(DukEnum *self)
{
duk_context *ctx = self->base.context->ctx;
PyObject *result = NULL;
int mode = self->mode;
int get_value = mode == DUKENUM_VALUES || mode == DUKENUM_PAIRS;
int pop = 1;
PUSH(&self->base);
if (duk_next(ctx, -1, get_value)) {
switch (mode) {
case DUKENUM_KEYS:
result = duk_to_python(ctx, -1);
pop = 2;
break;
case DUKENUM_VALUES:
result = duk_to_python(ctx, -1);
pop = 3;
break;
case DUKENUM_PAIRS:
result = Py_BuildValue("(NN)",
duk_to_python(ctx, -2),
duk_to_python(ctx, -1));
pop = 3;
break;
}
}
duk_pop_n(ctx, pop);
return result;
}
PyTypeObject DukEnum_Type = {
PyVarObject_HEAD_INIT(NULL, 0)
"Enumerator", /* tp_name */
sizeof(DukEnum), /* tp_basicsize */
0, /* tp_itemsize */
(destructor)DukEnum_dealloc, /* tp_dealloc */
0, /* tp_print */
0, /* tp_getattr */
0, /* tp_setattr */
0, /* tp_reserved */
0, /* tp_repr */
0, /* tp_as_number */
0, /* tp_as_sequence */
0, /* tp_as_mapping */
0, /* tp_hash */
0, /* tp_call */
0, /* tp_str */
0, /* tp_getattro */
0, /* tp_setattro */
0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT, /* tp_flags */
"Duktape enumerator", /* tp_doc */
0, /* tp_traverse */
0, /* tp_clear */
0, /* tp_richcompare */
0, /* tp_weaklistoffset */
(getiterfunc)DukEnum_iter, /* tp_iter */
(iternextfunc)DukEnum_iternext, /* tp_iternext */
};

174
src/duktape/tests.py Normal file
View File

@ -0,0 +1,174 @@
import os
import sys
import unittest
from duktape import Context, undefined
class ContextTests(unittest.TestCase):
def setUp(self):
self.ctx = Context()
self.g = self.ctx.g
def test_create_context(self):
pass
def test_create_new_global_env(self):
new = self.ctx.new_global_env()
# The new context should have a distinct global object
self.g.a = 1
self.assertIs(new.g.a, undefined)
def test_eval(self):
pass
def test_eval_file(self):
pass
def test_undefined(self):
self.assertEqual(repr(undefined), 'undefined')
class ValueTests(unittest.TestCase):
def setUp(self):
self.ctx = Context()
self.g = self.ctx.g
def test_simple(self):
for value in [undefined, None, True, False]:
self.g.value = value
self.assertIs(self.g.value, value)
for value in ["foo", 42, 3.141592, 3.141592e20]:
self.g.value = value
self.assertEqual(self.g.value, value)
def test_object(self):
self.g.value = {}
self.assertEqual(dict(self.g.value), {})
self.g.value = {'a': 1}
self.assertEqual(dict(self.g.value), {'a': 1})
self.g.value = {'a': {'b': 2}}
self.assertEqual(dict(self.g.value.a), {'b': 2})
def test_array(self):
self.g.value = []
self.assertEqual(list(self.g.value), [])
self.g.value = [0, 1, 2]
self.assertEqual(self.g.value[0], 0)
self.assertEqual(self.g.value[1], 1)
self.assertEqual(self.g.value[2], 2)
self.assertEqual(self.g.value[3], undefined)
self.assertEqual(list(self.g.value), [0, 1, 2])
self.assertEqual(len(self.g.value), 3)
self.g.value[1] = 9
self.assertEqual(self.g.value[0], 0)
self.assertEqual(self.g.value[1], 9)
self.assertEqual(self.g.value[2], 2)
self.assertEqual(self.g.value[3], undefined)
self.assertEqual(list(self.g.value), [0, 9, 2])
self.assertEqual(len(self.g.value), 3)
def test_callable(self):
def f(x):
return x * x
num = sys.getrefcount(f)
self.g.func = f
self.assertEqual(sys.getrefcount(f), num + 1)
self.assertEqual(self.g.func(123), 15129)
self.g.func = undefined
self.assertEqual(sys.getrefcount(f), num)
a = 13450234
def rval():
return a
num = sys.getrefcount(a)
self.g.func = rval
self.assertEqual(self.g.eval('func()'), a)
self.assertEqual(sys.getrefcount(a), num)
def bad():
raise Exception('testing a python exception xyz')
self.g.func = bad
val = self.g.eval('try{func();}catch(err) {err.message}')
self.assertTrue('testing a python exception xyz' in val)
self.assertTrue('bad at 0x' in val)
def test_proxy(self):
self.g.obj1 = {'a': 42}
self.g.obj2 = self.g.obj1
self.assertEqual(self.g.obj1.a, self.g.obj2.a)
class EvalTests(unittest.TestCase):
def setUp(self):
self.ctx = Context()
self.g = self.ctx.g
self.testfile = 'dukpy_test.js'
with open(self.testfile, 'w') as fobj:
fobj.write('1+1')
def tearDown(self):
os.remove(self.testfile)
def test_eval_invalid_args(self):
with self.assertRaises(TypeError):
self.ctx.eval()
with self.assertRaises(TypeError):
self.ctx.eval(123)
def test_eval(self):
self.assertEqual(self.ctx.eval("1+1"), 2)
def test_eval_kwargs(self):
self.assertEqual(self.ctx.eval(code="1+1"), 2)
def test_eval_noreturn(self):
self.assertIsNone(self.ctx.eval("1+1", noreturn=True))
def test_eval_file_invalid_args(self):
with self.assertRaises(TypeError):
self.ctx.eval_file()
with self.assertRaises(TypeError):
self.ctx.eval_file(123)
def test_eval_file(self):
self.assertEqual(self.ctx.eval_file(self.testfile), 2)
def test_eval_file_kwargs(self):
self.assertEqual(self.ctx.eval_file(path=self.testfile), 2)
def test_eval_file_noreturn(self):
self.assertIsNone(self.ctx.eval_file(self.testfile, noreturn=True))
def load_tests(loader, suite, pattern):
for x in globals().itervalues():
if isinstance(x, type) and issubclass(x, unittest.TestCase):
tests = loader.loadTestsFromTestCase(x)
suite.addTests(tests)
return suite
class TestRunner(unittest.main):
def createTests(self):
tl = unittest.TestLoader()
suite = unittest.TestSuite()
self.test = load_tests(tl, suite, None)
def run(verbosity=4):
TestRunner(verbosity=verbosity, exit=False)
def test_build():
result = TestRunner(verbosity=0, buffer=True, catchbreak=True, failfast=True, argv=sys.argv[:1], exit=False).result
if not result.wasSuccessful():
raise SystemExit(1)
if __name__ == '__main__':
run()