'
- patron = r'SRC="([^"]+)"'
- links = re.findall(patron, block, re.IGNORECASE)
- if "#" in links:
- links = link.replace('#', 'speedvideo.net')
+ links = str(support.match(item, r'SRC="([^"]+)"', patronBlock=r'
(.+?)
')[0])
+ if links:
+ links = links.replace('#', 'speedvideo.net')
return support.server(item, links)
else:
return support.server(item)
diff --git a/lib/js2py/base.py b/lib/js2py/base.py
index 67c80d59..cf1eca08 100644
--- a/lib/js2py/base.py
+++ b/lib/js2py/base.py
@@ -5,6 +5,7 @@ import re
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
@@ -603,15 +604,7 @@ class PyJs(object):
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
@@ -1046,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)
diff --git a/lib/js2py/constructors/time_helpers.py b/lib/js2py/constructors/time_helpers.py
index a744d8ca..8c42a120 100644
--- a/lib/js2py/constructors/time_helpers.py
+++ b/lib/js2py/constructors/time_helpers.py
@@ -4,7 +4,7 @@ import datetime
import warnings
try:
- from tzlocal import get_localzone
+ from lib.tzlocal import get_localzone
LOCAL_ZONE = get_localzone()
except: # except all problems...
warnings.warn(
diff --git a/lib/js2py/evaljs.py b/lib/js2py/evaljs.py
index 3f5eeee5..ef4d7d95 100644
--- a/lib/js2py/evaljs.py
+++ b/lib/js2py/evaljs.py
@@ -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__
diff --git a/lib/js2py/internals/base.py b/lib/js2py/internals/base.py
index ec277c64..a02a2122 100644
--- a/lib/js2py/internals/base.py
+++ b/lib/js2py/internals/base.py
@@ -3,15 +3,19 @@ import re
import datetime
-from desc import *
-from simplex import *
-from conversions import *
-import six
-from pyjsparser import PyJsParser
-from itertools import izip
+from .desc import *
+from .simplex import *
+from .conversions import *
+
+from pyjsparser import PyJsParser
+
+import six
+if six.PY2:
+ from itertools import izip
+else:
+ izip = zip
+
-from conversions import *
-from simplex import *
def Type(obj):
diff --git a/lib/js2py/internals/byte_trans.py b/lib/js2py/internals/byte_trans.py
index 87fab4b4..e32bcb1e 100644
--- a/lib/js2py/internals/byte_trans.py
+++ b/lib/js2py/internals/byte_trans.py
@@ -1,8 +1,8 @@
-from code import Code
-from simplex import MakeError
-from opcodes import *
-from operations import *
-from trans_utils import *
+from .code import Code
+from .simplex import MakeError
+from .opcodes import *
+from .operations import *
+from .trans_utils import *
SPECIAL_IDENTIFIERS = {'true', 'false', 'this'}
@@ -465,10 +465,11 @@ class ByteCodeGenerator:
self.emit('LOAD_OBJECT', tuple(data))
def Program(self, body, **kwargs):
+ old_tape_len = len(self.exe.tape)
self.emit('LOAD_UNDEFINED')
self.emit(body)
# add function tape !
- self.exe.tape = self.function_declaration_tape + self.exe.tape
+ self.exe.tape = self.exe.tape[:old_tape_len] + self.function_declaration_tape + self.exe.tape[old_tape_len:]
def Pyimport(self, imp, **kwargs):
raise NotImplementedError(
@@ -735,17 +736,17 @@ def main():
#
# }
a.emit(d)
- print a.declared_vars
- print a.exe.tape
- print len(a.exe.tape)
+ print(a.declared_vars)
+ print(a.exe.tape)
+ print(len(a.exe.tape))
a.exe.compile()
def log(this, args):
- print args[0]
+ print(args[0])
return 999
- print a.exe.run(a.exe.space.GlobalObj)
+ print(a.exe.run(a.exe.space.GlobalObj))
if __name__ == '__main__':
diff --git a/lib/js2py/internals/code.py b/lib/js2py/internals/code.py
index 6bd6739f..9af0e602 100644
--- a/lib/js2py/internals/code.py
+++ b/lib/js2py/internals/code.py
@@ -1,16 +1,17 @@
-from opcodes import *
-from space import *
-from base import *
+from .opcodes import *
+from .space import *
+from .base import *
class Code:
'''Can generate, store and run sequence of ops representing js code'''
- def __init__(self, is_strict=False):
+ def __init__(self, is_strict=False, debug_mode=False):
self.tape = []
self.compiled = False
self.label_locs = None
self.is_strict = is_strict
+ self.debug_mode = debug_mode
self.contexts = []
self.current_ctx = None
@@ -22,6 +23,10 @@ class Code:
self.GLOBAL_THIS = None
self.space = None
+ # dbg
+ self.ctx_depth = 0
+
+
def get_new_label(self):
self._label_count += 1
return self._label_count
@@ -74,21 +79,35 @@ class Code:
# 0=normal, 1=return, 2=jump_outside, 3=errors
# execute_fragment_under_context returns:
# (return_value, typ, return_value/jump_loc/py_error)
- # ctx.stack must be len 1 and its always empty after the call.
+ # IMPARTANT: It is guaranteed that the length of the ctx.stack is unchanged.
'''
old_curr_ctx = self.current_ctx
+ self.ctx_depth += 1
+ old_stack_len = len(ctx.stack)
+ old_ret_len = len(self.return_locs)
+ old_ctx_len = len(self.contexts)
try:
self.current_ctx = ctx
return self._execute_fragment_under_context(
ctx, start_label, end_label)
except JsException as err:
- # undo the things that were put on the stack (if any)
- # don't worry, I know the recovery is possible through try statement and for this reason try statement
- # has its own context and stack so it will not delete the contents of the outer stack
- del ctx.stack[:]
+ if self.debug_mode:
+ self._on_fragment_exit("js errors")
+ # undo the things that were put on the stack (if any) to ensure a proper error recovery
+ del ctx.stack[old_stack_len:]
+ del self.return_locs[old_ret_len:]
+ del self.contexts[old_ctx_len :]
return undefined, 3, err
finally:
+ self.ctx_depth -= 1
self.current_ctx = old_curr_ctx
+ assert old_stack_len == len(ctx.stack)
+
+ def _get_dbg_indent(self):
+ return self.ctx_depth * ' '
+
+ def _on_fragment_exit(self, mode):
+ print(self._get_dbg_indent() + 'ctx exit (%s)' % mode)
def _execute_fragment_under_context(self, ctx, start_label, end_label):
start, end = self.label_locs[start_label], self.label_locs[end_label]
@@ -97,16 +116,20 @@ class Code:
entry_level = len(self.contexts)
# for e in self.tape[start:end]:
# print e
-
+ if self.debug_mode:
+ print(self._get_dbg_indent() + 'ctx entry (from:%d, to:%d)' % (start, end))
while loc < len(self.tape):
- #print loc, self.tape[loc]
if len(self.contexts) == entry_level and loc >= end:
+ if self.debug_mode:
+ self._on_fragment_exit('normal')
assert loc == end
- assert len(ctx.stack) == (
- 1 + initial_len), 'Stack change must be equal to +1!'
+ delta_stack = len(ctx.stack) - initial_len
+ assert delta_stack == +1, 'Stack change must be equal to +1! got %d' % delta_stack
return ctx.stack.pop(), 0, None # means normal return
# execute instruction
+ if self.debug_mode:
+ print(self._get_dbg_indent() + str(loc), self.tape[loc])
status = self.tape[loc].eval(ctx)
# check status for special actions
@@ -116,9 +139,10 @@ class Code:
if len(self.contexts) == entry_level:
# check if jumped outside of the fragment and break if so
if not start <= loc < end:
- assert len(ctx.stack) == (
- 1 + initial_len
- ), 'Stack change must be equal to +1!'
+ if self.debug_mode:
+ self._on_fragment_exit('jump outside loc:%d label:%d' % (loc, status))
+ delta_stack = len(ctx.stack) - initial_len
+ assert delta_stack == +1, 'Stack change must be equal to +1! got %d' % delta_stack
return ctx.stack.pop(), 2, status # jump outside
continue
@@ -137,7 +161,10 @@ class Code:
# return: (None, None)
else:
if len(self.contexts) == entry_level:
- assert len(ctx.stack) == 1 + initial_len
+ if self.debug_mode:
+ self._on_fragment_exit('return')
+ delta_stack = len(ctx.stack) - initial_len
+ assert delta_stack == +1, 'Stack change must be equal to +1! got %d' % delta_stack
return undefined, 1, ctx.stack.pop(
) # return signal
return_value = ctx.stack.pop()
@@ -149,6 +176,8 @@ class Code:
continue
# next instruction
loc += 1
+ if self.debug_mode:
+ self._on_fragment_exit('internal error - unexpected end of tape, will crash')
assert False, 'Remember to add NOP at the end!'
def run(self, ctx, starting_loc=0):
@@ -156,7 +185,8 @@ class Code:
self.current_ctx = ctx
while loc < len(self.tape):
# execute instruction
- #print loc, self.tape[loc]
+ if self.debug_mode:
+ print(loc, self.tape[loc])
status = self.tape[loc].eval(ctx)
# check status for special actions
diff --git a/lib/js2py/internals/constructors/jsfunction.py b/lib/js2py/internals/constructors/jsfunction.py
index 9728fb38..d62731ac 100644
--- a/lib/js2py/internals/constructors/jsfunction.py
+++ b/lib/js2py/internals/constructors/jsfunction.py
@@ -42,6 +42,7 @@ def executable_code(code_str, space, global_context=True):
space.byte_generator.emit('LABEL', skip)
space.byte_generator.emit('NOP')
space.byte_generator.restore_state()
+
space.byte_generator.exe.compile(
start_loc=old_tape_len
) # dont read the code from the beginning, dont be stupid!
@@ -71,5 +72,5 @@ def _eval(this, args):
def log(this, args):
- print ' '.join(map(to_string, args))
+ print(' '.join(map(to_string, args)))
return undefined
diff --git a/lib/js2py/internals/conversions.py b/lib/js2py/internals/conversions.py
index b90a427d..8b2c7c30 100644
--- a/lib/js2py/internals/conversions.py
+++ b/lib/js2py/internals/conversions.py
@@ -1,6 +1,6 @@
from __future__ import unicode_literals
# Type Conversions. to_type. All must return PyJs subclass instance
-from simplex import *
+from .simplex import *
def to_primitive(self, hint=None):
@@ -73,14 +73,7 @@ def to_string(self):
elif typ == 'Boolean':
return 'true' if self else 'false'
elif typ == 'Number': # or self.Class=='Number':
- if is_nan(self):
- return 'NaN'
- elif is_infinity(self):
- sign = '-' if self < 0 else ''
- return sign + 'Infinity'
- elif int(self) == self: # integer value!
- return unicode(int(self))
- return unicode(self) # todo make it print exactly like node.js
+ return js_dtoa(self)
else: # object
return to_string(to_primitive(self, 'String'))
diff --git a/lib/js2py/internals/fill_space.py b/lib/js2py/internals/fill_space.py
index 9aa9c4d2..329c8b28 100644
--- a/lib/js2py/internals/fill_space.py
+++ b/lib/js2py/internals/fill_space.py
@@ -1,29 +1,22 @@
from __future__ import unicode_literals
-from base import Scope
-from func_utils import *
-from conversions import *
+from .base import Scope
+from .func_utils import *
+from .conversions import *
import six
-from prototypes.jsboolean import BooleanPrototype
-from prototypes.jserror import ErrorPrototype
-from prototypes.jsfunction import FunctionPrototype
-from prototypes.jsnumber import NumberPrototype
-from prototypes.jsobject import ObjectPrototype
-from prototypes.jsregexp import RegExpPrototype
-from prototypes.jsstring import StringPrototype
-from prototypes.jsarray import ArrayPrototype
-import prototypes.jsjson as jsjson
-import prototypes.jsutils as jsutils
+from .prototypes.jsboolean import BooleanPrototype
+from .prototypes.jserror import ErrorPrototype
+from .prototypes.jsfunction import FunctionPrototype
+from .prototypes.jsnumber import NumberPrototype
+from .prototypes.jsobject import ObjectPrototype
+from .prototypes.jsregexp import RegExpPrototype
+from .prototypes.jsstring import StringPrototype
+from .prototypes.jsarray import ArrayPrototype
+from .prototypes import jsjson
+from .prototypes import jsutils
+
+from .constructors import jsnumber, jsstring, jsarray, jsboolean, jsregexp, jsmath, jsobject, jsfunction, jsconsole
-from constructors import jsnumber
-from constructors import jsstring
-from constructors import jsarray
-from constructors import jsboolean
-from constructors import jsregexp
-from constructors import jsmath
-from constructors import jsobject
-from constructors import jsfunction
-from constructors import jsconsole
def fill_proto(proto, proto_class, space):
@@ -155,7 +148,10 @@ def fill_space(space, byte_generator):
j = easy_func(creator, space)
j.name = unicode(typ)
- j.prototype = space.ERROR_TYPES[typ]
+
+ set_protected(j, 'prototype', space.ERROR_TYPES[typ])
+
+ set_non_enumerable(space.ERROR_TYPES[typ], 'constructor', j)
def new_create(args, space):
message = get_arg(args, 0)
@@ -178,6 +174,7 @@ def fill_space(space, byte_generator):
setattr(space, err_type_name + u'Prototype', extra_err)
error_constructors[err_type_name] = construct_constructor(
err_type_name)
+
assert space.TypeErrorPrototype is not None
# RegExp
diff --git a/lib/js2py/internals/func_utils.py b/lib/js2py/internals/func_utils.py
index 3c0b8d57..58dfef9e 100644
--- a/lib/js2py/internals/func_utils.py
+++ b/lib/js2py/internals/func_utils.py
@@ -1,5 +1,5 @@
-from simplex import *
-from conversions import *
+from .simplex import *
+from .conversions import *
import six
if six.PY3:
diff --git a/lib/js2py/internals/opcodes.py b/lib/js2py/internals/opcodes.py
index 0f3127db..15c57ccd 100644
--- a/lib/js2py/internals/opcodes.py
+++ b/lib/js2py/internals/opcodes.py
@@ -1,5 +1,5 @@
-from operations import *
-from base import get_member, get_member_dot, PyJsFunction, Scope
+from .operations import *
+from .base import get_member, get_member_dot, PyJsFunction, Scope
class OP_CODE(object):
diff --git a/lib/js2py/internals/operations.py b/lib/js2py/internals/operations.py
index d9875088..35b90179 100644
--- a/lib/js2py/internals/operations.py
+++ b/lib/js2py/internals/operations.py
@@ -1,6 +1,6 @@
from __future__ import unicode_literals
-from simplex import *
-from conversions import *
+from .simplex import *
+from .conversions import *
# ------------------------------------------------------------------------------
# Unary operations
diff --git a/lib/js2py/internals/prototypes/jsstring.py b/lib/js2py/internals/prototypes/jsstring.py
index b56246e2..be38802e 100644
--- a/lib/js2py/internals/prototypes/jsstring.py
+++ b/lib/js2py/internals/prototypes/jsstring.py
@@ -4,7 +4,7 @@ from __future__ import unicode_literals
import re
from ..conversions import *
from ..func_utils import *
-from jsregexp import RegExpExec
+from .jsregexp import RegExpExec
DIGS = set(u'0123456789')
WHITE = u"\u0009\u000A\u000B\u000C\u000D\u0020\u00A0\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF"
diff --git a/lib/js2py/internals/seval.py b/lib/js2py/internals/seval.py
index c4404ab7..cd8ea50f 100644
--- a/lib/js2py/internals/seval.py
+++ b/lib/js2py/internals/seval.py
@@ -1,11 +1,9 @@
import pyjsparser
-from space import Space
-import fill_space
-from byte_trans import ByteCodeGenerator
-from code import Code
-from simplex import MakeError
-import sys
-sys.setrecursionlimit(100000)
+from .space import Space
+from . import fill_space
+from .byte_trans import ByteCodeGenerator
+from .code import Code
+from .simplex import *
pyjsparser.parser.ENABLE_JS2PY_ERRORS = lambda msg: MakeError(u'SyntaxError', unicode(msg))
@@ -16,8 +14,8 @@ def get_js_bytecode(js):
a.emit(d)
return a.exe.tape
-def eval_js_vm(js):
- a = ByteCodeGenerator(Code())
+def eval_js_vm(js, debug=False):
+ a = ByteCodeGenerator(Code(debug_mode=debug))
s = Space()
a.exe.space = s
s.exe = a.exe
@@ -26,7 +24,10 @@ def eval_js_vm(js):
a.emit(d)
fill_space.fill_space(s, a)
- # print a.exe.tape
+ if debug:
+ from pprint import pprint
+ pprint(a.exe.tape)
+ print()
a.exe.compile()
return a.exe.run(a.exe.space.GlobalObj)
diff --git a/lib/js2py/internals/simplex.py b/lib/js2py/internals/simplex.py
index b05f6174..4cd247eb 100644
--- a/lib/js2py/internals/simplex.py
+++ b/lib/js2py/internals/simplex.py
@@ -1,6 +1,10 @@
from __future__ import unicode_literals
import six
-
+if six.PY3:
+ basestring = str
+ long = int
+ xrange = range
+ unicode = str
#Undefined
class PyJsUndefined(object):
@@ -75,7 +79,7 @@ def is_callable(self):
def is_infinity(self):
- return self == float('inf') or self == -float('inf')
+ return self == Infinity or self == -Infinity
def is_nan(self):
@@ -114,7 +118,7 @@ class JsException(Exception):
return self.mes.to_string().value
else:
if self.throw is not None:
- from conversions import to_string
+ from .conversions import to_string
return to_string(self.throw)
else:
return self.typ + ': ' + self.message
@@ -131,3 +135,26 @@ def value_from_js_exception(js_exception, space):
return js_exception.throw
else:
return space.NewError(js_exception.typ, js_exception.message)
+
+
+def js_dtoa(number):
+ if is_nan(number):
+ return u'NaN'
+ elif is_infinity(number):
+ if number > 0:
+ return u'Infinity'
+ return u'-Infinity'
+ elif number == 0.:
+ return u'0'
+ elif abs(number) < 1e-6 or abs(number) >= 1e21:
+ frac, exponent = unicode(repr(float(number))).split('e')
+ # Remove leading zeros from the exponent.
+ exponent = int(exponent)
+ return frac + ('e' if exponent < 0 else 'e+') + unicode(exponent)
+ elif abs(number) < 1e-4: # python starts to return exp notation while we still want the prec
+ frac, exponent = unicode(repr(float(number))).split('e-')
+ base = u'0.' + u'0' * (int(exponent) - 1) + frac.lstrip('-').replace('.', '')
+ return base if number > 0. else u'-' + base
+ elif isinstance(number, long) or number.is_integer(): # dont print .0
+ return unicode(int(number))
+ return unicode(repr(number)) # python representation should be equivalent.
diff --git a/lib/js2py/internals/space.py b/lib/js2py/internals/space.py
index 7283070c..cb2e77ae 100644
--- a/lib/js2py/internals/space.py
+++ b/lib/js2py/internals/space.py
@@ -1,5 +1,5 @@
-from base import *
-from simplex import *
+from .base import *
+from .simplex import *
class Space(object):
diff --git a/lib/js2py/internals/trans_utils.py b/lib/js2py/internals/trans_utils.py
index 235b46a8..f99c0994 100644
--- a/lib/js2py/internals/trans_utils.py
+++ b/lib/js2py/internals/trans_utils.py
@@ -1,3 +1,10 @@
+import six
+if six.PY3:
+ basestring = str
+ long = int
+ xrange = range
+ unicode = str
+
def to_key(literal_or_identifier):
''' returns string representation of this object'''
if literal_or_identifier['type'] == 'Identifier':
diff --git a/lib/js2py/node_import.py b/lib/js2py/node_import.py
index a49a1f51..605c4b3a 100644
--- a/lib/js2py/node_import.py
+++ b/lib/js2py/node_import.py
@@ -1,6 +1,6 @@
__all__ = ['require']
import subprocess, os, codecs, glob
-from .evaljs import translate_js
+from .evaljs import translate_js, DEFAULT_HEADER
import six
DID_INIT = False
DIRNAME = os.path.dirname(os.path.abspath(__file__))
@@ -15,7 +15,7 @@ def _init():
'node -v', shell=True, cwd=DIRNAME
) == 0, 'You must have node installed! run: brew install node'
assert subprocess.call(
- 'cd %s;npm install babel-core babel-cli babel-preset-es2015 babel-polyfill babelify browserify'
+ 'cd %s;npm install babel-core babel-cli babel-preset-es2015 babel-polyfill babelify browserify browserify-shim'
% repr(DIRNAME),
shell=True,
cwd=DIRNAME) == 0, 'Could not link required node_modules'
@@ -46,12 +46,18 @@ GET_FROM_GLOBALS_FUNC = '''
'''
+def _get_module_py_name(module_name):
+ return module_name.replace('-', '_')
-def require(module_name, include_polyfill=False, update=False):
+def _get_module_var_name(module_name):
+ return _get_module_py_name(module_name).rpartition('/')[-1]
+
+
+def _get_and_translate_npm_module(module_name, include_polyfill=False, update=False):
assert isinstance(module_name, str), 'module_name must be a string!'
- py_name = module_name.replace('-', '_')
+ py_name = _get_module_py_name(module_name)
module_filename = '%s.py' % py_name
- var_name = py_name.rpartition('/')[-1]
+ var_name = _get_module_var_name(module_name)
if not os.path.exists(os.path.join(PY_NODE_MODULES_PATH,
module_filename)) or update:
_init()
@@ -77,7 +83,7 @@ def require(module_name, include_polyfill=False, update=False):
# convert the module
assert subprocess.call(
- '''node -e "(require('browserify')('./%s').bundle(function (err,data) {fs.writeFile('%s', require('babel-core').transform(data, {'presets': require('babel-preset-es2015')}).code, ()=>{});}))"'''
+ '''node -e "(require('browserify')('./%s').bundle(function (err,data) {if (err) {console.log(err);throw new Error(err);};fs.writeFile('%s', require('babel-core').transform(data, {'presets': require('babel-preset-es2015')}).code, ()=>{});}))"'''
% (in_file_name, out_file_name),
shell=True,
cwd=DIRNAME,
@@ -88,7 +94,8 @@ def require(module_name, include_polyfill=False, update=False):
"utf-8") as f:
js_code = f.read()
os.remove(os.path.join(DIRNAME, out_file_name))
-
+ if len(js_code) < 50:
+ raise RuntimeError("Candidate JS bundle too short - likely browserify issue.")
js_code += GET_FROM_GLOBALS_FUNC
js_code += ';var %s = getFromGlobals(%s);%s' % (
var_name, repr(module_name), var_name)
@@ -107,7 +114,32 @@ def require(module_name, include_polyfill=False, update=False):
os.path.join(PY_NODE_MODULES_PATH, module_filename), "r",
"utf-8") as f:
py_code = f.read()
+ return py_code
- context = {}
+
+def require(module_name, include_polyfill=False, update=False, context=None):
+ """
+ Installs the provided npm module, exports a js bundle via browserify, converts to ECMA 5.1 via babel and
+ finally translates the generated JS bundle to Python via Js2Py.
+ Returns a pure python object that behaves like the installed module. Nice!
+
+ :param module_name: Name of the npm module to require. For example 'esprima'.
+ :param include_polyfill: Whether the babel-polyfill should be included as part of the translation. May be needed
+ for some modules that use unsupported features.
+ :param update: Whether to force update the translation. Otherwise uses a cached version if exists.
+ :param context: Optional context in which the translated module should be executed in. If provided, the
+ header (js2py imports) will be skipped as it is assumed that the context already has all the necessary imports.
+ :return: The JsObjectWrapper containing the translated module object. Can be used like a standard python object.
+ """
+ py_code = _get_and_translate_npm_module(module_name, include_polyfill=include_polyfill, update=update)
+ # this is a bit hacky but we need to strip the default header from the generated code...
+ if context is not None:
+ if not py_code.startswith(DEFAULT_HEADER):
+ # new header version? retranslate...
+ assert not update, "Unexpected header."
+ py_code = _get_and_translate_npm_module(module_name, include_polyfill=include_polyfill, update=True)
+ assert py_code.startswith(DEFAULT_HEADER), "Unexpected header."
+ py_code = py_code[len(DEFAULT_HEADER):]
+ context = {} if context is None else context
exec (py_code, context)
- return context['var'][var_name].to_py()
+ return context['var'][_get_module_var_name(module_name)].to_py()
diff --git a/lib/js2py/prototypes/jsfunction.py b/lib/js2py/prototypes/jsfunction.py
index f9598a31..2ed417e0 100644
--- a/lib/js2py/prototypes/jsfunction.py
+++ b/lib/js2py/prototypes/jsfunction.py
@@ -6,8 +6,6 @@ if six.PY3:
xrange = range
unicode = str
-# todo fix apply and bind
-
class FunctionPrototype:
def toString():
@@ -41,6 +39,7 @@ class FunctionPrototype:
return this.call(obj, args)
def bind(thisArg):
+ arguments_ = arguments
target = this
if not target.is_callable():
raise this.MakeError(
@@ -48,5 +47,5 @@ class FunctionPrototype:
if len(arguments) <= 1:
args = ()
else:
- args = tuple([arguments[e] for e in xrange(1, len(arguments))])
+ args = tuple([arguments_[e] for e in xrange(1, len(arguments_))])
return this.PyJsBoundFunction(target, thisArg, args)
diff --git a/lib/js2py/translators/translating_nodes.py b/lib/js2py/translators/translating_nodes.py
index b9eea29f..371c8ede 100644
--- a/lib/js2py/translators/translating_nodes.py
+++ b/lib/js2py/translators/translating_nodes.py
@@ -155,7 +155,7 @@ def limited(func):
inf = float('inf')
-def Literal(type, value, raw, regex=None, comments=None):
+def Literal(type, value, raw, regex=None):
if regex: # regex
return 'JsRegExp(%s)' % repr(compose_regex(value))
elif value is None: # null
@@ -165,12 +165,12 @@ def Literal(type, value, raw, regex=None, comments=None):
return 'Js(%s)' % repr(value) if value != inf else 'Js(float("inf"))'
-def Identifier(type, name, comments=None):
+def Identifier(type, name):
return 'var.get(%s)' % repr(name)
@limited
-def MemberExpression(type, computed, object, property, comments=None):
+def MemberExpression(type, computed, object, property):
far_left = trans(object)
if computed: # obj[prop] type accessor
# may be literal which is the same in every case so we can save some time on conversion
@@ -183,12 +183,12 @@ def MemberExpression(type, computed, object, property, comments=None):
return far_left + '.get(%s)' % prop
-def ThisExpression(type, comments=None):
+def ThisExpression(type):
return 'var.get(u"this")'
@limited
-def CallExpression(type, callee, arguments, comments=None):
+def CallExpression(type, callee, arguments):
arguments = [trans(e) for e in arguments]
if callee['type'] == 'MemberExpression':
far_left = trans(callee['object'])
@@ -210,38 +210,47 @@ def CallExpression(type, callee, arguments, comments=None):
# ========== ARRAYS ============
-def ArrayExpression(type, elements, comments=None): # todo fix null inside problem
+def ArrayExpression(type, elements): # todo fix null inside problem
return 'Js([%s])' % ', '.join(trans(e) if e else 'None' for e in elements)
# ========== OBJECTS =============
-def ObjectExpression(type, properties, comments=None):
- name = inline_stack.require('Object')
+def ObjectExpression(type, properties):
+ name = None
elems = []
after = ''
for p in properties:
if p['kind'] == 'init':
elems.append('%s:%s' % Property(**p))
- elif p['kind'] == 'set':
- k, setter = Property(
- **p
- ) # setter is just a lval referring to that function, it will be defined in InlineStack automatically
- after += '%s.define_own_property(%s, {"set":%s, "configurable":True, "enumerable":True})\n' % (
- name, k, setter)
- elif p['kind'] == 'get':
- k, getter = Property(**p)
- after += '%s.define_own_property(%s, {"get":%s, "configurable":True, "enumerable":True})\n' % (
- name, k, getter)
else:
- raise RuntimeError('Unexpected object propery kind')
- obj = '%s = Js({%s})\n' % (name, ','.join(elems))
- inline_stack.define(name, obj + after)
- return name
+ if name is None:
+ name = inline_stack.require('Object')
+ if p['kind'] == 'set':
+ k, setter = Property(
+ **p
+ ) # setter is just a lval referring to that function, it will be defined in InlineStack automatically
+ after += '%s.define_own_property(%s, {"set":%s, "configurable":True, "enumerable":True})\n' % (
+ name, k, setter)
+ elif p['kind'] == 'get':
+ k, getter = Property(**p)
+ after += '%s.define_own_property(%s, {"get":%s, "configurable":True, "enumerable":True})\n' % (
+ name, k, getter)
+ else:
+ raise RuntimeError('Unexpected object propery kind')
+ definition = 'Js({%s})' % ','.join(elems)
+ if name is None:
+ return definition
+ body = '%s = %s\n' % (name, definition)
+ body += after
+ body += 'return %s\n' % name
+ code = 'def %s():\n%s' % (name, indent(body))
+ inline_stack.define(name, code)
+ return name + '()'
-def Property(type, kind, key, computed, value, method, shorthand, comments=None):
+def Property(type, kind, key, computed, value, method, shorthand):
if shorthand or computed:
raise NotImplementedError(
'Shorthand and Computed properties not implemented!')
@@ -256,7 +265,7 @@ def Property(type, kind, key, computed, value, method, shorthand, comments=None)
@limited
-def UnaryExpression(type, operator, argument, prefix, comments=None):
+def UnaryExpression(type, operator, argument, prefix):
a = trans(
argument, standard=True
) # unary involve some complex operations so we cant use line shorteners here
@@ -271,7 +280,7 @@ def UnaryExpression(type, operator, argument, prefix, comments=None):
@limited
-def BinaryExpression(type, operator, left, right, comments=None):
+def BinaryExpression(type, operator, left, right):
a = trans(left)
b = trans(right)
# delegate to our friends
@@ -279,7 +288,7 @@ def BinaryExpression(type, operator, left, right, comments=None):
@limited
-def UpdateExpression(type, operator, argument, prefix, comments=None):
+def UpdateExpression(type, operator, argument, prefix):
a = trans(
argument, standard=True
) # also complex operation involving parsing of the result so no line length reducing here
@@ -287,7 +296,7 @@ def UpdateExpression(type, operator, argument, prefix, comments=None):
@limited
-def AssignmentExpression(type, operator, left, right, comments=None):
+def AssignmentExpression(type, operator, left, right):
operator = operator[:-1]
if left['type'] == 'Identifier':
if operator:
@@ -319,12 +328,12 @@ six
@limited
-def SequenceExpression(type, expressions, comments=None):
+def SequenceExpression(type, expressions):
return reduce(js_comma, (trans(e) for e in expressions))
@limited
-def NewExpression(type, callee, arguments, comments=None):
+def NewExpression(type, callee, arguments):
return trans(callee) + '.create(%s)' % ', '.join(
trans(e) for e in arguments)
@@ -332,7 +341,7 @@ def NewExpression(type, callee, arguments, comments=None):
@limited
def ConditionalExpression(
type, test, consequent,
- alternate, comments=None): # caused plenty of problems in my home-made translator :)
+ alternate): # caused plenty of problems in my home-made translator :)
return '(%s if %s else %s)' % (trans(consequent), trans(test),
trans(alternate))
@@ -340,49 +349,49 @@ def ConditionalExpression(
# =========== STATEMENTS =============
-def BlockStatement(type, body, comments=None):
+def BlockStatement(type, body):
return StatementList(
body) # never returns empty string! In the worst case returns pass\n
-def ExpressionStatement(type, expression, comments=None):
+def ExpressionStatement(type, expression):
return trans(expression) + '\n' # end expression space with new line
-def BreakStatement(type, label, comments=None):
+def BreakStatement(type, label):
if label:
return 'raise %s("Breaked")\n' % (get_break_label(label['name']))
else:
return 'break\n'
-def ContinueStatement(type, label, comments=None):
+def ContinueStatement(type, label):
if label:
return 'raise %s("Continued")\n' % (get_continue_label(label['name']))
else:
return 'continue\n'
-def ReturnStatement(type, argument, comments=None):
+def ReturnStatement(type, argument):
return 'return %s\n' % (trans(argument)
if argument else "var.get('undefined')")
-def EmptyStatement(type, comments=None):
+def EmptyStatement(type):
return 'pass\n'
-def DebuggerStatement(type, comments=None):
+def DebuggerStatement(type):
return 'pass\n'
-def DoWhileStatement(type, body, test, comments=None):
+def DoWhileStatement(type, body, test):
inside = trans(body) + 'if not %s:\n' % trans(test) + indent('break\n')
result = 'while 1:\n' + indent(inside)
return result
-def ForStatement(type, init, test, update, body, comments=None):
+def ForStatement(type, init, test, update, body):
update = indent(trans(update)) if update else ''
init = trans(init) if init else ''
if not init.endswith('\n'):
@@ -398,7 +407,7 @@ def ForStatement(type, init, test, update, body, comments=None):
return result
-def ForInStatement(type, left, right, body, each, comments=None):
+def ForInStatement(type, left, right, body, each):
res = 'for PyJsTemp in %s:\n' % trans(right)
if left['type'] == "VariableDeclaration":
addon = trans(left) # make sure variable is registered
@@ -417,7 +426,7 @@ def ForInStatement(type, left, right, body, each, comments=None):
return res
-def IfStatement(type, test, consequent, alternate, comments=None):
+def IfStatement(type, test, consequent, alternate):
# NOTE we cannot do elif because function definition inside elif statement would not be possible!
IF = 'if %s:\n' % trans(test)
IF += indent(trans(consequent))
@@ -427,7 +436,7 @@ def IfStatement(type, test, consequent, alternate, comments=None):
return IF + ELSE
-def LabeledStatement(type, label, body, comments=None):
+def LabeledStatement(type, label, body):
# todo consider using smarter approach!
inside = trans(body)
defs = ''
@@ -448,7 +457,7 @@ def LabeledStatement(type, label, body, comments=None):
return defs + inside
-def StatementList(lis, comments=None):
+def StatementList(lis):
if lis: # ensure we don't return empty string because it may ruin indentation!
code = ''.join(trans(e) for e in lis)
return code if code else 'pass\n'
@@ -456,7 +465,7 @@ def StatementList(lis, comments=None):
return 'pass\n'
-def PyimportStatement(type, imp, comments=None):
+def PyimportStatement(type, imp):
lib = imp['name']
jlib = 'PyImport_%s' % lib
code = 'import %s as %s\n' % (lib, jlib)
@@ -471,7 +480,7 @@ def PyimportStatement(type, imp, comments=None):
return code
-def SwitchStatement(type, discriminant, cases, comments=None):
+def SwitchStatement(type, discriminant, cases):
#TODO there will be a problem with continue in a switch statement.... FIX IT
code = 'while 1:\n' + indent('SWITCHED = False\nCONDITION = (%s)\n')
code = code % trans(discriminant)
@@ -491,12 +500,12 @@ def SwitchStatement(type, discriminant, cases, comments=None):
return code
-def ThrowStatement(type, argument, comments=None):
+def ThrowStatement(type, argument):
return 'PyJsTempException = JsToPyException(%s)\nraise PyJsTempException\n' % trans(
argument)
-def TryStatement(type, block, handler, handlers, guardedHandlers, finalizer, comments=None):
+def TryStatement(type, block, handler, handlers, guardedHandlers, finalizer):
result = 'try:\n%s' % indent(trans(block))
# complicated catch statement...
if handler:
@@ -516,13 +525,13 @@ def TryStatement(type, block, handler, handlers, guardedHandlers, finalizer, com
return result
-def LexicalDeclaration(type, declarations, kind, comments=None):
+def LexicalDeclaration(type, declarations, kind):
raise NotImplementedError(
'let and const not implemented yet but they will be soon! Check github for updates.'
)
-def VariableDeclarator(type, id, init, comments=None):
+def VariableDeclarator(type, id, init):
name = id['name']
# register the name if not already registered
Context.register(name)
@@ -531,21 +540,21 @@ def VariableDeclarator(type, id, init, comments=None):
return ''
-def VariableDeclaration(type, declarations, kind, comments=None):
+def VariableDeclaration(type, declarations, kind):
code = ''.join(trans(d) for d in declarations)
return code if code else 'pass\n'
-def WhileStatement(type, test, body, comments=None):
+def WhileStatement(type, test, body):
result = 'while %s:\n' % trans(test) + indent(trans(body))
return result
-def WithStatement(type, object, body, comments=None):
+def WithStatement(type, object, body):
raise NotImplementedError('With statement not implemented!')
-def Program(type, body, comments=None):
+def Program(type, body):
inline_stack.reset()
code = ''.join(trans(e) for e in body)
# here add hoisted elements (register variables and define functions)
@@ -559,7 +568,7 @@ def Program(type, body, comments=None):
def FunctionDeclaration(type, id, params, defaults, body, generator,
- expression, comments=None):
+ expression):
if generator:
raise NotImplementedError('Generators not supported')
if defaults:
@@ -610,7 +619,7 @@ def FunctionDeclaration(type, id, params, defaults, body, generator,
def FunctionExpression(type, id, params, defaults, body, generator,
- expression, comments=None):
+ expression):
if generator:
raise NotImplementedError('Generators not supported')
if defaults:
diff --git a/lib/js2py/utils/injector.py b/lib/js2py/utils/injector.py
index dd714a48..ea236d5e 100644
--- a/lib/js2py/utils/injector.py
+++ b/lib/js2py/utils/injector.py
@@ -115,7 +115,16 @@ def append_arguments(code_obj, new_locals):
code_obj.co_freevars, code_obj.co_cellvars)
# Done modifying codestring - make the code object
- return types.CodeType(*args)
+ if hasattr(code_obj, "replace"):
+ # Python 3.8+
+ return code_obj.replace(
+ co_argcount=co_argcount + new_locals_len,
+ co_nlocals=code_obj.co_nlocals + new_locals_len,
+ co_code=code,
+ co_names=names,
+ co_varnames=varnames)
+ else:
+ return types.CodeType(*args)
def instructions(code_obj):