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

View File

@@ -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.

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)
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)

View File

@@ -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...',

View File

@@ -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()

View File

@@ -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)

View File

@@ -0,0 +1 @@
from .seval import eval_js_vm

View File

@@ -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:

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)
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)

View File

@@ -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):

View File

@@ -1,6 +1,6 @@
from ..conversions import *
from ..func_utils import *
from six import unichr
def fromCharCode(this, args):
res = u''

View File

@@ -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...',

View File

@@ -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

View File

@@ -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]]

View File

@@ -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

View File

@@ -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)

View File

@@ -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'))

View File

@@ -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])

View File

@@ -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}'))

View File

@@ -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)

View File

@@ -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'))

View File

@@ -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('', ')'))

View File

@@ -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')

View File

@@ -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()

View File

@@ -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]]

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 :)
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

View File

@@ -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

View File

@@ -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)