Simple JavaScript interpreter for Python
Go to file
Alessandro Molina 5b06de69b8 Experimental support for 'require' in js code 2016-04-09 21:49:58 +02:00
dukpy Experimental support for 'require' in js code 2016-04-09 21:49:58 +02:00
src Cope with repr that returns bytes 2016-04-09 20:54:47 +02:00
tests Experimental support for 'require' in js code 2016-04-09 21:49:58 +02:00
.gitignore Initial commit 2015-03-09 12:25:33 +01:00
.travis.yml Add webassets dependency on travis 2015-11-09 12:32:03 +01:00
LICENSE Initial commit 2015-03-09 12:25:33 +01:00
MANIFEST.in Include .h files in distribution 2015-11-22 19:15:00 +01:00
README.rst For coherence rename method to evaljs 2016-04-01 22:28:14 +02:00
setup.py Experimental support for 'require' in js code 2016-04-09 21:49:58 +02:00

README.rst

dukpy
=====

.. image:: https://travis-ci.org/amol-/dukpy.png?branch=master
    :target: https://travis-ci.org/amol-/dukpy

.. image:: https://coveralls.io/repos/amol-/dukpy/badge.png?branch=master
    :target: https://coveralls.io/r/amol-/dukpy?branch=master

DukPy is a simple javascript interpreter for Python built on top of
duktape engine **without any external dependency**.
It comes with a bunch of common transpilers built-in for convenience:

    - *CoffeeScript*
    - *BabelJS*
    - *TypeScript*

Dukpy has been tested on **Python 2.7** and **Python 3.4**, dukpy
is currently not production ready and might actually crash your
program as it is mostly implemented in C.

CoffeeScript Compiler
---------------------

Using the coffeescript compiler is as easy as running:

.. code:: python

    >>> import dukpy
    >>> dukpy.coffee_compile('''
    ...     fill = (container, liquid = "coffee") ->
    ...         "Filling the #{container} with #{liquid}..."
    ... ''')
    '(function() {\n  var fill;\n\n  fill = function*(container, liquid) {\n    if (liquid == null) {\n      liquid = "coffee";\n    }\n    return "Filling the " + container + " with " + liquid + "...";\n  };\n\n}).call(this);\n'

TypeScript Transpiler
---------------------

The TypeScript compiler can be used through the
``dukpy.typescript_compile`` function:

.. code:: python

    >>> import dukpy
    >>> dukpy.typescript_compile('''
    ... class Greeter {
    ...     constructor(public greeting: string) { }
    ...     greet() {
    ...         return "<h1>" + this.greeting + "</h1>";
    ...     }
    ... };
    ...
    ... var greeter = new Greeter("Hello, world!");
    ... ''')
    'var Greeter = (function () {\n    function Greeter(greeting) {\n        this.greeting = greeting;\n    }\n    Greeter.prototype.greet = function () {\n        return "<h1>" + this.greeting + "</h1>";\n    };\n    return Greeter;\n})();\n;\nvar greeter = new Greeter("Hello, world!");\n'

Currently the compiler has built-in options and doesn't accept additional ones,

The DukPY based TypeScript compiler also provides a WebAssets (
http://webassets.readthedocs.org/en/latest/ ) filter to automatically
compile TypeScript code in your assets pipeline.  You register this filter as
``typescript`` within WebAssets using:

.. code:: python

    from webassets.filter import register_filter
    from dukpy.webassets import TypeScript

    register_filter(TypeScript)

Which makes the filter available with the ``typescript`` name.

**NOTE:** When using the TypeScript compiler for code that needs to run
in the browser, make sure to add
https://cdnjs.cloudflare.com/ajax/libs/systemjs/0.19.24/system.js
dependency. As ``import`` statements are resolved using SystemJS.

EcmaScript6 BabelJS Transpiler
------------------------------

To compile ES6 code to ES5 for everyday usage you can use
``dukpy.babel_compile``:

.. code:: python

    >>> import dukpy
    >>> dukpy.babel_compile('''
    ... class Point {
    ...     constructor(x, y) {
    ...             this.x = x;
    ...         this.y = y;
    ...         }
    ...         toString() {
    ...             return '(' + this.x + ', ' + this.y + ')';
    ...         }
    ... }
    ... ''')
    '"use strict";\n\nvar _prototypeProperties = function (child, staticProps, instanceProps) { if (staticProps) Object.defineProperties(child, staticProps); if (instanceProps) Object.defineProperties(child.prototype, instanceProps); };\n\nvar _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } };\n\nvar Point = (function () {\n    function Point(x, y) {\n        _classCallCheck(this, Point);\n\n        this.x = x;\n        this.y = y;\n    }\n\n    _prototypeProperties(Point, null, {\n        toString: {\n            value: function toString() {\n                return "(" + this.x + ", " + this.y + ")";\n            },\n            writable: true,\n            configurable: true\n        }\n    });\n\n    return Point;\n})();\n'

You  can pass `options`__ to the BabelJS compiler just as keywords on
the call to ``babel_compile()``.

__ http://babeljs.io/docs/usage/options/

The DukPY based BabelJS compiler also provides a WebAssets (
http://webassets.readthedocs.org/en/latest/ ) filter to automatically
compile ES6 code in your assets pipeline.  You register this filter as
``babeljs`` within WebAssets using:

.. code:: python

    from webassets.filter import register_filter
    from dukpy.webassets import BabelJS

    register_filter(BabelJS)

Which makes the filter available with the ``babeljs`` name.

**NOTE:** When using the BabelJS compiler for code that needs to run
in the browser, make sure to add
https://cdnjs.cloudflare.com/ajax/libs/babel-polyfill/6.6.1/polyfill.min.js
dependency.


Using the JavaScript Interpreter
--------------------------------

Using dukpy is as simple as calling the ``dukpy.evaljs`` function with
the javascript code:

.. code:: python

    >>> import dukpy
    >>> dukpy.evaljs("var o = {'value': 5}; o['value'] += 3; o")
    {'value': 8}


The ``evaljs`` function executes the javascript and returns the
resulting value as far as it is possible to encode it in JSON.

If execution fails a ``dukpy.JSRuntimeError`` exception is raised
with the failure reason.

Passing Arguments
~~~~~~~~~~~~~~~~~

Any argument passed to ``evaljs`` is available in JavaScript inside
the ``dukpy`` object in javascript. It must be possible to encode
the arguments using JSON for them to be available in Javascript:

.. code:: python

    >>> import dukpy
    >>>
    >>> def sum3(value):
    ...     return dukpy.evaljs("dukpy['value'] + 3", value=value)
    ...
    >>> sum3(7)
    10

Running Multiple Scripts
~~~~~~~~~~~~~~~~~~~~~~~~

The ``evaljs`` function supports providing multiple source codes to
be executed in the same context.

Multiple script can be passed in a list or tuple:

.. code:: python

    >>> import dukpy
    >>> dukpy.evaljs(["var o = {'value': 5}",
    ...               "o['value'] += 3",
    ...               "o"])
    {'value': 8}

This is useful when your code requires dependencies to work,
as you can load the dependency and then your code.

This is actually how the coffeescript compiler is implemented
by DukPy itself:

.. code:: python

    def coffee_compile(source):
        with open(COFFEE_COMPILER, 'r') as coffeescript_js:
            return evaljs((coffeescript_js.read(), 'CoffeeScript.compile(dukpy.coffeecode)'),
                          coffeecode=source)

Using a persistent JavaScript Interpreter
-----------------------------------------

The ``evaljs`` function creates a new interpreter on each call,
this is usually convenient and avoid errors due to dirt global variables
or unexpected execution status.

In some cases you might want to run code that has a slow bootstrap, so
it's convenient to reuse the same interpreter between two different calls
so that the bootstrap cost has already been paid during the first execution.

This can be achieved by using the ``dukpy.JSInterpreter`` object.

Creating a ``dukpy.JSInterpreter`` permits to evaluate code inside that interpreter
and multiple ``eval`` calls will share the same interpreter and global status:


.. code:: python

    >>> import dukpy
    >>> interpreter = dukpy.JSInterpreter()
    >>> interpreter.evaljs("var o = {'value': 5}; o")
    {u'value': 5}
    >>> interpreter.evaljs("o.value += 1; o")
    {u'value': 6}