Aggiornata libreria js2py

This commit is contained in:
marco
2022-12-04 14:33:39 +01:00
parent d7ab122d31
commit e91e29b95d
27 changed files with 681 additions and 161 deletions
+11 -8
View File
@@ -126,7 +126,7 @@ def HJs(val):
except Exception as e: except Exception as e:
message = 'your Python function failed! ' message = 'your Python function failed! '
try: try:
message += e.message message += str(e)
except: except:
pass pass
raise MakeError('Error', message) raise MakeError('Error', message)
@@ -319,7 +319,7 @@ class PyJs(object):
#prop = prop.value #prop = prop.value
if self.Class == 'Undefined' or self.Class == 'Null': if self.Class == 'Undefined' or self.Class == 'Null':
raise MakeError('TypeError', 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): if not isinstance(prop, basestring):
prop = prop.to_string().value prop = prop.to_string().value
if not isinstance(prop, basestring): raise RuntimeError('Bug') if not isinstance(prop, basestring): raise RuntimeError('Bug')
@@ -361,7 +361,7 @@ class PyJs(object):
* / % + - << >> & ^ |''' * / % + - << >> & ^ |'''
if self.Class == 'Undefined' or self.Class == 'Null': if self.Class == 'Undefined' or self.Class == 'Null':
raise MakeError('TypeError', 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): if not isinstance(prop, basestring):
prop = prop.to_string().value prop = prop.to_string().value
if NUMPY_AVAILABLE and prop.isdigit(): if NUMPY_AVAILABLE and prop.isdigit():
@@ -991,7 +991,8 @@ class PyJs(object):
cand = self.get(prop) cand = self.get(prop)
if not cand.is_callable(): if not cand.is_callable():
raise MakeError('TypeError', 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) return cand.call(self, args)
def to_python(self): def to_python(self):
@@ -1304,7 +1305,7 @@ class PyObjectWrapper(PyJs):
except Exception as e: except Exception as e:
message = 'your Python function failed! ' message = 'your Python function failed! '
try: try:
message += e.message message += str(e)
except: except:
pass pass
raise MakeError('Error', message) raise MakeError('Error', message)
@@ -1464,9 +1465,11 @@ class PyJsFunction(PyJs):
except NotImplementedError: except NotImplementedError:
raise raise
except RuntimeError as e: # maximum recursion except RuntimeError as e: # maximum recursion
raise MakeError( try:
'RangeError', e.message if msg = e.message
not isinstance(e, NotImplementedError) else 'Not implemented!') except:
msg = repr(e)
raise MakeError('RangeError', msg)
def has_instance(self, other): def has_instance(self, other):
# I am not sure here so instanceof may not work lol. # I am not sure here so instanceof may not work lol.
+185 -14
View File
@@ -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) mili = args[6].to_number() if l > 6 else Js(0)
if not y.is_nan() and 0 <= y.value <= 99: if not y.is_nan() and 0 <= y.value <= 99:
y = y + Js(1900) y = y + Js(1900)
t = TimeClip(MakeDate(MakeDay(y, m, dt), MakeTime(h, mi, sec, mili))) return TimeClip(MakeDate(MakeDay(y, m, dt), MakeTime(h, mi, sec, mili)))
return PyJsDate(t, prototype=DatePrototype)
@Js @Js
@@ -76,11 +75,12 @@ class PyJsDate(PyJs):
# todo fix this problematic datetime part # todo fix this problematic datetime part
def to_local_dt(self): def to_local_dt(self):
return datetime.datetime.utcfromtimestamp( return datetime.datetime(1970, 1, 1) + datetime.timedelta(
UTCToLocal(self.value) // 1000) seconds=UTCToLocal(self.value) // 1000)
def to_utc_dt(self): 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): def local_strftime(self, pattern):
if self.value is NaN: if self.value is NaN:
@@ -118,20 +118,39 @@ class PyJsDate(PyJs):
def parse_date(py_string): # todo support all date string formats def parse_date(py_string): # todo support all date string formats
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: try:
try: dt = datetime.datetime.strptime(py_string, date_format)
dt = datetime.datetime.strptime(py_string, "%Y-%m-%dT%H:%M:%S.%fZ") except ValueError:
except: continue
dt = datetime.datetime.strptime(py_string, "%Y-%m-%dT%H:%M:%SZ") else:
return MakeDate( return MakeDate(
MakeDay(Js(dt.year), Js(dt.month - 1), Js(dt.day)), MakeDay(Js(dt.year), Js(dt.month - 1), Js(dt.day)),
MakeTime( MakeTime(
Js(dt.hour), Js(dt.minute), Js(dt.second), Js(dt.hour), Js(dt.minute), Js(dt.second),
Js(dt.microsecond // 1000))) Js(dt.microsecond // 1000)))
except:
raise MakeError( raise MakeError(
'TypeError', 'TypeError',
'Could not parse date %s - unsupported date format. Currently only supported format is RFC3339 utc. Sorry!' '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) % py_string)
@@ -332,7 +351,7 @@ class DateProto:
check_date(this) check_date(this)
t = UTCToLocal(this.value) t = UTCToLocal(this.value)
tim = MakeTime( 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))) u = TimeClip(LocalToUTC(MakeDate(Day(t), tim)))
this.value = u this.value = u
return u return u
@@ -341,12 +360,164 @@ class DateProto:
check_date(this) check_date(this)
t = this.value t = this.value
tim = MakeTime( 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)) u = TimeClip(MakeDate(Day(t), tim))
this.value = u this.value = u
return 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(): def toUTCString():
check_date(this) check_date(this)
+2 -2
View File
@@ -36,8 +36,8 @@ def DaylightSavingTA(t):
return t return t
try: try:
return int( return int(
LOCAL_ZONE.dst(datetime.datetime.utcfromtimestamp( LOCAL_ZONE.dst(datetime.datetime(1970, 1, 1) + datetime.timedelta(
t // 1000)).seconds) * 1000 seconds=t // 1000)).seconds) * 1000
except: except:
warnings.warn( warnings.warn(
'Invalid datetime date, assumed DST time, may be inaccurate...', 'Invalid datetime date, assumed DST time, may be inaccurate...',
+5 -12
View File
@@ -53,7 +53,7 @@ def write_file_contents(path_or_file, contents):
if hasattr(path_or_file, 'write'): if hasattr(path_or_file, 'write'):
path_or_file.write(contents) path_or_file.write(contents)
else: 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) f.write(contents)
@@ -238,6 +238,10 @@ class EvalJs(object):
self.execute_debug(code) self.execute_debug(code)
return self['PyJsEvalResult'] return self['PyJsEvalResult']
@property
def context(self):
return self._context
def __getattr__(self, var): def __getattr__(self, var):
return getattr(self._var, var) return getattr(self._var, var)
@@ -268,14 +272,3 @@ class EvalJs(object):
else: else:
sys.stderr.write('EXCEPTION: ' + str(e) + '\n') sys.stderr.write('EXCEPTION: ' + str(e) + '\n')
time.sleep(0.01) 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()
+1 -1
View File
@@ -6,7 +6,7 @@ def console():
@Js @Js
def log(): def log():
print(arguments[0]) print(" ".join(repr(element) for element in arguments.to_list()))
console.put('log', log) console.put('log', log)
console.put('debug', log) console.put('debug', log)
+1
View File
@@ -0,0 +1 @@
from .seval import eval_js_vm
+4 -3
View File
@@ -602,11 +602,12 @@ class PyJsDate(PyJs):
# todo fix this problematic datetime part # todo fix this problematic datetime part
def to_local_dt(self): def to_local_dt(self):
return datetime.datetime.utcfromtimestamp( return datetime.datetime(1970, 1, 1) + datetime.timedelta(
self.UTCToLocal(self.value) // 1000) seconds=self.UTCToLocal(self.value) // 1000)
def to_utc_dt(self): 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): def local_strftime(self, pattern):
if self.value is NaN: if self.value is NaN:
+160 -8
View File
@@ -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) mili = args[6].to_number() if l > 6 else Js(0)
if not y.is_nan() and 0 <= y.value <= 99: if not y.is_nan() and 0 <= y.value <= 99:
y = y + Js(1900) y = y + Js(1900)
t = TimeClip(MakeDate(MakeDay(y, m, dt), MakeTime(h, mi, sec, mili))) return TimeClip(MakeDate(MakeDay(y, m, dt), MakeTime(h, mi, sec, mili)))
return PyJsDate(t, prototype=DatePrototype)
@Js @Js
@@ -76,11 +75,12 @@ class PyJsDate(PyJs):
# todo fix this problematic datetime part # todo fix this problematic datetime part
def to_local_dt(self): def to_local_dt(self):
return datetime.datetime.utcfromtimestamp( return datetime.datetime(1970, 1, 1) + datetime.timedelta(
UTCToLocal(self.value) // 1000) seconds=UTCToLocal(self.value) // 1000)
def to_utc_dt(self): 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): def local_strftime(self, pattern):
if self.value is NaN: if self.value is NaN:
@@ -332,7 +332,7 @@ class DateProto:
check_date(this) check_date(this)
t = UTCToLocal(this.value) t = UTCToLocal(this.value)
tim = MakeTime( 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))) u = TimeClip(LocalToUTC(MakeDate(Day(t), tim)))
this.value = u this.value = u
return u return u
@@ -341,12 +341,164 @@ class DateProto:
check_date(this) check_date(this)
t = this.value t = this.value
tim = MakeTime( 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)) u = TimeClip(MakeDate(Day(t), tim))
this.value = u this.value = u
return 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(): def toUTCString():
check_date(this) check_date(this)
+8 -7
View File
@@ -16,7 +16,8 @@ CONSTANTS = {
'SQRT1_2': 0.7071067811865476, 'SQRT1_2': 0.7071067811865476,
'SQRT2': 1.4142135623730951 'SQRT2': 1.4142135623730951
} }
def is_infinity(x):
return x - 1e10 == x
class MathFunctions: class MathFunctions:
def abs(this, args): def abs(this, args):
@@ -65,22 +66,22 @@ class MathFunctions:
def ceil(this, args): def ceil(this, args):
x = get_arg(args, 0) x = get_arg(args, 0)
a = to_number(x) a = to_number(x)
if a != a: # it must be a nan if not is_finite(x):
return NaN return x
return float(math.ceil(a)) return float(math.ceil(a))
def floor(this, args): def floor(this, args):
x = get_arg(args, 0) x = get_arg(args, 0)
a = to_number(x) a = to_number(x)
if a != a: # it must be a nan if not is_finite(x):
return NaN return x
return float(math.floor(a)) return float(math.floor(a))
def round(this, args): def round(this, args):
x = get_arg(args, 0) x = get_arg(args, 0)
a = to_number(x) a = to_number(x)
if a != a: # it must be a nan if not is_finite(x):
return NaN return x
return float(round(a)) return float(round(a))
def sin(this, args): def sin(this, args):
+1 -1
View File
@@ -1,6 +1,6 @@
from ..conversions import * from ..conversions import *
from ..func_utils import * from ..func_utils import *
from six import unichr
def fromCharCode(this, args): def fromCharCode(this, args):
res = u'' res = u''
@@ -38,8 +38,8 @@ def DaylightSavingTA(t):
return t return t
try: try:
return int( return int(
LOCAL_ZONE.dst(datetime.datetime.utcfromtimestamp( LOCAL_ZONE.dst(datetime.datetime(1970, 1, 1) + datetime.timedelta(
t // 1000)).seconds) * 1000 seconds=t // 1000)).seconds) * 1000
except: except:
warnings.warn( warnings.warn(
'Invalid datetime date, assumed DST time, may be inaccurate...', 'Invalid datetime date, assumed DST time, may be inaccurate...',
+1 -1
View File
@@ -798,7 +798,7 @@ OP_CODES = {}
g = '' g = ''
for g in globals(): for g in globals():
try: 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 continue
except: except:
continue continue
@@ -22,6 +22,11 @@ def replacement_template(rep, source, span, npar):
res += '$' res += '$'
n += 2 n += 2
continue continue
elif rep[n + 1] == '&':
# replace with matched string
res += source[span[0]:span[1]]
n += 2
continue
elif rep[n + 1] == '`': elif rep[n + 1] == '`':
# replace with string that is BEFORE match # replace with string that is BEFORE match
res += source[:span[0]] res += source[:span[0]]
+11 -4
View File
@@ -1,7 +1,14 @@
from __future__ import print_function
from timeit import timeit from timeit import timeit
from collections import namedtuple from collections import namedtuple
from array import array 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 from collections import deque
@@ -47,7 +54,7 @@ t = []
Type = None Type = None
try: try:
print timeit( print(timeit(
""" """
t.append(4) t.append(4)
@@ -56,7 +63,7 @@ t.pop()
""", """,
"from __main__ import X,Y,namedtuple,array,t,add,Type, izip", "from __main__ import X,Y,namedtuple,array,t,add,Type, zip",
number=1000000) number=1000000))
except: except:
raise raise
+2 -1
View File
@@ -1,3 +1,4 @@
from __future__ import print_function
from string import ascii_lowercase, digits from string import ascii_lowercase, digits
################################## ##################################
StringName = u'PyJsConstantString%d_' StringName = u'PyJsConstantString%d_'
@@ -305,4 +306,4 @@ if __name__ == '__main__':
''') ''')
t, d = remove_constants(test) t, d = remove_constants(test)
print t, d print(t, d)
+3 -1
View File
@@ -16,6 +16,8 @@ If case of parsing errors it must return a pos of error.
NOTES: NOTES:
Strings and other literals are not present so each = means assignment Strings and other literals are not present so each = means assignment
""" """
from __future__ import print_function
from utils import * from utils import *
from jsparser import * from jsparser import *
@@ -80,4 +82,4 @@ def bass_translator(s):
if __name__ == '__main__': if __name__ == '__main__':
print bass_translator('3.ddsd = 40') print(bass_translator('3.ddsd = 40'))
+3 -1
View File
@@ -9,6 +9,8 @@ FOR 123
FOR iter FOR iter
CONTINUE, BREAK, RETURN, LABEL, THROW, TRY, SWITCH CONTINUE, BREAK, RETURN, LABEL, THROW, TRY, SWITCH
""" """
from __future__ import print_function
from utils import * from utils import *
from jsparser import * from jsparser import *
from nodevisitor import exp_translator from nodevisitor import exp_translator
@@ -477,4 +479,4 @@ def translate_flow(source):
if __name__ == '__main__': if __name__ == '__main__':
#print do_dowhile('do {} while(k+f)', 0)[0] #print do_dowhile('do {} while(k+f)', 0)[0]
#print 'e: "%s"'%do_expression('++(c?g:h); mj', 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])
+4 -2
View File
@@ -1,4 +1,6 @@
"""This module removes JS functions from source code""" """This module removes JS functions from source code"""
from __future__ import print_function
from jsparser import * from jsparser import *
from utils import * from utils import *
@@ -94,5 +96,5 @@ def remove_functions(source, all_inline=False):
if __name__ == '__main__': if __name__ == '__main__':
print remove_functions( print(remove_functions(
'5+5 function n (functiona ,functionaj) {dsd s, dsdd}') '5+5 function n (functiona ,functionaj) {dsd s, dsdd}'))
+6 -5
View File
@@ -45,6 +45,7 @@ TODO
""" """
from __future__ import print_function
from utils import * from utils import *
@@ -64,7 +65,7 @@ OP_METHODS = {
def dbg(source): def dbg(source):
try: 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) f.write(source)
except: except:
pass pass
@@ -77,13 +78,13 @@ def indent(lines, ind=4):
def inject_before_lval(source, lval, code): def inject_before_lval(source, lval, code):
if source.count(lval) > 1: if source.count(lval) > 1:
dbg(source) dbg(source)
print print()
print lval print(lval)
raise RuntimeError('To many lvals (%s)' % lval) raise RuntimeError('To many lvals (%s)' % lval)
elif not source.count(lval): elif not source.count(lval):
dbg(source) dbg(source)
print print()
print lval print(lval)
assert lval not in source assert lval not in source
raise RuntimeError('No lval found "%s"' % lval) raise RuntimeError('No lval found "%s"' % lval)
end = source.index(lval) end = source.index(lval)
+4 -2
View File
@@ -1,3 +1,5 @@
from __future__ import print_function
from jsparser import * from jsparser import *
from utils import * from utils import *
import re import re
@@ -557,6 +559,6 @@ if __name__ == '__main__':
#print 'Here', trans('(eee ) . ii [ PyJsMarker ] [ jkj ] ( j , j ) . #print 'Here', trans('(eee ) . ii [ PyJsMarker ] [ jkj ] ( j , j ) .
# jiji (h , ji , i)(non )( )()()()') # jiji (h , ji , i)(non )( )()()()')
for e in xrange(3): 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! #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'))
+7 -5
View File
@@ -1,6 +1,8 @@
""" This module removes all objects/arrays from JS source code and replace them with LVALS. """ 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. 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""" Use this module just after removing constants. Later move on to removing functions"""
from __future__ import print_function
OBJECT_LVAL = 'PyJsLvalObject%d_' OBJECT_LVAL = 'PyJsLvalObject%d_'
ARRAY_LVAL = 'PyJsLvalArray%d_' ARRAY_LVAL = 'PyJsLvalArray%d_'
from utils import * from utils import *
@@ -180,7 +182,7 @@ def translate_object(obj, lval, obj_count=1, arr_count=1):
try: try:
key, value = spl key, value = spl
except: #len(spl)> 2 except: #len(spl)> 2
print 'Unusual case ' + repr(e) print('Unusual case ' + repr(e))
key = spl[0] key = spl[0]
value = ':'.join(spl[1:]) value = ':'.join(spl[1:])
key = key.strip() key = key.strip()
@@ -293,8 +295,8 @@ if __name__ == '__main__':
#print remove_objects(test) #print remove_objects(test)
#print list(bracket_split(' {}')) #print list(bracket_split(' {}'))
print print()
print remove_arrays( print(remove_arrays(
'typeof a&&!db.test(a)&&!ib[(bb.exec(a)||["",""], [][[5][5]])[1].toLowerCase()])' 'typeof a&&!db.test(a)&&!ib[(bb.exec(a)||["",""], [][[5][5]])[1].toLowerCase()])'
) ))
print is_object('', ')') print(is_object('', ')'))
+3 -1
View File
@@ -1,3 +1,5 @@
from __future__ import print_function
from flow import translate_flow from flow import translate_flow
from constants import remove_constants, recover_constants from constants import remove_constants, recover_constants
from objects import remove_objects, remove_arrays, translate_object, translate_array, set_func_translator 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(jq)
res = translate_js(t) res = translate_js(t)
dbg(SANDBOX % indent(res)) dbg(SANDBOX % indent(res))
print 'Done' print('Done')
+34 -12
View File
@@ -1,10 +1,16 @@
__all__ = ['require'] __all__ = ['require']
import subprocess, os, codecs, glob import subprocess, os, codecs, glob
from .evaljs import translate_js, DEFAULT_HEADER from .evaljs import translate_js, DEFAULT_HEADER
from .translators.friendly_nodes import is_valid_py_name
import six import six
import tempfile
import hashlib
import random
DID_INIT = False DID_INIT = False
DIRNAME = os.path.dirname(os.path.abspath(__file__)) DIRNAME = tempfile.mkdtemp()
PY_NODE_MODULES_PATH = os.path.join(DIRNAME, 'py_node_modules') PY_NODE_MODULES_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'py_node_modules')
def _init(): def _init():
@@ -46,23 +52,33 @@ GET_FROM_GLOBALS_FUNC = '''
''' '''
def _get_module_py_name(module_name): def _get_module_py_name(module_name):
return module_name.replace('-', '_') return module_name.replace('-', '_')
def _get_module_var_name(module_name): 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!' assert isinstance(module_name, str), 'module_name must be a string!'
py_name = _get_module_py_name(module_name) py_name = _get_module_py_name(module_name)
module_filename = '%s.py' % py_name module_filename = '%s.py' % py_name
var_name = _get_module_var_name(module_name) var_name = _get_module_var_name(module_name)
if not os.path.exists(os.path.join(PY_NODE_MODULES_PATH, if not os.path.exists(os.path.join(PY_NODE_MODULES_PATH,
module_filename)) or update: module_filename)) or update:
_init() _init()
in_file_name = 'tmp0in439341018923js2py.js' module_hash = hashlib.sha1(module_name.encode("utf-8")).hexdigest()[:15]
out_file_name = 'tmp0out439341018923js2py.js' 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 code = ADD_TO_GLOBALS_FUNC
if include_polyfill: if include_polyfill:
code += "\n;require('babel-polyfill');\n" 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) f.write(code.encode('utf-8') if six.PY3 else code)
pkg_name = module_name.partition('/')[0] pkg_name = module_name.partition('/')[0]
if maybe_version_str:
pkg_name += '@' + maybe_version_str
# make sure the module is installed # make sure the module is installed
assert subprocess.call( assert subprocess.call(
'cd %s;npm install %s' % (repr(DIRNAME), pkg_name), '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", with codecs.open(os.path.join(DIRNAME, out_file_name), "r",
"utf-8") as f: "utf-8") as f:
js_code = f.read() 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: if len(js_code) < 50:
raise RuntimeError("Candidate JS bundle too short - likely browserify issue.") raise RuntimeError("Candidate JS bundle too short - likely browserify issue.")
js_code += GET_FROM_GLOBALS_FUNC 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 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 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. finally translates the generated JS bundle to Python via Js2Py.
Returns a pure python object that behaves like the installed module. Nice! 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 :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 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 :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. 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. :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... # this is a bit hacky but we need to strip the default header from the generated code...
if context is not None: if context is not None:
if not py_code.startswith(DEFAULT_HEADER): 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." assert py_code.startswith(DEFAULT_HEADER), "Unexpected header."
py_code = py_code[len(DEFAULT_HEADER):] py_code = py_code[len(DEFAULT_HEADER):]
context = {} if context is None else context 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() return context['var'][_get_module_var_name(module_name)].to_py()
+5
View File
@@ -17,6 +17,11 @@ def replacement_template(rep, source, span, npar):
res += '$' res += '$'
n += 2 n += 2
continue continue
elif rep[n + 1] == '&':
# replace with matched string
res += source[span[0]:span[1]]
n += 2
continue
elif rep[n + 1] == '`': elif rep[n + 1] == '`':
# replace with string that is BEFORE match # replace with string that is BEFORE match
res += source[:span[0]] res += source[:span[0]]
+62 -26
View File
@@ -14,26 +14,36 @@ if six.PY3:
LINE_LEN_LIMIT = 400 # 200 # or any other value - the larger the smaller probability of errors :) LINE_LEN_LIMIT = 400 # 200 # or any other value - the larger the smaller probability of errors :)
class ForController: class LoopController:
def __init__(self): def __init__(self):
self.inside = [False] self.update = [""]
self.update = '' self.label_to_update_idx = {}
def enter_for(self, update): def enter(self, update=""):
self.inside.append(True) self.update.append(update)
self.update = update
def leave_for(self): def leave(self):
self.inside.pop() 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: class InlineStack:
@@ -86,9 +96,10 @@ class ContextStack:
def clean_stacks(): def clean_stacks():
global Context, inline_stack global Context, inline_stack, loop_controller
Context = ContextStack() Context = ContextStack()
inline_stack = InlineStack() inline_stack = InlineStack()
loop_controller = LoopController()
def to_key(literal_or_identifier): def to_key(literal_or_identifier):
@@ -108,6 +119,13 @@ def to_key(literal_or_identifier):
else: else:
return unicode(k) 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): def trans(ele, standard=False):
"""Translates esprima syntax tree to python by delegating to appropriate translating node""" """Translates esprima syntax tree to python by delegating to appropriate translating node"""
@@ -367,9 +385,14 @@ def BreakStatement(type, label):
def ContinueStatement(type, label): def ContinueStatement(type, label):
if 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: 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): def ReturnStatement(type, argument):
@@ -386,24 +409,28 @@ def DebuggerStatement(type):
def DoWhileStatement(type, body, test): 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) result = 'while 1:\n' + indent(inside)
return result return result
def ForStatement(type, init, test, update, body): 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 '' init = trans(init) if init else ''
if not init.endswith('\n'): if not init.endswith('\n'):
init += '\n' init += '\n'
test = trans(test) if test else '1' test = trans(test) if test else '1'
loop_controller.enter(update)
if not update: if not update:
result = '#for JS loop\n%swhile %s:\n%s%s\n' % ( result = '#for JS loop\n%swhile %s:\n%s%s\n' % (
init, test, indent(trans(body)), update) init, test, indent(trans(body)), update)
else: else:
result = '#for JS loop\n%swhile %s:\n' % (init, test) result = '#for JS loop\n%swhile %s:\n' % (init, test)
body = 'try:\n%sfinally:\n %s\n' % (indent(trans(body)), update) result += indent("%s# update\n%s\n" % (trans(body), update))
result += indent(body) loop_controller.leave()
return result return result
@@ -422,7 +449,9 @@ def ForInStatement(type, left, right, body, each):
name = left['name'] name = left['name']
else: else:
raise RuntimeError('Unusual ForIn loop') raise RuntimeError('Unusual ForIn loop')
loop_controller.enter()
res += indent('var.put(%s, PyJsTemp)\n' % repr(name) + trans(body)) res += indent('var.put(%s, PyJsTemp)\n' % repr(name) + trans(body))
loop_controller.leave()
return res return res
@@ -438,20 +467,23 @@ def IfStatement(type, test, consequent, alternate):
def LabeledStatement(type, label, body): def LabeledStatement(type, label, body):
# todo consider using smarter approach! # todo consider using smarter approach!
label_name = label['name']
loop_controller.register_label(label_name)
inside = trans(body) inside = trans(body)
loop_controller.deregister_label(label_name)
defs = '' defs = ''
if inside.startswith('while ') or inside.startswith( if is_iteration_statement(body) and (inside.startswith('while ') or inside.startswith(
'for ') or inside.startswith('#for'): 'for ') or inside.startswith('#for')):
# we have to add contine label as well... # we have to add contine label as well...
# 3 or 1 since #for loop type has more lines before real for. # 3 or 1 since #for loop type has more lines before real for.
sep = 1 if not inside.startswith('#for') else 3 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') temp = inside.split('\n')
injected = 'try:\n' + '\n'.join(temp[sep:]) injected = 'try:\n' + '\n'.join(temp[sep:])
injected += 'except %s:\n pass\n' % cont_label injected += 'except %s:\n pass\n' % cont_label
inside = '\n'.join(temp[:sep]) + '\n' + indent(injected) inside = '\n'.join(temp[:sep]) + '\n' + indent(injected)
defs += 'class %s(Exception): pass\n' % cont_label 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) inside = 'try:\n%sexcept %s:\n pass\n' % (indent(inside), break_label)
defs += 'class %s(Exception): pass\n' % break_label defs += 'class %s(Exception): pass\n' % break_label
return defs + inside return defs + inside
@@ -546,7 +578,11 @@ def VariableDeclaration(type, declarations, kind):
def WhileStatement(type, test, body): 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 return result
+8 -5
View File
@@ -55,16 +55,19 @@ def dbg(x):
"""does nothing, legacy dummy function""" """does nothing, legacy dummy function"""
return '' 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. """js has to be a javascript source code.
returns equivalent python code.""" returns equivalent python code."""
if use_compilation_plan and not '//' in js and not '/*' in js: if use_compilation_plan and not '//' in js and not '/*' in js:
return translate_js_with_compilation_plan(js, HEADER=HEADER) return translate_js_with_compilation_plan(js, HEADER=HEADER)
parser = pyjsparser.PyJsParser()
parsed = parser.parse(js) # js to esprima syntax tree parsed = parse_fn(js)
# 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()
translating_nodes.clean_stacks() translating_nodes.clean_stacks()
return HEADER + translating_nodes.trans( return HEADER + translating_nodes.trans(
parsed) # syntax tree to python code parsed) # syntax tree to python code
+135 -29
View File
@@ -26,17 +26,19 @@ def fix_js_args(func):
return func return func
code = append_arguments(six.get_function_code(func), ('this', 'arguments')) code = append_arguments(six.get_function_code(func), ('this', 'arguments'))
return types.FunctionType( result = types.FunctionType(
code, code,
six.get_function_globals(func), six.get_function_globals(func),
func.__name__, func.__name__,
closure=six.get_function_closure(func)) closure=six.get_function_closure(func))
return result
def append_arguments(code_obj, new_locals): def append_arguments(code_obj, new_locals):
co_varnames = code_obj.co_varnames # Old locals co_varnames = code_obj.co_varnames # Old locals
co_names = code_obj.co_names # Old globals 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_argcount = code_obj.co_argcount # Argument count
co_code = code_obj.co_code # The actual bytecode as a string 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( names_to_varnames = dict(
(co_names.index(name), varnames.index(name)) for name in new_locals) (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 # Now we modify the actual bytecode
modified = [] modified = []
drop_future_cache = False
for inst in instructions(code_obj): 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 op, arg = inst.opcode, inst.arg
# If the instruction is a LOAD_GLOBAL, we have to check to see if # 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, # it's one of the globals that we are replacing. Either way,
# update its arg using the appropriate dict. # update its arg using the appropriate dict.
drop_future_cache = False
if inst.opcode == LOAD_GLOBAL: 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 op = LOAD_FAST
arg = names_to_varnames[inst.arg] arg = names_to_varnames[idx]
elif inst.arg in name_translations: # Cache is not present after LOAD_FAST and needs to be removed.
arg = name_translations[inst.arg] 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: 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. # If it accesses co_varnames or co_names then update its argument.
elif inst.opcode in opcode.haslocal: elif inst.opcode in opcode.haslocal:
arg = varname_translations[inst.arg] arg = varname_translations[inst.arg]
elif inst.opcode in opcode.hasname: elif inst.opcode in opcode.hasname:
# for example STORE_GLOBAL
arg = name_translations[inst.arg] 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)) modified.extend(write_instruction(op, arg))
if six.PY2: if six.PY2:
code = ''.join(modified) 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_filename, code_obj.co_name,
code_obj.co_firstlineno, code_obj.co_lnotab, code_obj.co_firstlineno, code_obj.co_lnotab,
code_obj.co_freevars, code_obj.co_cellvars) code_obj.co_freevars, code_obj.co_cellvars)
# Done modifying codestring - make the code object # Done modifying codestring - make the code object
if hasattr(code_obj, "replace"): if hasattr(code_obj, "replace"):
# Python 3.8+ # Python 3.8+
return code_obj.replace( code_obj = code_obj.replace(
co_argcount=co_argcount + new_locals_len, co_argcount=co_argcount + new_locals_len,
co_nlocals=code_obj.co_nlocals + new_locals_len, co_nlocals=code_obj.co_nlocals + new_locals_len,
co_code=code, co_code=code,
co_names=names, co_names=names,
co_varnames=varnames) co_varnames=varnames)
return code_obj
else: else:
return types.CodeType(*args) return types.CodeType(*args)
def instructions(code_obj): def instructions(code_obj, show_cache=True):
# easy for python 3.4+ if sys.version_info >= (3, 11):
if sys.version_info >= (3, 4): # 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): for inst in dis.Bytecode(code_obj):
yield inst yield inst
else: else:
@@ -171,7 +201,7 @@ def write_instruction(op, arg):
chr((arg >> 8) & 255) chr((arg >> 8) & 255)
] ]
else: 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 :) else: # python 3.6+ uses wordcode instead of bytecode and they already supply all the EXTENDEND_ARG ops :)
if arg is None: if arg is None:
return [chr(op), 0] return [chr(op), 0]
@@ -191,6 +221,7 @@ def write_instruction(op, arg):
# raise ValueError("Invalid oparg: {0} is too large".format(oparg)) # raise ValueError("Invalid oparg: {0} is too large".format(oparg))
def check(code_obj): def check(code_obj):
old_bytecode = code_obj.co_code old_bytecode = code_obj.co_code
insts = list(instructions(code_obj)) insts = list(instructions(code_obj))
@@ -221,24 +252,99 @@ def check(code_obj):
'Your python version made changes to the bytecode') '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)) 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__': if __name__ == '__main__':
x = 'Wrong' import faulthandler
dick = 3000
def func(a): faulthandler.enable()
print(x, y, z, a)
print(dick)
d = (x, )
for e in (e for e in x):
print(e)
return x, y, z
func2 = types.FunctionType( def func(cmpfn):
append_arguments(six.get_function_code(func), ('x', 'y', 'z')), if not this.Class in ('Array', 'Arguments'):
six.get_function_globals(func), return this.to_object() # do nothing
func.__name__, arr = []
closure=six.get_function_closure(func)) for i in xrange(len(this)):
args = (2, 2, 3, 4), 3, 4 arr.append(this.get(six.text_type(i)))
assert func2(1, *args) == args
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)