Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .travis.yml
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
dist: xenial
language: python
python:
- "2.6"
- "2.7"
- "3.3"
- "3.4"
- "3.5"
- "3.6"
- "3.7"
- "3.8-dev"
# command to install dependencies!
install: "pip install -r requirements.txt && pip install numpy"
# command to run tests!
Expand Down
130 changes: 105 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,15 +64,10 @@ Every feature of ECMA 5.1 is implemented (except of 'with' statement):
```
Unfortunately even though Js2Py can be generally used to translate huge Js files (over 50k lines long), in rare cases you may encounter some unexpected problems (like javascript calling a function with 300 arguments - python allows only 255). These problems are very hard to fix with current translation approach. I will try to implement an interpreter in near future which will hopefully fix all the edge cases.

### JavaScript 'VirtualMachine' in Python

If the translator for some reason does not work for you, you can try the new JS VM:

```python
from js2py.internals import seval
seval.eval_js_vm(code)
```
#### Installation

pip install js2py

<hr>

#### More advanced usage example
Expand Down Expand Up @@ -110,26 +105,95 @@ function bind(thisArg) { [python code] }
6
```

You can also enable require support in JavaScript like this:

```python
>>> context = js2py.EvalJs(enable_require=True)
>>> context.eval("require('esprima').parse('var a = 1')")
```
<hr>

### JavaScript 'VirtualMachine' in Python

As a fun experimental project I have also implemented a VM-based JavaScript
(yes - there are 2 separate JS implementations in this repo). It is feature complete and faster than the translation based version.
Below you can see a demo with a nice debug view (bytecode + execution sequence):

```python
>>> from js2py.internals import seval
>>> seval.eval_js_vm("try {throw 3+3} catch (e) {console.log(e)}", debug=True)
[LOAD_UNDEFINED(),
JUMP(4,),
LABEL(1,),
LOAD_UNDEFINED(),
POP(),
LOAD_NUMBER(3.0,),
LOAD_NUMBER(3.0,),
BINARY_OP('+',),
THROW(),
NOP(),
LABEL(2,),
LOAD_UNDEFINED(),
POP(),
LOAD('console',),
LOAD('e',),
LOAD_N_TUPLE(1,),
CALL_METHOD_DOT('log',),
NOP(),
LABEL(3,),
LOAD_UNDEFINED(),
NOP(),
LABEL(4,),
TRY_CATCH_FINALLY(1, 2, 'e', 3, False, 4)]

0 LOAD_UNDEFINED()
1 JUMP(4,)
18 TRY_CATCH_FINALLY(1, 2, 'e', 3, False, 4)
ctx entry (from:2, to:9)
2 LOAD_UNDEFINED()
3 POP()
4 LOAD_NUMBER(3.0,)
5 LOAD_NUMBER(3.0,)
6 BINARY_OP('+',)
7 THROW()
ctx exit (js errors)
ctx entry (from:9, to:16)
9 LOAD_UNDEFINED()
10 POP()
11 LOAD('console',)
12 LOAD('e',)
13 LOAD_N_TUPLE(1,)
14 CALL_METHOD_DOT('log',)
6
15 NOP()
ctx exit (normal)

```

This is just a curiosity and I do not recommend using VM in practice (requires more polishing).

<hr>

#### Limitations

It has only 3 known limitations:
There are 3 main limitations:
<ul>
<li>"strict mode" is ignored</li>
<li>with statement is not supported</li>
<li>Indirect call to eval is treated as direct call to eval (hence always evals in local scope)</li>
</ul>

Please let me know if you find any bugs - they will be fixed within 48 hours.
They are generally not a big issue in practice.
In practice more problematic are minor edge cases that unfortunately
sometimes do happen. Please report a bug if you find one.

Js2Py was able to successfully
translate and run huge JS libraries like Babel (100k+ loc), esprima, crypto-js and more.
You can try it yourself by importing any supported npm package via `js2py.require('your_package')`.

<hr>

#### Installation

pip install js2py

<hr>

#### Other Examples

Expand Down Expand Up @@ -180,26 +244,42 @@ Also, of course you can use Js2Py to parse (tree is the same as in esprima.js) a
#### Parsing:
```python
>>> js2py.parse_js('var $ = 5')
{'body': [{'kind': 'var', 'declarations': [{'init': {'raw': None, 'type': u'Literal', 'value': 5.0}, 'type': u'VariableDeclarator', 'id': {'type': u'Identifier', 'name': u'$'}}], 'type': u'VariableDeclaration'}], 'type': u'Program'}
{
"body": [
{
"declarations": [
{
"id": {
"name": "$",
"type": "Identifier"
},
"init": {
"raw": "5",
"type": "Literal",
"value": 5
},
"type": "VariableDeclarator"
}
],
"kind": "var",
"type": "VariableDeclaration"
}
],
"type": "Program"
}
```
#### Translating:

```python
>>> print js2py.translate_js('var $ = 5')
import js2py.pyjs, sys
# Redefine builtin objects... Do you have a better idea?
for m in sys.modules.keys():
if m.startswith('js2py'):
del sys.modules[m]
del js2py.pyjs
del js2py
>>> print(js2py.translate_js('var $ = 5'))
from js2py.pyjs import *
# setting scope
var = Scope( JS_BUILTINS )
set_global_object(var)

# Code follows:
var.registers([u'$'])
var.put(u'$', Js(5.0))
var.registers(['$'])
var.put('$', Js(5.0))
```
<hr>

Expand Down
23 changes: 11 additions & 12 deletions js2py/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from .translators.friendly_nodes import REGEXP_CONVERTER
from .utils.injector import fix_js_args
from types import FunctionType, ModuleType, GeneratorType, BuiltinFunctionType, MethodType, BuiltinMethodType
from math import floor, log10
import traceback
try:
import numpy
Expand Down Expand Up @@ -50,7 +51,12 @@ def to_python(val):
return val.value
elif isinstance(val, PyObjectWrapper):
return val.__dict__['obj']
return JsObjectWrapper(val)
elif isinstance(val, PyJsArray) and val.CONVERT_TO_PY_PRIMITIVES:
return to_list(val)
elif isinstance(val, PyJsObject) and val.CONVERT_TO_PY_PRIMITIVES:
return to_dict(val)
else:
return JsObjectWrapper(val)


def to_dict(js_obj,
Expand Down Expand Up @@ -255,6 +261,7 @@ class PyJs(object):
own = {}
GlobalObject = None
IS_CHILD_SCOPE = False
CONVERT_TO_PY_PRIMITIVES = False
value = None
buff = None

Expand Down Expand Up @@ -597,15 +604,7 @@ def to_string(self):
elif typ == 'Boolean':
return Js('true') if self.value else Js('false')
elif typ == 'Number': #or self.Class=='Number':
if self.is_nan():
return Js('NaN')
elif self.is_infinity():
sign = '-' if self.value < 0 else ''
return Js(sign + 'Infinity')
elif isinstance(self.value,
long) or self.value.is_integer(): # dont print .0
return Js(unicode(int(self.value)))
return Js(unicode(self.value)) # accurate enough
return Js(unicode(js_dtoa(self.value)))
elif typ == 'String':
return self
else: #object
Expand Down Expand Up @@ -1040,7 +1039,7 @@ def PyJsComma(a, b):
return b


from .internals.simplex import JsException as PyJsException
from .internals.simplex import JsException as PyJsException, js_dtoa
import pyjsparser
pyjsparser.parser.ENABLE_JS2PY_ERRORS = lambda msg: MakeError('SyntaxError', msg)

Expand Down Expand Up @@ -1250,7 +1249,7 @@ def __repr__(self):
'Uint16Array', 'Int32Array', 'Uint32Array',
'Float32Array', 'Float64Array', 'Arguments'):
return repr(self.to_list())
return repr(self.to_dict())
return str([(i[0], type(i[1]).__name__) for i in self.to_dict().items()])

def __len__(self):
return len(self._obj)
Expand Down
32 changes: 24 additions & 8 deletions js2py/evaljs.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,36 +116,52 @@ def eval_js(js):


def eval_js6(js):
"""Just like eval_js but with experimental support for js6 via babel."""
return eval_js(js6_to_js5(js))


def translate_js6(js):
"""Just like translate_js but with experimental support for js6 via babel."""
return translate_js(js6_to_js5(js))


class EvalJs(object):
"""This class supports continuous execution of javascript under same context.

>>> js = EvalJs()
>>> js.execute('var a = 10;function f(x) {return x*x};')
>>> js.f(9)
>>> ctx = EvalJs()
>>> ctx.execute('var a = 10;function f(x) {return x*x};')
>>> ctx.f(9)
81
>>> js.a
>>> ctx.a
10

context is a python dict or object that contains python variables that should be available to JavaScript
For example:
>>> js = EvalJs({'a': 30})
>>> js.execute('var x = a')
>>> js.x
>>> ctx = EvalJs({'a': 30})
>>> ctx.execute('var x = a')
>>> ctx.x
30

You can enable JS require function via enable_require. With this feature enabled you can use js modules
from npm, for example:
>>> ctx = EvalJs(enable_require=True)
>>> ctx.execute("var esprima = require('esprima');")
>>> ctx.execute("esprima.parse('var a = 1')")

You can run interactive javascript console with console method!"""

def __init__(self, context={}):
def __init__(self, context={}, enable_require=False):
self.__dict__['_context'] = {}
exec (DEFAULT_HEADER, self._context)
self.__dict__['_var'] = self._context['var'].to_python()

if enable_require:
def _js_require_impl(npm_module_name):
from .node_import import require
from .base import to_python
return require(to_python(npm_module_name), context=self._context)
setattr(self._var, 'require', _js_require_impl)

if not isinstance(context, dict):
try:
context = context.__dict__
Expand Down
7 changes: 4 additions & 3 deletions js2py/host/console.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
from ..base import *


@Js
def console():
pass


@Js
def log():
print(arguments[0])


console.put('log', log)
console.put('debug', log)
console.put('info', log)
console.put('warn', log)
console.put('error', log)
18 changes: 11 additions & 7 deletions js2py/internals/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,19 @@

import datetime

from desc import *
from simplex import *
from conversions import *
import six
from .desc import *
from .simplex import *
from .conversions import *

from pyjsparser import PyJsParser
from itertools import izip

from conversions import *
from simplex import *
import six
if six.PY2:
from itertools import izip
else:
izip = zip




def Type(obj):
Expand Down
Loading