From 3d4de0722a505eeeba6f6922e714d13a9e82d91c Mon Sep 17 00:00:00 2001 From: Alhaziel Date: Wed, 27 Nov 2019 18:55:07 +0100 Subject: [PATCH] Aggiornato js2py Previene loop in caso cloudscrape fallisca --- lib/js2py/base.py | 13 +- lib/js2py/constructors/time_helpers.py | 2 +- lib/js2py/evaljs.py | 32 +++-- lib/js2py/internals/base.py | 20 +-- lib/js2py/internals/byte_trans.py | 23 ++-- lib/js2py/internals/code.py | 66 +++++++--- .../internals/constructors/jsfunction.py | 3 +- lib/js2py/internals/conversions.py | 11 +- lib/js2py/internals/fill_space.py | 43 +++---- lib/js2py/internals/func_utils.py | 4 +- lib/js2py/internals/opcodes.py | 4 +- lib/js2py/internals/operations.py | 4 +- lib/js2py/internals/prototypes/jsstring.py | 2 +- lib/js2py/internals/seval.py | 21 ++-- lib/js2py/internals/simplex.py | 33 ++++- lib/js2py/internals/space.py | 4 +- lib/js2py/internals/trans_utils.py | 7 ++ lib/js2py/node_import.py | 50 ++++++-- lib/js2py/prototypes/jsfunction.py | 5 +- lib/js2py/translators/translating_nodes.py | 119 ++++++++++-------- lib/js2py/utils/injector.py | 11 +- 21 files changed, 298 insertions(+), 179 deletions(-) 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):