diff --git a/lib/js2py/base.py b/lib/js2py/base.py index cf1eca08..f4ee721c 100644 --- a/lib/js2py/base.py +++ b/lib/js2py/base.py @@ -126,7 +126,7 @@ def HJs(val): except Exception as e: message = 'your Python function failed! ' try: - message += e.message + message += str(e) except: pass raise MakeError('Error', message) @@ -319,7 +319,7 @@ class PyJs(object): #prop = prop.value if self.Class == 'Undefined' or self.Class == 'Null': raise MakeError('TypeError', - 'Undefined and null dont have properties!') + 'Undefined and null dont have properties (tried getting property %s)' % repr(prop)) if not isinstance(prop, basestring): prop = prop.to_string().value if not isinstance(prop, basestring): raise RuntimeError('Bug') @@ -361,7 +361,7 @@ class PyJs(object): * / % + - << >> & ^ |''' if self.Class == 'Undefined' or self.Class == 'Null': raise MakeError('TypeError', - 'Undefined and null dont have properties!') + 'Undefined and null don\'t have properties (tried setting property %s)' % repr(prop)) if not isinstance(prop, basestring): prop = prop.to_string().value if NUMPY_AVAILABLE and prop.isdigit(): @@ -991,7 +991,8 @@ class PyJs(object): cand = self.get(prop) if not cand.is_callable(): raise MakeError('TypeError', - '%s is not a function' % cand.typeof()) + '%s is not a function (tried calling property %s of %s)' % ( + cand.typeof(), repr(prop), repr(self.Class))) return cand.call(self, args) def to_python(self): @@ -1304,7 +1305,7 @@ class PyObjectWrapper(PyJs): except Exception as e: message = 'your Python function failed! ' try: - message += e.message + message += str(e) except: pass raise MakeError('Error', message) @@ -1464,9 +1465,11 @@ class PyJsFunction(PyJs): except NotImplementedError: raise except RuntimeError as e: # maximum recursion - raise MakeError( - 'RangeError', e.message if - not isinstance(e, NotImplementedError) else 'Not implemented!') + try: + msg = e.message + except: + msg = repr(e) + raise MakeError('RangeError', msg) def has_instance(self, other): # I am not sure here so instanceof may not work lol. diff --git a/lib/js2py/constructors/jsdate.py b/lib/js2py/constructors/jsdate.py index 98de6431..b6b70a81 100644 --- a/lib/js2py/constructors/jsdate.py +++ b/lib/js2py/constructors/jsdate.py @@ -32,8 +32,7 @@ def UTC(year, month, date, hours, minutes, seconds, ms): # todo complete this mili = args[6].to_number() if l > 6 else Js(0) if not y.is_nan() and 0 <= y.value <= 99: y = y + Js(1900) - t = TimeClip(MakeDate(MakeDay(y, m, dt), MakeTime(h, mi, sec, mili))) - return PyJsDate(t, prototype=DatePrototype) + return TimeClip(MakeDate(MakeDay(y, m, dt), MakeTime(h, mi, sec, mili))) @Js @@ -76,11 +75,12 @@ class PyJsDate(PyJs): # todo fix this problematic datetime part def to_local_dt(self): - return datetime.datetime.utcfromtimestamp( - UTCToLocal(self.value) // 1000) + return datetime.datetime(1970, 1, 1) + datetime.timedelta( + seconds=UTCToLocal(self.value) // 1000) def to_utc_dt(self): - return datetime.datetime.utcfromtimestamp(self.value // 1000) + return datetime.datetime(1970, 1, 1) + datetime.timedelta( + seconds=self.value // 1000) def local_strftime(self, pattern): if self.value is NaN: @@ -118,21 +118,40 @@ class PyJsDate(PyJs): def parse_date(py_string): # todo support all date string formats - try: + date_formats = ( + "%Y-%m-%d", + "%m/%d/%Y", + "%b %d %Y", + ) + # Supports these hour formats and with or hour. + hour_formats = ( + "T%H:%M:%S.%f", + "T%H:%M:%S", + ) + ('',) + # Supports with or without Z indicator. + z_formats = ("Z",) + ('',) + supported_formats = [ + d + t + z + for d in date_formats + for t in hour_formats + for z in z_formats + ] + for date_format in supported_formats: try: - dt = datetime.datetime.strptime(py_string, "%Y-%m-%dT%H:%M:%S.%fZ") - except: - dt = datetime.datetime.strptime(py_string, "%Y-%m-%dT%H:%M:%SZ") - return MakeDate( - MakeDay(Js(dt.year), Js(dt.month - 1), Js(dt.day)), - MakeTime( - Js(dt.hour), Js(dt.minute), Js(dt.second), - Js(dt.microsecond // 1000))) - except: - raise MakeError( - 'TypeError', - 'Could not parse date %s - unsupported date format. Currently only supported format is RFC3339 utc. Sorry!' - % py_string) + dt = datetime.datetime.strptime(py_string, date_format) + except ValueError: + continue + else: + return MakeDate( + MakeDay(Js(dt.year), Js(dt.month - 1), Js(dt.day)), + MakeTime( + Js(dt.hour), Js(dt.minute), Js(dt.second), + Js(dt.microsecond // 1000))) + + raise MakeError( + 'TypeError', + 'Could not parse date %s - unsupported date format. Currently only supported formats are RFC3339 utc, ISO Date, Short Date, and Long Date. Sorry!' + % py_string) def date_constructor(*args): @@ -332,7 +351,7 @@ class DateProto: check_date(this) t = UTCToLocal(this.value) tim = MakeTime( - HourFromTime(t), MinFromTime(t), SecFromTime(t), ms.to_int()) + Js(HourFromTime(t)), Js(MinFromTime(t)), Js(SecFromTime(t)), ms) u = TimeClip(LocalToUTC(MakeDate(Day(t), tim))) this.value = u return u @@ -341,12 +360,164 @@ class DateProto: check_date(this) t = this.value tim = MakeTime( - HourFromTime(t), MinFromTime(t), SecFromTime(t), ms.to_int()) + Js(HourFromTime(t)), Js(MinFromTime(t)), Js(SecFromTime(t)), ms) u = TimeClip(MakeDate(Day(t), tim)) this.value = u return u - # todo Complete all setters! + def setSeconds(sec, ms=None): + check_date(this) + t = UTCToLocal(this.value) + s = sec.to_number() + if not ms is None: milli = Js(msFromTime(t)) + else: milli = ms.to_number() + date = MakeDate( + Day(t), MakeTime(Js(HourFromTime(t)), Js(MinFromTime(t)), s, milli)) + u = TimeClip(LocalToUTC(date)) + this.value = u + return u + + def setUTCSeconds(sec, ms=None): + check_date(this) + t = this.value + s = sec.to_number() + if not ms is None: milli = Js(msFromTime(t)) + else: milli = ms.to_number() + date = MakeDate( + Day(t), MakeTime(Js(HourFromTime(t)), Js(MinFromTime(t)), s, milli)) + v = TimeClip(date) + this.value = v + return v + + def setMinutes(min, sec=None, ms=None): + check_date(this) + t = UTCToLocal(this.value) + m = min.to_number() + if not sec is None: s = Js(SecFromTime(t)) + else: s = sec.to_number() + if not ms is None: milli = Js(msFromTime(t)) + else: milli = ms.to_number() + date = MakeDate(Day(t), MakeTime(Js(HourFromTime(t)), m, s, milli)) + u = TimeClip(LocalToUTC(date)) + this.value = u + return u + + def setUTCMinutes(min, sec=None, ms=None): + check_date(this) + t = this.value + m = min.to_number() + if not sec is None: s = Js(SecFromTime(t)) + else: s = sec.to_number() + if not ms is None: milli = Js(msFromTime(t)) + else: milli = ms.to_number() + date = MakeDate(Day(t), MakeTime(Js(HourFromTime(t)), m, s, milli)) + v = TimeClip(date) + this.value = v + return v + + def setHours(hour, min=None, sec=None, ms=None): + check_date(this) + t = UTCToLocal(this.value) + h = hour.to_number() + if not min is None: m = Js(MinFromTime(t)) + else: m = min.to_number() + if not sec is None: s = Js(SecFromTime(t)) + else: s = sec.to_number() + if not ms is None: milli = Js(msFromTime(t)) + else: milli = ms.to_number() + date = MakeDate(Day(t), MakeTime(h, m, s, milli)) + u = TimeClip(LocalToUTC(date)) + this.value = u + return u + + def setUTCHours(hour, min=None, sec=None, ms=None): + check_date(this) + t = this.value + h = hour.to_number() + if not min is None: m = Js(MinFromTime(t)) + else: m = min.to_number() + if not sec is None: s = Js(SecFromTime(t)) + else: s = sec.to_number() + if not ms is None: milli = Js(msFromTime(t)) + else: milli = ms.to_number() + date = MakeDate(Day(t), MakeTime(h, m, s, milli)) + v = TimeClip(date) + this.value = v + return v + + def setDate(date): + check_date(this) + t = UTCToLocal(this.value) + dt = date.to_number() + newDate = MakeDate( + MakeDay(Js(YearFromTime(t)), Js(MonthFromTime(t)), dt), TimeWithinDay(t)) + u = TimeClip(LocalToUTC(newDate)) + this.value = u + return u + + def setUTCDate(date): + check_date(this) + t = this.value + dt = date.to_number() + newDate = MakeDate( + MakeDay(Js(YearFromTime(t)), Js(MonthFromTime(t)), dt), TimeWithinDay(t)) + v = TimeClip(newDate) + this.value = v + return v + + def setMonth(month, date=None): + check_date(this) + t = UTCToLocal(this.value) + m = month.to_number() + if not date is None: dt = Js(DateFromTime(t)) + else: dt = date.to_number() + newDate = MakeDate( + MakeDay(Js(YearFromTime(t)), m, dt), TimeWithinDay(t)) + u = TimeClip(LocalToUTC(newDate)) + this.value = u + return u + + def setUTCMonth(month, date=None): + check_date(this) + t = this.value + m = month.to_number() + if not date is None: dt = Js(DateFromTime(t)) + else: dt = date.to_number() + newDate = MakeDate( + MakeDay(Js(YearFromTime(t)), m, dt), TimeWithinDay(t)) + v = TimeClip(newDate) + this.value = v + return v + + def setFullYear(year, month=None, date=None): + check_date(this) + if not this.value is NaN: t = UTCToLocal(this.value) + else: t = 0 + y = year.to_number() + if not month is None: m = Js(MonthFromTime(t)) + else: m = month.to_number() + if not date is None: dt = Js(DateFromTime(t)) + else: dt = date.to_number() + newDate = MakeDate( + MakeDay(y, m, dt), TimeWithinDay(t)) + u = TimeClip(LocalToUTC(newDate)) + this.value = u + return u + + def setUTCFullYear(year, month=None, date=None): + check_date(this) + if not this.value is NaN: t = UTCToLocal(this.value) + else: t = 0 + y = year.to_number() + if not month is None: m = Js(MonthFromTime(t)) + else: m = month.to_number() + if not date is None: dt = Js(DateFromTime(t)) + else: dt = date.to_number() + newDate = MakeDate( + MakeDay(y, m, dt), TimeWithinDay(t)) + v = TimeClip(newDate) + this.value = v + return v def toUTCString(): check_date(this) diff --git a/lib/js2py/constructors/time_helpers.py b/lib/js2py/constructors/time_helpers.py index a744d8ca..f3d21f3e 100644 --- a/lib/js2py/constructors/time_helpers.py +++ b/lib/js2py/constructors/time_helpers.py @@ -36,8 +36,8 @@ def DaylightSavingTA(t): return t try: return int( - LOCAL_ZONE.dst(datetime.datetime.utcfromtimestamp( - t // 1000)).seconds) * 1000 + LOCAL_ZONE.dst(datetime.datetime(1970, 1, 1) + datetime.timedelta( + seconds=t // 1000)).seconds) * 1000 except: warnings.warn( 'Invalid datetime date, assumed DST time, may be inaccurate...', diff --git a/lib/js2py/evaljs.py b/lib/js2py/evaljs.py index ef4d7d95..f4649c4d 100644 --- a/lib/js2py/evaljs.py +++ b/lib/js2py/evaljs.py @@ -53,7 +53,7 @@ def write_file_contents(path_or_file, contents): if hasattr(path_or_file, 'write'): path_or_file.write(contents) else: - with open(path_as_local(path_or_file), 'w') as f: + with codecs.open(path_as_local(path_or_file), "w", "utf-8") as f: f.write(contents) @@ -238,6 +238,10 @@ class EvalJs(object): self.execute_debug(code) return self['PyJsEvalResult'] + @property + def context(self): + return self._context + def __getattr__(self, var): return getattr(self._var, var) @@ -268,14 +272,3 @@ class EvalJs(object): else: sys.stderr.write('EXCEPTION: ' + str(e) + '\n') time.sleep(0.01) - - -#print x - -if __name__ == '__main__': - #with open('C:\Users\Piotrek\Desktop\esprima.js', 'rb') as f: - # x = f.read() - e = EvalJs() - e.execute('square(x)') - #e.execute(x) - e.console() diff --git a/lib/js2py/host/console.py b/lib/js2py/host/console.py index 50a08632..269eef3d 100644 --- a/lib/js2py/host/console.py +++ b/lib/js2py/host/console.py @@ -6,7 +6,7 @@ def console(): @Js def log(): - print(arguments[0]) + print(" ".join(repr(element) for element in arguments.to_list())) console.put('log', log) console.put('debug', log) diff --git a/lib/js2py/internals/__init__.py b/lib/js2py/internals/__init__.py index e69de29b..5473e711 100644 --- a/lib/js2py/internals/__init__.py +++ b/lib/js2py/internals/__init__.py @@ -0,0 +1 @@ +from .seval import eval_js_vm \ No newline at end of file diff --git a/lib/js2py/internals/base.py b/lib/js2py/internals/base.py index a02a2122..938dec37 100644 --- a/lib/js2py/internals/base.py +++ b/lib/js2py/internals/base.py @@ -602,11 +602,12 @@ class PyJsDate(PyJs): # todo fix this problematic datetime part def to_local_dt(self): - return datetime.datetime.utcfromtimestamp( - self.UTCToLocal(self.value) // 1000) + return datetime.datetime(1970, 1, 1) + datetime.timedelta( + seconds=self.UTCToLocal(self.value) // 1000) def to_utc_dt(self): - return datetime.datetime.utcfromtimestamp(self.value // 1000) + return datetime.datetime(1970, 1, 1) + datetime.timedelta( + seconds=self.value // 1000) def local_strftime(self, pattern): if self.value is NaN: diff --git a/lib/js2py/internals/constructors/jsdate.py b/lib/js2py/internals/constructors/jsdate.py index 98de6431..5aed830e 100644 --- a/lib/js2py/internals/constructors/jsdate.py +++ b/lib/js2py/internals/constructors/jsdate.py @@ -32,8 +32,7 @@ def UTC(year, month, date, hours, minutes, seconds, ms): # todo complete this mili = args[6].to_number() if l > 6 else Js(0) if not y.is_nan() and 0 <= y.value <= 99: y = y + Js(1900) - t = TimeClip(MakeDate(MakeDay(y, m, dt), MakeTime(h, mi, sec, mili))) - return PyJsDate(t, prototype=DatePrototype) + return TimeClip(MakeDate(MakeDay(y, m, dt), MakeTime(h, mi, sec, mili))) @Js @@ -76,11 +75,12 @@ class PyJsDate(PyJs): # todo fix this problematic datetime part def to_local_dt(self): - return datetime.datetime.utcfromtimestamp( - UTCToLocal(self.value) // 1000) + return datetime.datetime(1970, 1, 1) + datetime.timedelta( + seconds=UTCToLocal(self.value) // 1000) def to_utc_dt(self): - return datetime.datetime.utcfromtimestamp(self.value // 1000) + return datetime.datetime(1970, 1, 1) + datetime.timedelta( + seconds=self.value // 1000) def local_strftime(self, pattern): if self.value is NaN: @@ -332,7 +332,7 @@ class DateProto: check_date(this) t = UTCToLocal(this.value) tim = MakeTime( - HourFromTime(t), MinFromTime(t), SecFromTime(t), ms.to_int()) + Js(HourFromTime(t)), Js(MinFromTime(t)), Js(SecFromTime(t)), ms) u = TimeClip(LocalToUTC(MakeDate(Day(t), tim))) this.value = u return u @@ -341,12 +341,164 @@ class DateProto: check_date(this) t = this.value tim = MakeTime( - HourFromTime(t), MinFromTime(t), SecFromTime(t), ms.to_int()) + Js(HourFromTime(t)), Js(MinFromTime(t)), Js(SecFromTime(t)), ms) u = TimeClip(MakeDate(Day(t), tim)) this.value = u return u - # todo Complete all setters! + def setSeconds(sec, ms=None): + check_date(this) + t = UTCToLocal(this.value) + s = sec.to_number() + if not ms is None: milli = Js(msFromTime(t)) + else: milli = ms.to_number() + date = MakeDate( + Day(t), MakeTime(Js(HourFromTime(t)), Js(MinFromTime(t)), s, milli)) + u = TimeClip(LocalToUTC(date)) + this.value = u + return u + + def setUTCSeconds(sec, ms=None): + check_date(this) + t = this.value + s = sec.to_number() + if not ms is None: milli = Js(msFromTime(t)) + else: milli = ms.to_number() + date = MakeDate( + Day(t), MakeTime(Js(HourFromTime(t)), Js(MinFromTime(t)), s, milli)) + v = TimeClip(date) + this.value = v + return v + + def setMinutes(min, sec=None, ms=None): + check_date(this) + t = UTCToLocal(this.value) + m = min.to_number() + if not sec is None: s = Js(SecFromTime(t)) + else: s = sec.to_number() + if not ms is None: milli = Js(msFromTime(t)) + else: milli = ms.to_number() + date = MakeDate(Day(t), MakeTime(Js(HourFromTime(t)), m, s, milli)) + u = TimeClip(LocalToUTC(date)) + this.value = u + return u + + def setUTCMinutes(min, sec=None, ms=None): + check_date(this) + t = this.value + m = min.to_number() + if not sec is None: s = Js(SecFromTime(t)) + else: s = sec.to_number() + if not ms is None: milli = Js(msFromTime(t)) + else: milli = ms.to_number() + date = MakeDate(Day(t), MakeTime(Js(HourFromTime(t)), m, s, milli)) + v = TimeClip(date) + this.value = v + return v + + def setHours(hour, min=None, sec=None, ms=None): + check_date(this) + t = UTCToLocal(this.value) + h = hour.to_number() + if not min is None: m = Js(MinFromTime(t)) + else: m = min.to_number() + if not sec is None: s = Js(SecFromTime(t)) + else: s = sec.to_number() + if not ms is None: milli = Js(msFromTime(t)) + else: milli = ms.to_number() + date = MakeDate(Day(t), MakeTime(h, m, s, milli)) + u = TimeClip(LocalToUTC(date)) + this.value = u + return u + + def setUTCHours(hour, min=None, sec=None, ms=None): + check_date(this) + t = this.value + h = hour.to_number() + if not min is None: m = Js(MinFromTime(t)) + else: m = min.to_number() + if not sec is None: s = Js(SecFromTime(t)) + else: s = sec.to_number() + if not ms is None: milli = Js(msFromTime(t)) + else: milli = ms.to_number() + date = MakeDate(Day(t), MakeTime(h, m, s, milli)) + v = TimeClip(date) + this.value = v + return v + + def setDate(date): + check_date(this) + t = UTCToLocal(this.value) + dt = date.to_number() + newDate = MakeDate( + MakeDay(Js(YearFromTime(t)), Js(MonthFromTime(t)), dt), TimeWithinDay(t)) + u = TimeClip(LocalToUTC(newDate)) + this.value = u + return u + + def setUTCDate(date): + check_date(this) + t = this.value + dt = date.to_number() + newDate = MakeDate( + MakeDay(Js(YearFromTime(t)), Js(MonthFromTime(t)), dt), TimeWithinDay(t)) + v = TimeClip(newDate) + this.value = v + return v + + def setMonth(month, date=None): + check_date(this) + t = UTCToLocal(this.value) + m = month.to_number() + if not date is None: dt = Js(DateFromTime(t)) + else: dt = date.to_number() + newDate = MakeDate( + MakeDay(Js(YearFromTime(t)), m, dt), TimeWithinDay(t)) + u = TimeClip(LocalToUTC(newDate)) + this.value = u + return u + + def setUTCMonth(month, date=None): + check_date(this) + t = this.value + m = month.to_number() + if not date is None: dt = Js(DateFromTime(t)) + else: dt = date.to_number() + newDate = MakeDate( + MakeDay(Js(YearFromTime(t)), m, dt), TimeWithinDay(t)) + v = TimeClip(newDate) + this.value = v + return v + + def setFullYear(year, month=None, date=None): + check_date(this) + if not this.value is NaN: t = UTCToLocal(this.value) + else: t = 0 + y = year.to_number() + if not month is None: m = Js(MonthFromTime(t)) + else: m = month.to_number() + if not date is None: dt = Js(DateFromTime(t)) + else: dt = date.to_number() + newDate = MakeDate( + MakeDay(y, m, dt), TimeWithinDay(t)) + u = TimeClip(LocalToUTC(newDate)) + this.value = u + return u + + def setUTCFullYear(year, month=None, date=None): + check_date(this) + if not this.value is NaN: t = UTCToLocal(this.value) + else: t = 0 + y = year.to_number() + if not month is None: m = Js(MonthFromTime(t)) + else: m = month.to_number() + if not date is None: dt = Js(DateFromTime(t)) + else: dt = date.to_number() + newDate = MakeDate( + MakeDay(y, m, dt), TimeWithinDay(t)) + v = TimeClip(newDate) + this.value = v + return v def toUTCString(): check_date(this) diff --git a/lib/js2py/internals/constructors/jsmath.py b/lib/js2py/internals/constructors/jsmath.py index 3eb44616..c6fde67e 100644 --- a/lib/js2py/internals/constructors/jsmath.py +++ b/lib/js2py/internals/constructors/jsmath.py @@ -16,7 +16,8 @@ CONSTANTS = { 'SQRT1_2': 0.7071067811865476, 'SQRT2': 1.4142135623730951 } - +def is_infinity(x): + return x - 1e10 == x class MathFunctions: def abs(this, args): @@ -65,22 +66,22 @@ class MathFunctions: def ceil(this, args): x = get_arg(args, 0) a = to_number(x) - if a != a: # it must be a nan - return NaN + if not is_finite(x): + return x return float(math.ceil(a)) def floor(this, args): x = get_arg(args, 0) a = to_number(x) - if a != a: # it must be a nan - return NaN + if not is_finite(x): + return x return float(math.floor(a)) def round(this, args): x = get_arg(args, 0) a = to_number(x) - if a != a: # it must be a nan - return NaN + if not is_finite(x): + return x return float(round(a)) def sin(this, args): diff --git a/lib/js2py/internals/constructors/jsstring.py b/lib/js2py/internals/constructors/jsstring.py index f2b43831..20d92a4b 100644 --- a/lib/js2py/internals/constructors/jsstring.py +++ b/lib/js2py/internals/constructors/jsstring.py @@ -1,6 +1,6 @@ from ..conversions import * from ..func_utils import * - +from six import unichr def fromCharCode(this, args): res = u'' diff --git a/lib/js2py/internals/constructors/time_helpers.py b/lib/js2py/internals/constructors/time_helpers.py index eda95fb6..2d694b58 100644 --- a/lib/js2py/internals/constructors/time_helpers.py +++ b/lib/js2py/internals/constructors/time_helpers.py @@ -38,8 +38,8 @@ def DaylightSavingTA(t): return t try: return int( - LOCAL_ZONE.dst(datetime.datetime.utcfromtimestamp( - t // 1000)).seconds) * 1000 + LOCAL_ZONE.dst(datetime.datetime(1970, 1, 1) + datetime.timedelta( + seconds=t // 1000)).seconds) * 1000 except: warnings.warn( 'Invalid datetime date, assumed DST time, may be inaccurate...', diff --git a/lib/js2py/internals/opcodes.py b/lib/js2py/internals/opcodes.py index 15c57ccd..2f6c65f5 100644 --- a/lib/js2py/internals/opcodes.py +++ b/lib/js2py/internals/opcodes.py @@ -798,7 +798,7 @@ OP_CODES = {} g = '' for g in globals(): try: - if not issubclass(globals()[g], OP_CODE) or g is 'OP_CODE': + if not issubclass(globals()[g], OP_CODE) or g == 'OP_CODE': continue except: continue diff --git a/lib/js2py/internals/prototypes/jsstring.py b/lib/js2py/internals/prototypes/jsstring.py index be38802e..e8473af8 100644 --- a/lib/js2py/internals/prototypes/jsstring.py +++ b/lib/js2py/internals/prototypes/jsstring.py @@ -22,6 +22,11 @@ def replacement_template(rep, source, span, npar): res += '$' n += 2 continue + elif rep[n + 1] == '&': + # replace with matched string + res += source[span[0]:span[1]] + n += 2 + continue elif rep[n + 1] == '`': # replace with string that is BEFORE match res += source[:span[0]] diff --git a/lib/js2py/internals/speed.py b/lib/js2py/internals/speed.py index 3ca4079e..482eb7ea 100644 --- a/lib/js2py/internals/speed.py +++ b/lib/js2py/internals/speed.py @@ -1,7 +1,14 @@ +from __future__ import print_function + from timeit import timeit from collections import namedtuple from array import array -from itertools import izip +try: + #python 2 code + from itertools import izip as zip +except ImportError: + pass + from collections import deque @@ -47,7 +54,7 @@ t = [] Type = None try: - print timeit( + print(timeit( """ t.append(4) @@ -56,7 +63,7 @@ t.pop() """, - "from __main__ import X,Y,namedtuple,array,t,add,Type, izip", - number=1000000) + "from __main__ import X,Y,namedtuple,array,t,add,Type, zip", + number=1000000)) except: raise diff --git a/lib/js2py/legecy_translators/constants.py b/lib/js2py/legecy_translators/constants.py index ea048f12..8ada25ad 100644 --- a/lib/js2py/legecy_translators/constants.py +++ b/lib/js2py/legecy_translators/constants.py @@ -1,3 +1,4 @@ +from __future__ import print_function from string import ascii_lowercase, digits ################################## StringName = u'PyJsConstantString%d_' @@ -305,4 +306,4 @@ if __name__ == '__main__': ''') t, d = remove_constants(test) - print t, d + print(t, d) diff --git a/lib/js2py/legecy_translators/exps.py b/lib/js2py/legecy_translators/exps.py index 3c8f793c..bcd09b19 100644 --- a/lib/js2py/legecy_translators/exps.py +++ b/lib/js2py/legecy_translators/exps.py @@ -16,6 +16,8 @@ If case of parsing errors it must return a pos of error. NOTES: Strings and other literals are not present so each = means assignment """ +from __future__ import print_function + from utils import * from jsparser import * @@ -80,4 +82,4 @@ def bass_translator(s): if __name__ == '__main__': - print bass_translator('3.ddsd = 40') + print(bass_translator('3.ddsd = 40')) diff --git a/lib/js2py/legecy_translators/flow.py b/lib/js2py/legecy_translators/flow.py index 9e2bb0fa..60d99aa3 100644 --- a/lib/js2py/legecy_translators/flow.py +++ b/lib/js2py/legecy_translators/flow.py @@ -9,6 +9,8 @@ FOR 123 FOR iter CONTINUE, BREAK, RETURN, LABEL, THROW, TRY, SWITCH """ +from __future__ import print_function + from utils import * from jsparser import * from nodevisitor import exp_translator @@ -477,4 +479,4 @@ def translate_flow(source): if __name__ == '__main__': #print do_dowhile('do {} while(k+f)', 0)[0] #print 'e: "%s"'%do_expression('++(c?g:h); mj', 0)[0] - print translate_flow('a; yimport test')[0] + print(translate_flow('a; yimport test')[0]) diff --git a/lib/js2py/legecy_translators/functions.py b/lib/js2py/legecy_translators/functions.py index 4825eee0..ceffc9e4 100644 --- a/lib/js2py/legecy_translators/functions.py +++ b/lib/js2py/legecy_translators/functions.py @@ -1,4 +1,6 @@ """This module removes JS functions from source code""" +from __future__ import print_function + from jsparser import * from utils import * @@ -94,5 +96,5 @@ def remove_functions(source, all_inline=False): if __name__ == '__main__': - print remove_functions( - '5+5 function n (functiona ,functionaj) {dsd s, dsdd}') + print(remove_functions( + '5+5 function n (functiona ,functionaj) {dsd s, dsdd}')) diff --git a/lib/js2py/legecy_translators/jsparser.py b/lib/js2py/legecy_translators/jsparser.py index a09537d3..7452b078 100644 --- a/lib/js2py/legecy_translators/jsparser.py +++ b/lib/js2py/legecy_translators/jsparser.py @@ -45,6 +45,7 @@ TODO """ +from __future__ import print_function from utils import * @@ -64,7 +65,7 @@ OP_METHODS = { def dbg(source): try: - with open('C:\Users\Piotrek\Desktop\dbg.py', 'w') as f: + with open(r'C:\Users\Piotrek\Desktop\dbg.py', 'w') as f: f.write(source) except: pass @@ -77,13 +78,13 @@ def indent(lines, ind=4): def inject_before_lval(source, lval, code): if source.count(lval) > 1: dbg(source) - print - print lval + print() + print(lval) raise RuntimeError('To many lvals (%s)' % lval) elif not source.count(lval): dbg(source) - print - print lval + print() + print(lval) assert lval not in source raise RuntimeError('No lval found "%s"' % lval) end = source.index(lval) diff --git a/lib/js2py/legecy_translators/nodevisitor.py b/lib/js2py/legecy_translators/nodevisitor.py index 4d50545c..5e20654b 100644 --- a/lib/js2py/legecy_translators/nodevisitor.py +++ b/lib/js2py/legecy_translators/nodevisitor.py @@ -1,3 +1,5 @@ +from __future__ import print_function + from jsparser import * from utils import * import re @@ -557,6 +559,6 @@ if __name__ == '__main__': #print 'Here', trans('(eee ) . ii [ PyJsMarker ] [ jkj ] ( j , j ) . # jiji (h , ji , i)(non )( )()()()') for e in xrange(3): - print exp_translator('jk = kk.ik++') + print(exp_translator('jk = kk.ik++')) #First line translated with PyJs: PyJsStrictEq(PyJsAdd((Js(100)*Js(50)),Js(30)), Js("5030")), yay! - print exp_translator('delete a.f') + print(exp_translator('delete a.f')) diff --git a/lib/js2py/legecy_translators/objects.py b/lib/js2py/legecy_translators/objects.py index 2abda3e8..5cb9012a 100644 --- a/lib/js2py/legecy_translators/objects.py +++ b/lib/js2py/legecy_translators/objects.py @@ -1,6 +1,8 @@ """ This module removes all objects/arrays from JS source code and replace them with LVALS. Also it has s function translating removed object/array to python code. Use this module just after removing constants. Later move on to removing functions""" +from __future__ import print_function + OBJECT_LVAL = 'PyJsLvalObject%d_' ARRAY_LVAL = 'PyJsLvalArray%d_' from utils import * @@ -180,7 +182,7 @@ def translate_object(obj, lval, obj_count=1, arr_count=1): try: key, value = spl except: #len(spl)> 2 - print 'Unusual case ' + repr(e) + print('Unusual case ' + repr(e)) key = spl[0] value = ':'.join(spl[1:]) key = key.strip() @@ -293,8 +295,8 @@ if __name__ == '__main__': #print remove_objects(test) #print list(bracket_split(' {}')) - print - print remove_arrays( + print() + print(remove_arrays( 'typeof a&&!db.test(a)&&!ib[(bb.exec(a)||["",""], [][[5][5]])[1].toLowerCase()])' - ) - print is_object('', ')') + )) + print(is_object('', ')')) diff --git a/lib/js2py/legecy_translators/translator.py b/lib/js2py/legecy_translators/translator.py index 3573f06c..c0ad8459 100644 --- a/lib/js2py/legecy_translators/translator.py +++ b/lib/js2py/legecy_translators/translator.py @@ -1,3 +1,5 @@ +from __future__ import print_function + from flow import translate_flow from constants import remove_constants, recover_constants from objects import remove_objects, remove_arrays, translate_object, translate_array, set_func_translator @@ -148,4 +150,4 @@ if __name__ == '__main__': #res = translate_js(jq) res = translate_js(t) dbg(SANDBOX % indent(res)) - print 'Done' + print('Done') diff --git a/lib/js2py/node_import.py b/lib/js2py/node_import.py index 605c4b3a..21844314 100644 --- a/lib/js2py/node_import.py +++ b/lib/js2py/node_import.py @@ -1,10 +1,16 @@ __all__ = ['require'] + import subprocess, os, codecs, glob from .evaljs import translate_js, DEFAULT_HEADER +from .translators.friendly_nodes import is_valid_py_name import six +import tempfile +import hashlib +import random + DID_INIT = False -DIRNAME = os.path.dirname(os.path.abspath(__file__)) -PY_NODE_MODULES_PATH = os.path.join(DIRNAME, 'py_node_modules') +DIRNAME = tempfile.mkdtemp() +PY_NODE_MODULES_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'py_node_modules') def _init(): @@ -46,23 +52,33 @@ GET_FROM_GLOBALS_FUNC = ''' ''' + def _get_module_py_name(module_name): return module_name.replace('-', '_') + def _get_module_var_name(module_name): - return _get_module_py_name(module_name).rpartition('/')[-1] + cand = _get_module_py_name(module_name).rpartition('/')[-1] + if not is_valid_py_name(cand): + raise ValueError( + "Invalid Python module name %s (generated from %s). Unsupported/invalid npm module specification?" % ( + repr(cand), repr(module_name))) + return cand -def _get_and_translate_npm_module(module_name, include_polyfill=False, update=False): +def _get_and_translate_npm_module(module_name, include_polyfill=False, update=False, maybe_version_str=""): assert isinstance(module_name, str), 'module_name must be a string!' + py_name = _get_module_py_name(module_name) module_filename = '%s.py' % py_name 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() - in_file_name = 'tmp0in439341018923js2py.js' - out_file_name = 'tmp0out439341018923js2py.js' + module_hash = hashlib.sha1(module_name.encode("utf-8")).hexdigest()[:15] + version = random.randrange(10000000000000) + in_file_name = 'in_%s_%d.js' % (module_hash, version) + out_file_name = 'out_%s_%d.js' % (module_hash, version) code = ADD_TO_GLOBALS_FUNC if include_polyfill: code += "\n;require('babel-polyfill');\n" @@ -74,6 +90,8 @@ def _get_and_translate_npm_module(module_name, include_polyfill=False, update=Fa f.write(code.encode('utf-8') if six.PY3 else code) pkg_name = module_name.partition('/')[0] + if maybe_version_str: + pkg_name += '@' + maybe_version_str # make sure the module is installed assert subprocess.call( 'cd %s;npm install %s' % (repr(DIRNAME), pkg_name), @@ -93,7 +111,7 @@ def _get_and_translate_npm_module(module_name, include_polyfill=False, update=Fa with codecs.open(os.path.join(DIRNAME, out_file_name), "r", "utf-8") as f: js_code = f.read() - os.remove(os.path.join(DIRNAME, out_file_name)) + print("Bundled JS library dumped at: %s" % 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 @@ -117,21 +135,25 @@ def _get_and_translate_npm_module(module_name, include_polyfill=False, update=Fa return py_code -def require(module_name, include_polyfill=False, update=False, context=None): +def require(module_name, include_polyfill=True, 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 module_name: Name of the npm module to require. For example 'esprima'. Supports specific versions via @ + specification. Eg: 'crypto-js@3.3'. :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. + for some modules that use unsupported features of JS6 such as Map or typed arrays. :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) + module_name, maybe_version = (module_name+"@@@").split('@')[:2] + + py_code = _get_and_translate_npm_module(module_name, include_polyfill=include_polyfill, update=update, + maybe_version_str=maybe_version) # 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): @@ -141,5 +163,5 @@ def require(module_name, include_polyfill=False, update=False, context=None): 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) + exec(py_code, context) return context['var'][_get_module_var_name(module_name)].to_py() diff --git a/lib/js2py/prototypes/jsstring.py b/lib/js2py/prototypes/jsstring.py index ee320709..a313bfb9 100644 --- a/lib/js2py/prototypes/jsstring.py +++ b/lib/js2py/prototypes/jsstring.py @@ -17,6 +17,11 @@ def replacement_template(rep, source, span, npar): res += '$' n += 2 continue + elif rep[n + 1] == '&': + # replace with matched string + res += source[span[0]:span[1]] + n += 2 + continue elif rep[n + 1] == '`': # replace with string that is BEFORE match res += source[:span[0]] diff --git a/lib/js2py/translators/translating_nodes.py b/lib/js2py/translators/translating_nodes.py index 371c8ede..4e2b5760 100644 --- a/lib/js2py/translators/translating_nodes.py +++ b/lib/js2py/translators/translating_nodes.py @@ -14,26 +14,36 @@ if six.PY3: LINE_LEN_LIMIT = 400 # 200 # or any other value - the larger the smaller probability of errors :) -class ForController: +class LoopController: def __init__(self): - self.inside = [False] - self.update = '' + self.update = [""] + self.label_to_update_idx = {} - def enter_for(self, update): - self.inside.append(True) - self.update = update + def enter(self, update=""): + self.update.append(update) - def leave_for(self): - self.inside.pop() + def leave(self): + self.update.pop() + + def get_update(self, label=None): + if label is None: + return self.update[-1] + if label not in self.label_to_update_idx: + raise SyntaxError("Undefined label %s" % label) + if self.label_to_update_idx[label] >= len(self.update): + raise SyntaxError("%s is not a iteration statement label?" % label) + return self.update[self.label_to_update_idx[label]] + + def register_label(self, label): + if label in self.label_to_update_idx: + raise SyntaxError("label %s already used") + self.label_to_update_idx[label] = len(self.update) + + def deregister_label(self, label): + del self.label_to_update_idx[label] - def enter_other(self): - self.inside.append(False) - def leave_other(self): - self.inside.pop() - def is_inside(self): - return self.inside[-1] class InlineStack: @@ -86,9 +96,10 @@ class ContextStack: def clean_stacks(): - global Context, inline_stack + global Context, inline_stack, loop_controller Context = ContextStack() inline_stack = InlineStack() + loop_controller = LoopController() def to_key(literal_or_identifier): @@ -108,6 +119,13 @@ def to_key(literal_or_identifier): else: return unicode(k) +def is_iteration_statement(cand): + if not isinstance(cand, dict): + # Multiple statements. + return False + return cand.get("type", "?") in {"ForStatement", "ForInStatement", "WhileStatement", "DoWhileStatement"} + + def trans(ele, standard=False): """Translates esprima syntax tree to python by delegating to appropriate translating node""" @@ -367,9 +385,14 @@ def BreakStatement(type, label): def ContinueStatement(type, label): if label: - return 'raise %s("Continued")\n' % (get_continue_label(label['name'])) + maybe_update_expr = loop_controller.get_update(label=label['name']) + continue_stmt = 'raise %s("Continued")\n' % (get_continue_label(label['name'])) else: - return 'continue\n' + maybe_update_expr = loop_controller.get_update() + continue_stmt = "continue\n" + if maybe_update_expr: + return "# continue update\n%s\n%s" % (maybe_update_expr, continue_stmt) + return continue_stmt def ReturnStatement(type, argument): @@ -386,24 +409,28 @@ def DebuggerStatement(type): def DoWhileStatement(type, body, test): - inside = trans(body) + 'if not %s:\n' % trans(test) + indent('break\n') + loop_controller.enter() + body_code = trans(body) + loop_controller.leave() + inside = body_code + 'if not %s:\n' % trans(test) + indent('break\n') result = 'while 1:\n' + indent(inside) return result def ForStatement(type, init, test, update, body): - update = indent(trans(update)) if update else '' + update = trans(update) if update else '' init = trans(init) if init else '' if not init.endswith('\n'): init += '\n' test = trans(test) if test else '1' + loop_controller.enter(update) if not update: result = '#for JS loop\n%swhile %s:\n%s%s\n' % ( init, test, indent(trans(body)), update) else: result = '#for JS loop\n%swhile %s:\n' % (init, test) - body = 'try:\n%sfinally:\n %s\n' % (indent(trans(body)), update) - result += indent(body) + result += indent("%s# update\n%s\n" % (trans(body), update)) + loop_controller.leave() return result @@ -422,7 +449,9 @@ def ForInStatement(type, left, right, body, each): name = left['name'] else: raise RuntimeError('Unusual ForIn loop') + loop_controller.enter() res += indent('var.put(%s, PyJsTemp)\n' % repr(name) + trans(body)) + loop_controller.leave() return res @@ -438,20 +467,23 @@ def IfStatement(type, test, consequent, alternate): def LabeledStatement(type, label, body): # todo consider using smarter approach! + label_name = label['name'] + loop_controller.register_label(label_name) inside = trans(body) + loop_controller.deregister_label(label_name) defs = '' - if inside.startswith('while ') or inside.startswith( - 'for ') or inside.startswith('#for'): + if is_iteration_statement(body) and (inside.startswith('while ') or inside.startswith( + 'for ') or inside.startswith('#for')): # we have to add contine label as well... # 3 or 1 since #for loop type has more lines before real for. sep = 1 if not inside.startswith('#for') else 3 - cont_label = get_continue_label(label['name']) + cont_label = get_continue_label(label_name) temp = inside.split('\n') injected = 'try:\n' + '\n'.join(temp[sep:]) injected += 'except %s:\n pass\n' % cont_label inside = '\n'.join(temp[:sep]) + '\n' + indent(injected) defs += 'class %s(Exception): pass\n' % cont_label - break_label = get_break_label(label['name']) + break_label = get_break_label(label_name) inside = 'try:\n%sexcept %s:\n pass\n' % (indent(inside), break_label) defs += 'class %s(Exception): pass\n' % break_label return defs + inside @@ -546,7 +578,11 @@ def VariableDeclaration(type, declarations, kind): def WhileStatement(type, test, body): - result = 'while %s:\n' % trans(test) + indent(trans(body)) + test_code = trans(test) + loop_controller.enter() + body_code = trans(body) + loop_controller.leave() + result = 'while %s:\n' % test_code + indent(body_code) return result diff --git a/lib/js2py/translators/translator.py b/lib/js2py/translators/translator.py index 16ed5bdb..5ec47002 100644 --- a/lib/js2py/translators/translator.py +++ b/lib/js2py/translators/translator.py @@ -55,16 +55,19 @@ def dbg(x): """does nothing, legacy dummy function""" return '' +# Another way of doing that would be with my auto esprima translation but its much slower: +# parsed = esprima.parse(js).to_dict() +def pyjsparser_parse_fn(code): + parser = pyjsparser.PyJsParser() + return parser.parse(code) -def translate_js(js, HEADER=DEFAULT_HEADER, use_compilation_plan=False): +def translate_js(js, HEADER=DEFAULT_HEADER, use_compilation_plan=False, parse_fn=pyjsparser_parse_fn): """js has to be a javascript source code. returns equivalent python code.""" if use_compilation_plan and not '//' in js and not '/*' in js: return translate_js_with_compilation_plan(js, HEADER=HEADER) - parser = pyjsparser.PyJsParser() - parsed = parser.parse(js) # js to esprima syntax tree - # Another way of doing that would be with my auto esprima translation but its much slower and causes import problems: - # parsed = esprima.parse(js).to_dict() + + parsed = parse_fn(js) translating_nodes.clean_stacks() return HEADER + translating_nodes.trans( parsed) # syntax tree to python code diff --git a/lib/js2py/utils/injector.py b/lib/js2py/utils/injector.py index ea236d5e..88e0d93e 100644 --- a/lib/js2py/utils/injector.py +++ b/lib/js2py/utils/injector.py @@ -26,17 +26,19 @@ def fix_js_args(func): return func code = append_arguments(six.get_function_code(func), ('this', 'arguments')) - return types.FunctionType( + result = types.FunctionType( code, six.get_function_globals(func), func.__name__, closure=six.get_function_closure(func)) + return result def append_arguments(code_obj, new_locals): co_varnames = code_obj.co_varnames # Old locals co_names = code_obj.co_names # Old globals - co_names += tuple(e for e in new_locals if e not in co_names) + new_args = tuple(e for e in new_locals if e not in co_names) + co_names += new_args co_argcount = code_obj.co_argcount # Argument count co_code = code_obj.co_code # The actual bytecode as a string @@ -76,26 +78,51 @@ def append_arguments(code_obj, new_locals): names_to_varnames = dict( (co_names.index(name), varnames.index(name)) for name in new_locals) + is_new_bytecode = sys.version_info >= (3, 11) # Now we modify the actual bytecode modified = [] + drop_future_cache = False for inst in instructions(code_obj): + if is_new_bytecode and inst.opname == "CACHE": + assert inst.arg == 0 + if not drop_future_cache: + modified.extend(write_instruction(inst.opcode, inst.arg)) + else: + # We need to inject NOOP to not break jumps :( + modified.extend(write_instruction(dis.opmap["NOP"], 0)) + + continue op, arg = inst.opcode, inst.arg # If the instruction is a LOAD_GLOBAL, we have to check to see if # it's one of the globals that we are replacing. Either way, # update its arg using the appropriate dict. + drop_future_cache = False if inst.opcode == LOAD_GLOBAL: - if inst.arg in names_to_varnames: + idx = inst.arg + if is_new_bytecode: + idx = idx // 2 + if idx in names_to_varnames: op = LOAD_FAST - arg = names_to_varnames[inst.arg] - elif inst.arg in name_translations: - arg = name_translations[inst.arg] + arg = names_to_varnames[idx] + # Cache is not present after LOAD_FAST and needs to be removed. + drop_future_cache = True + elif idx in name_translations: + tgt = name_translations[idx] + if is_new_bytecode: + tgt = 2*tgt + (inst.arg % 2) + arg = tgt else: - raise ValueError("a name was lost in translation") + raise(ValueError("a name was lost in translation last instruction %s" % str(inst))) # If it accesses co_varnames or co_names then update its argument. elif inst.opcode in opcode.haslocal: arg = varname_translations[inst.arg] elif inst.opcode in opcode.hasname: + # for example STORE_GLOBAL arg = name_translations[inst.arg] + elif is_new_bytecode and inst.opcode in opcode.hasfree: + # Python 3.11+ adds refs at the end (after locals), for whatever reason... + if inst.argval not in code_obj.co_varnames[:code_obj.co_argcount]: # we do not need to remap existing arguments, they are not shifted by new ones. + arg = inst.arg + len(new_locals) modified.extend(write_instruction(op, arg)) if six.PY2: code = ''.join(modified) @@ -113,23 +140,26 @@ def append_arguments(code_obj, new_locals): code_obj.co_filename, code_obj.co_name, code_obj.co_firstlineno, code_obj.co_lnotab, code_obj.co_freevars, code_obj.co_cellvars) - # Done modifying codestring - make the code object if hasattr(code_obj, "replace"): # Python 3.8+ - return code_obj.replace( + code_obj = 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) + return code_obj else: return types.CodeType(*args) -def instructions(code_obj): - # easy for python 3.4+ - if sys.version_info >= (3, 4): +def instructions(code_obj, show_cache=True): + if sys.version_info >= (3, 11): + # Python 3.11 introduced "cache instructions", hidden by default. + for inst in dis.Bytecode(code_obj, show_caches=show_cache): + yield inst + elif sys.version_info >= (3, 4): # easy for python 3.4+ for inst in dis.Bytecode(code_obj): yield inst else: @@ -171,7 +201,7 @@ def write_instruction(op, arg): chr((arg >> 8) & 255) ] else: - raise ValueError("Invalid oparg: {0} is too large".format(oparg)) + raise ValueError("Invalid oparg: {0} is too large".format(arg)) else: # python 3.6+ uses wordcode instead of bytecode and they already supply all the EXTENDEND_ARG ops :) if arg is None: return [chr(op), 0] @@ -191,6 +221,7 @@ def write_instruction(op, arg): # raise ValueError("Invalid oparg: {0} is too large".format(oparg)) + def check(code_obj): old_bytecode = code_obj.co_code insts = list(instructions(code_obj)) @@ -221,24 +252,99 @@ def check(code_obj): 'Your python version made changes to the bytecode') + + +def signature(func): + code_obj = six.get_function_code(func) + return (code_obj.co_nlocals, code_obj.co_argcount, code_obj.co_nlocals, code_obj.co_stacksize, + code_obj.co_flags, code_obj.co_names, code_obj.co_varnames, + code_obj.co_filename, + code_obj.co_freevars, code_obj.co_cellvars) + check(six.get_function_code(check)) + + +def compare_func(fake_func, gt_func): + print(signature(fake_func)) + print(signature(gt_func)) + assert signature(fake_func) == signature(gt_func) + fake_ins = list(instructions(six.get_function_code(fake_func), show_cache=False)) + real_ins = list(instructions(six.get_function_code(gt_func), show_cache=False)) + offset = 0 + pos = 0 + for e in fake_ins: + if e.opname == "NOP": + offset += 1 # ignore NOPs that are inserted in place of old cache. + else: + real = real_ins[pos] + fake = e + print("POS %d OFFSET: %d FAKE VS REAL" % (pos, offset)) + print(fake) + print(real) + assert fake.opcode == real.opcode + if fake.opcode in dis.hasjabs or fake.opcode in dis.hasjrel: + pass + else: + assert fake.arg == real.arg + assert fake.argval == real.argval or fake.opname in ["LOAD_CONST"] + assert fake.is_jump_target == real.is_jump_target + + pos += 1 + assert pos == len(real_ins), (pos, len(real_ins)) + print("DONE, looks good.") + + if __name__ == '__main__': - x = 'Wrong' - dick = 3000 + import faulthandler - def func(a): - print(x, y, z, a) - print(dick) - d = (x, ) - for e in (e for e in x): - print(e) - return x, y, z + faulthandler.enable() - func2 = types.FunctionType( - append_arguments(six.get_function_code(func), ('x', 'y', 'z')), - six.get_function_globals(func), - func.__name__, - closure=six.get_function_closure(func)) - args = (2, 2, 3, 4), 3, 4 - assert func2(1, *args) == args + def func(cmpfn): + if not this.Class in ('Array', 'Arguments'): + return this.to_object() # do nothing + arr = [] + for i in xrange(len(this)): + arr.append(this.get(six.text_type(i))) + + if not arr: + return this + if not cmpfn.is_callable(): + cmpfn = None + cmp = lambda a, b: sort_compare(a, b, cmpfn) + if six.PY3: + key = functools.cmp_to_key(cmp) + arr.sort(key=key) + else: + arr.sort(cmp=cmp) + for i in xrange(len(arr)): + this.put(six.text_type(i), arr[i]) + + return this + + + def func_gt(cmpfn, this, arguments): + if not this.Class in ('Array', 'Arguments'): + return this.to_object() # do nothing + arr = [] + for i in xrange(len(this)): + arr.append(this.get(six.text_type(i))) + + if not arr: + return this + if not cmpfn.is_callable(): + cmpfn = None + cmp = lambda a, b: sort_compare(a, b, cmpfn) + if six.PY3: + key = functools.cmp_to_key(cmp) + arr.sort(key=key) + else: + arr.sort(cmp=cmp) + for i in xrange(len(arr)): + this.put(six.text_type(i), arr[i]) + + return this + + + func2 = fix_js_args(func) + compare_func(func2, func_gt)