118 changed files with 16560 additions and 3067 deletions
After Width: | Height: | Size: 13 KiB |
File diff suppressed because it is too large
@ -1,38 +1,48 @@ |
|||
from ..base import * |
|||
|
|||
@Js |
|||
def Array(): |
|||
if len(arguments)==0 or len(arguments)>1: |
|||
return arguments.to_list() |
|||
a = arguments[0] |
|||
if isinstance(a, PyJsNumber): |
|||
length = a.to_uint32() |
|||
if length!=a.value: |
|||
raise MakeError('RangeError', 'Invalid array length') |
|||
temp = Js([]) |
|||
temp.put('length', a) |
|||
return temp |
|||
return [a] |
|||
|
|||
Array.create = Array |
|||
Array.own['length']['value'] = Js(1) |
|||
|
|||
@Js |
|||
def isArray(arg): |
|||
return arg.Class=='Array' |
|||
|
|||
|
|||
Array.define_own_property('isArray', {'value': isArray, |
|||
'enumerable': False, |
|||
'writable': True, |
|||
'configurable': True}) |
|||
|
|||
Array.define_own_property('prototype', {'value': ArrayPrototype, |
|||
'enumerable': False, |
|||
'writable': False, |
|||
'configurable': False}) |
|||
|
|||
ArrayPrototype.define_own_property('constructor', {'value': Array, |
|||
'enumerable': False, |
|||
'writable': True, |
|||
'configurable': True}) |
|||
from ..base import * |
|||
|
|||
|
|||
@Js |
|||
def Array(): |
|||
if len(arguments) == 0 or len(arguments) > 1: |
|||
return arguments.to_list() |
|||
a = arguments[0] |
|||
if isinstance(a, PyJsNumber): |
|||
length = a.to_uint32() |
|||
if length != a.value: |
|||
raise MakeError('RangeError', 'Invalid array length') |
|||
temp = Js([]) |
|||
temp.put('length', a) |
|||
return temp |
|||
return [a] |
|||
|
|||
|
|||
Array.create = Array |
|||
Array.own['length']['value'] = Js(1) |
|||
|
|||
|
|||
@Js |
|||
def isArray(arg): |
|||
return arg.Class == 'Array' |
|||
|
|||
|
|||
Array.define_own_property('isArray', { |
|||
'value': isArray, |
|||
'enumerable': False, |
|||
'writable': True, |
|||
'configurable': True |
|||
}) |
|||
|
|||
Array.define_own_property( |
|||
'prototype', { |
|||
'value': ArrayPrototype, |
|||
'enumerable': False, |
|||
'writable': False, |
|||
'configurable': False |
|||
}) |
|||
|
|||
ArrayPrototype.define_own_property('constructor', { |
|||
'value': Array, |
|||
'enumerable': False, |
|||
'writable': True, |
|||
'configurable': True |
|||
}) |
|||
|
@ -1,11 +1,16 @@ |
|||
from ..base import * |
|||
|
|||
BooleanPrototype.define_own_property('constructor', {'value': Boolean, |
|||
'enumerable': False, |
|||
'writable': True, |
|||
'configurable': True}) |
|||
|
|||
Boolean.define_own_property('prototype', {'value': BooleanPrototype, |
|||
'enumerable': False, |
|||
'writable': False, |
|||
'configurable': False}) |
|||
from ..base import * |
|||
|
|||
BooleanPrototype.define_own_property('constructor', { |
|||
'value': Boolean, |
|||
'enumerable': False, |
|||
'writable': True, |
|||
'configurable': True |
|||
}) |
|||
|
|||
Boolean.define_own_property( |
|||
'prototype', { |
|||
'value': BooleanPrototype, |
|||
'enumerable': False, |
|||
'writable': False, |
|||
'configurable': False |
|||
}) |
|||
|
@ -1,368 +1,405 @@ |
|||
from ..base import * |
|||
from .time_helpers import * |
|||
|
|||
TZ_OFFSET = (time.altzone//3600) |
|||
ABS_OFFSET = abs(TZ_OFFSET) |
|||
TZ_NAME = time.tzname[1] |
|||
ISO_FORMAT = '%s-%s-%sT%s:%s:%s.%sZ' |
|||
@Js |
|||
def Date(year, month, date, hours, minutes, seconds, ms): |
|||
return now().to_string() |
|||
|
|||
Date.Class = 'Date' |
|||
|
|||
def now(): |
|||
return PyJsDate(int(time.time()*1000), prototype=DatePrototype) |
|||
|
|||
|
|||
@Js |
|||
def UTC(year, month, date, hours, minutes, seconds, ms): # todo complete this |
|||
args = arguments |
|||
y = args[0].to_number() |
|||
m = args[1].to_number() |
|||
l = len(args) |
|||
dt = args[2].to_number() if l>2 else Js(1) |
|||
h = args[3].to_number() if l>3 else Js(0) |
|||
mi = args[4].to_number() if l>4 else Js(0) |
|||
sec = args[5].to_number() if l>5 else Js(0) |
|||
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) |
|||
|
|||
@Js |
|||
def parse(string): |
|||
return PyJsDate(TimeClip(parse_date(string.to_string().value)), prototype=DatePrototype) |
|||
|
|||
|
|||
|
|||
Date.define_own_property('now', {'value': Js(now), |
|||
'enumerable': False, |
|||
'writable': True, |
|||
'configurable': True}) |
|||
|
|||
Date.define_own_property('parse', {'value': parse, |
|||
'enumerable': False, |
|||
'writable': True, |
|||
'configurable': True}) |
|||
|
|||
Date.define_own_property('UTC', {'value': UTC, |
|||
'enumerable': False, |
|||
'writable': True, |
|||
'configurable': True}) |
|||
|
|||
class PyJsDate(PyJs): |
|||
Class = 'Date' |
|||
extensible = True |
|||
def __init__(self, value, prototype=None): |
|||
self.value = value |
|||
self.own = {} |
|||
self.prototype = prototype |
|||
|
|||
# todo fix this problematic datetime part |
|||
def to_local_dt(self): |
|||
return datetime.datetime.utcfromtimestamp(UTCToLocal(self.value)//1000) |
|||
|
|||
def to_utc_dt(self): |
|||
return datetime.datetime.utcfromtimestamp(self.value//1000) |
|||
|
|||
def local_strftime(self, pattern): |
|||
if self.value is NaN: |
|||
return 'Invalid Date' |
|||
try: |
|||
dt = self.to_local_dt() |
|||
except: |
|||
raise MakeError('TypeError', 'unsupported date range. Will fix in future versions') |
|||
try: |
|||
return dt.strftime(pattern) |
|||
except: |
|||
raise MakeError('TypeError', 'Could not generate date string from this date (limitations of python.datetime)') |
|||
|
|||
def utc_strftime(self, pattern): |
|||
if self.value is NaN: |
|||
return 'Invalid Date' |
|||
try: |
|||
dt = self.to_utc_dt() |
|||
except: |
|||
raise MakeError('TypeError', 'unsupported date range. Will fix in future versions') |
|||
try: |
|||
return dt.strftime(pattern) |
|||
except: |
|||
raise MakeError('TypeError', 'Could not generate date string from this date (limitations of python.datetime)') |
|||
|
|||
|
|||
|
|||
|
|||
def parse_date(py_string): # todo support all date string formats |
|||
try: |
|||
dt = datetime.datetime.strptime(py_string, "%Y-%m-%dT%H:%M:%S.%fZ") |
|||
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) |
|||
|
|||
|
|||
|
|||
def date_constructor(*args): |
|||
if len(args)>=2: |
|||
return date_constructor2(*args) |
|||
elif len(args)==1: |
|||
return date_constructor1(args[0]) |
|||
else: |
|||
return date_constructor0() |
|||
|
|||
|
|||
def date_constructor0(): |
|||
return now() |
|||
|
|||
|
|||
def date_constructor1(value): |
|||
v = value.to_primitive() |
|||
if v._type()=='String': |
|||
v = parse_date(v.value) |
|||
else: |
|||
v = v.to_int() |
|||
return PyJsDate(TimeClip(v), prototype=DatePrototype) |
|||
|
|||
|
|||
def date_constructor2(*args): |
|||
y = args[0].to_number() |
|||
m = args[1].to_number() |
|||
l = len(args) |
|||
dt = args[2].to_number() if l>2 else Js(1) |
|||
h = args[3].to_number() if l>3 else Js(0) |
|||
mi = args[4].to_number() if l>4 else Js(0) |
|||
sec = args[5].to_number() if l>5 else Js(0) |
|||
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(LocalToUTC(MakeDate(MakeDay(y, m, dt), MakeTime(h, mi, sec, mili)))) |
|||
return PyJsDate(t, prototype=DatePrototype) |
|||
|
|||
Date.create = date_constructor |
|||
|
|||
DatePrototype = PyJsDate(float('nan'), prototype=ObjectPrototype) |
|||
|
|||
def check_date(obj): |
|||
if obj.Class!='Date': |
|||
raise MakeError('TypeError', 'this is not a Date object') |
|||
|
|||
|
|||
class DateProto: |
|||
def toString(): |
|||
check_date(this) |
|||
if this.value is NaN: |
|||
return 'Invalid Date' |
|||
offset = (UTCToLocal(this.value) - this.value)//msPerHour |
|||
return this.local_strftime('%a %b %d %Y %H:%M:%S GMT') + '%s00 (%s)' % (pad(offset, 2, True), GetTimeZoneName(this.value)) |
|||
|
|||
def toDateString(): |
|||
check_date(this) |
|||
return this.local_strftime('%d %B %Y') |
|||
|
|||
def toTimeString(): |
|||
check_date(this) |
|||
return this.local_strftime('%H:%M:%S') |
|||
|
|||
def toLocaleString(): |
|||
check_date(this) |
|||
return this.local_strftime('%d %B %Y %H:%M:%S') |
|||
|
|||
def toLocaleDateString(): |
|||
check_date(this) |
|||
return this.local_strftime('%d %B %Y') |
|||
|
|||
def toLocaleTimeString(): |
|||
check_date(this) |
|||
return this.local_strftime('%H:%M:%S') |
|||
|
|||
def valueOf(): |
|||
check_date(this) |
|||
return this.value |
|||
|
|||
def getTime(): |
|||
check_date(this) |
|||
return this.value |
|||
|
|||
def getFullYear(): |
|||
check_date(this) |
|||
if this.value is NaN: |
|||
return NaN |
|||
return YearFromTime(UTCToLocal(this.value)) |
|||
|
|||
def getUTCFullYear(): |
|||
check_date(this) |
|||
if this.value is NaN: |
|||
return NaN |
|||
return YearFromTime(this.value) |
|||
|
|||
def getMonth(): |
|||
check_date(this) |
|||
if this.value is NaN: |
|||
return NaN |
|||
return MonthFromTime(UTCToLocal(this.value)) |
|||
|
|||
def getDate(): |
|||
check_date(this) |
|||
if this.value is NaN: |
|||
return NaN |
|||
return DateFromTime(UTCToLocal(this.value)) |
|||
|
|||
def getUTCMonth(): |
|||
check_date(this) |
|||
if this.value is NaN: |
|||
return NaN |
|||
return MonthFromTime(this.value) |
|||
|
|||
def getUTCDate(): |
|||
check_date(this) |
|||
if this.value is NaN: |
|||
return NaN |
|||
return DateFromTime(this.value) |
|||
|
|||
def getDay(): |
|||
check_date(this) |
|||
if this.value is NaN: |
|||
return NaN |
|||
return WeekDay(UTCToLocal(this.value)) |
|||
|
|||
def getUTCDay(): |
|||
check_date(this) |
|||
if this.value is NaN: |
|||
return NaN |
|||
return WeekDay(this.value) |
|||
|
|||
def getHours(): |
|||
check_date(this) |
|||
if this.value is NaN: |
|||
return NaN |
|||
return HourFromTime(UTCToLocal(this.value)) |
|||
|
|||
def getUTCHours(): |
|||
check_date(this) |
|||
if this.value is NaN: |
|||
return NaN |
|||
return HourFromTime(this.value) |
|||
|
|||
def getMinutes(): |
|||
check_date(this) |
|||
if this.value is NaN: |
|||
return NaN |
|||
return MinFromTime(UTCToLocal(this.value)) |
|||
|
|||
def getUTCMinutes(): |
|||
check_date(this) |
|||
if this.value is NaN: |
|||
return NaN |
|||
return MinFromTime(this.value) |
|||
|
|||
def getSeconds(): |
|||
check_date(this) |
|||
if this.value is NaN: |
|||
return NaN |
|||
return SecFromTime(UTCToLocal(this.value)) |
|||
|
|||
def getUTCSeconds(): |
|||
check_date(this) |
|||
if this.value is NaN: |
|||
return NaN |
|||
return SecFromTime(this.value) |
|||
|
|||
def getMilliseconds(): |
|||
check_date(this) |
|||
if this.value is NaN: |
|||
return NaN |
|||
return msFromTime(UTCToLocal(this.value)) |
|||
|
|||
def getUTCMilliseconds(): |
|||
check_date(this) |
|||
if this.value is NaN: |
|||
return NaN |
|||
return msFromTime(this.value) |
|||
|
|||
def getTimezoneOffset(): |
|||
check_date(this) |
|||
if this.value is NaN: |
|||
return NaN |
|||
return (this.value - UTCToLocal(this.value))//60000 |
|||
|
|||
|
|||
def setTime(time): |
|||
check_date(this) |
|||
this.value = TimeClip(time.to_number().to_int()) |
|||
return this.value |
|||
|
|||
def setMilliseconds(ms): |
|||
check_date(this) |
|||
t = UTCToLocal(this.value) |
|||
tim = MakeTime(HourFromTime(t), MinFromTime(t), SecFromTime(t), ms.to_int()) |
|||
u = TimeClip(LocalToUTC(MakeDate(Day(t), tim))) |
|||
this.value = u |
|||
return u |
|||
|
|||
def setUTCMilliseconds(ms): |
|||
check_date(this) |
|||
t = this.value |
|||
tim = MakeTime(HourFromTime(t), MinFromTime(t), SecFromTime(t), ms.to_int()) |
|||
u = TimeClip(MakeDate(Day(t), tim)) |
|||
this.value = u |
|||
return u |
|||
|
|||
# todo Complete all setters! |
|||
|
|||
def toUTCString(): |
|||
check_date(this) |
|||
return this.utc_strftime('%d %B %Y %H:%M:%S') |
|||
|
|||
def toISOString(): |
|||
check_date(this) |
|||
t = this.value |
|||
year = YearFromTime(t) |
|||
month, day, hour, minute, second, milli = pad(MonthFromTime(t)+1), pad(DateFromTime(t)), pad(HourFromTime(t)), pad(MinFromTime(t)), pad(SecFromTime(t)), pad(msFromTime(t)) |
|||
return ISO_FORMAT % (unicode(year) if 0<=year<=9999 else pad(year, 6, True), month, day, hour, minute, second, milli) |
|||
|
|||
def toJSON(key): |
|||
o = this.to_object() |
|||
tv = o.to_primitive('Number') |
|||
if tv.Class=='Number' and not tv.is_finite(): |
|||
return this.null |
|||
toISO = o.get('toISOString') |
|||
if not toISO.is_callable(): |
|||
raise this.MakeError('TypeError', 'toISOString is not callable') |
|||
return toISO.call(o, ()) |
|||
|
|||
|
|||
def pad(num, n=2, sign=False): |
|||
'''returns n digit string representation of the num''' |
|||
s = unicode(abs(num)) |
|||
if len(s)<n: |
|||
s = '0'*(n-len(s)) + s |
|||
if not sign: |
|||
return s |
|||
if num>=0: |
|||
return '+'+s |
|||
else: |
|||
return '-'+s |
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
fill_prototype(DatePrototype, DateProto, default_attrs) |
|||
|
|||
|
|||
|
|||
Date.define_own_property('prototype', {'value': DatePrototype, |
|||
'enumerable': False, |
|||
'writable': False, |
|||
'configurable': False}) |
|||
|
|||
DatePrototype.define_own_property('constructor', {'value': Date, |
|||
'enumerable': False, |
|||
'writable': True, |
|||
'configurable': True}) |
|||
from ..base import * |
|||
from .time_helpers import * |
|||
|
|||
TZ_OFFSET = (time.altzone // 3600) |
|||
ABS_OFFSET = abs(TZ_OFFSET) |
|||
TZ_NAME = time.tzname[1] |
|||
ISO_FORMAT = '%s-%s-%sT%s:%s:%s.%sZ' |
|||
|
|||
|
|||
@Js |
|||
def Date(year, month, date, hours, minutes, seconds, ms): |
|||
return now().to_string() |
|||
|
|||
|
|||
Date.Class = 'Date' |
|||
|
|||
|
|||
def now(): |
|||
return PyJsDate(int(time.time() * 1000), prototype=DatePrototype) |
|||
|
|||
|
|||
@Js |
|||
def UTC(year, month, date, hours, minutes, seconds, ms): # todo complete this |
|||
args = arguments |
|||
y = args[0].to_number() |
|||
m = args[1].to_number() |
|||
l = len(args) |
|||
dt = args[2].to_number() if l > 2 else Js(1) |
|||
h = args[3].to_number() if l > 3 else Js(0) |
|||
mi = args[4].to_number() if l > 4 else Js(0) |
|||
sec = args[5].to_number() if l > 5 else Js(0) |
|||
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) |
|||
|
|||
|
|||
@Js |
|||
def parse(string): |
|||
return PyJsDate( |
|||
TimeClip(parse_date(string.to_string().value)), |
|||
prototype=DatePrototype) |
|||
|
|||
|
|||
Date.define_own_property('now', { |
|||
'value': Js(now), |
|||
'enumerable': False, |
|||
'writable': True, |
|||
'configurable': True |
|||
}) |
|||
|
|||
Date.define_own_property('parse', { |
|||
'value': parse, |
|||
'enumerable': False, |
|||
'writable': True, |
|||
'configurable': True |
|||
}) |
|||
|
|||
Date.define_own_property('UTC', { |
|||
'value': UTC, |
|||
'enumerable': False, |
|||
'writable': True, |
|||
'configurable': True |
|||
}) |
|||
|
|||
|
|||
class PyJsDate(PyJs): |
|||
Class = 'Date' |
|||
extensible = True |
|||
|
|||
def __init__(self, value, prototype=None): |
|||
self.value = value |
|||
self.own = {} |
|||
self.prototype = prototype |
|||
|
|||
# todo fix this problematic datetime part |
|||
def to_local_dt(self): |
|||
return datetime.datetime.utcfromtimestamp( |
|||
UTCToLocal(self.value) // 1000) |
|||
|
|||
def to_utc_dt(self): |
|||
return datetime.datetime.utcfromtimestamp(self.value // 1000) |
|||
|
|||
def local_strftime(self, pattern): |
|||
if self.value is NaN: |
|||
return 'Invalid Date' |
|||
try: |
|||
dt = self.to_local_dt() |
|||
except: |
|||
raise MakeError( |
|||
'TypeError', |
|||
'unsupported date range. Will fix in future versions') |
|||
try: |
|||
return dt.strftime(pattern) |
|||
except: |
|||
raise MakeError( |
|||
'TypeError', |
|||
'Could not generate date string from this date (limitations of python.datetime)' |
|||
) |
|||
|
|||
def utc_strftime(self, pattern): |
|||
if self.value is NaN: |
|||
return 'Invalid Date' |
|||
try: |
|||
dt = self.to_utc_dt() |
|||
except: |
|||
raise MakeError( |
|||
'TypeError', |
|||
'unsupported date range. Will fix in future versions') |
|||
try: |
|||
return dt.strftime(pattern) |
|||
except: |
|||
raise MakeError( |
|||
'TypeError', |
|||
'Could not generate date string from this date (limitations of python.datetime)' |
|||
) |
|||
|
|||
|
|||
def parse_date(py_string): # todo support all date string formats |
|||
try: |
|||
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) |
|||
|
|||
|
|||
def date_constructor(*args): |
|||
if len(args) >= 2: |
|||
return date_constructor2(*args) |
|||
elif len(args) == 1: |
|||
return date_constructor1(args[0]) |
|||
else: |
|||
return date_constructor0() |
|||
|
|||
|
|||
def date_constructor0(): |
|||
return now() |
|||
|
|||
|
|||
def date_constructor1(value): |
|||
v = value.to_primitive() |
|||
if v._type() == 'String': |
|||
v = parse_date(v.value) |
|||
else: |
|||
v = v.to_int() |
|||
return PyJsDate(TimeClip(v), prototype=DatePrototype) |
|||
|
|||
|
|||
def date_constructor2(*args): |
|||
y = args[0].to_number() |
|||
m = args[1].to_number() |
|||
l = len(args) |
|||
dt = args[2].to_number() if l > 2 else Js(1) |
|||
h = args[3].to_number() if l > 3 else Js(0) |
|||
mi = args[4].to_number() if l > 4 else Js(0) |
|||
sec = args[5].to_number() if l > 5 else Js(0) |
|||
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( |
|||
LocalToUTC(MakeDate(MakeDay(y, m, dt), MakeTime(h, mi, sec, mili)))) |
|||
return PyJsDate(t, prototype=DatePrototype) |
|||
|
|||
|
|||
Date.create = date_constructor |
|||
|
|||
DatePrototype = PyJsDate(float('nan'), prototype=ObjectPrototype) |
|||
|
|||
|
|||
def check_date(obj): |
|||
if obj.Class != 'Date': |
|||
raise MakeError('TypeError', 'this is not a Date object') |
|||
|
|||
|
|||
class DateProto: |
|||
def toString(): |
|||
check_date(this) |
|||
if this.value is NaN: |
|||
return 'Invalid Date' |
|||
offset = (UTCToLocal(this.value) - this.value) // msPerHour |
|||
return this.local_strftime( |
|||
'%a %b %d %Y %H:%M:%S GMT') + '%s00 (%s)' % (pad( |
|||
offset, 2, True), GetTimeZoneName(this.value)) |
|||
|
|||
def toDateString(): |
|||
check_date(this) |
|||
return this.local_strftime('%d %B %Y') |
|||
|
|||
def toTimeString(): |
|||
check_date(this) |
|||
return this.local_strftime('%H:%M:%S') |
|||
|
|||
def toLocaleString(): |
|||
check_date(this) |
|||
return this.local_strftime('%d %B %Y %H:%M:%S') |
|||
|
|||
def toLocaleDateString(): |
|||
check_date(this) |
|||
return this.local_strftime('%d %B %Y') |
|||
|
|||
def toLocaleTimeString(): |
|||
check_date(this) |
|||
return this.local_strftime('%H:%M:%S') |
|||
|
|||
def valueOf(): |
|||
check_date(this) |
|||
return this.value |
|||
|
|||
def getTime(): |
|||
check_date(this) |
|||
return this.value |
|||
|
|||
def getFullYear(): |
|||
check_date(this) |
|||
if this.value is NaN: |
|||
return NaN |
|||
return YearFromTime(UTCToLocal(this.value)) |
|||
|
|||
def getUTCFullYear(): |
|||
check_date(this) |
|||
if this.value is NaN: |
|||
return NaN |
|||
return YearFromTime(this.value) |
|||
|
|||
def getMonth(): |
|||
check_date(this) |
|||
if this.value is NaN: |
|||
return NaN |
|||
return MonthFromTime(UTCToLocal(this.value)) |
|||
|
|||
def getDate(): |
|||
check_date(this) |
|||
if this.value is NaN: |
|||
return NaN |
|||
return DateFromTime(UTCToLocal(this.value)) |
|||
|
|||
def getUTCMonth(): |
|||
check_date(this) |
|||
if this.value is NaN: |
|||
return NaN |
|||
return MonthFromTime(this.value) |
|||
|
|||
def getUTCDate(): |
|||
check_date(this) |
|||
if this.value is NaN: |
|||
return NaN |
|||
return DateFromTime(this.value) |
|||
|
|||
def getDay(): |
|||
check_date(this) |
|||
if this.value is NaN: |
|||
return NaN |
|||
return WeekDay(UTCToLocal(this.value)) |
|||
|
|||
def getUTCDay(): |
|||
check_date(this) |
|||
if this.value is NaN: |
|||
return NaN |
|||
return WeekDay(this.value) |
|||
|
|||
def getHours(): |
|||
check_date(this) |
|||
if this.value is NaN: |
|||
return NaN |
|||
return HourFromTime(UTCToLocal(this.value)) |
|||
|
|||
def getUTCHours(): |
|||
check_date(this) |
|||
if this.value is NaN: |
|||
return NaN |
|||
return HourFromTime(this.value) |
|||
|
|||
def getMinutes(): |
|||
check_date(this) |
|||
if this.value is NaN: |
|||
return NaN |
|||
return MinFromTime(UTCToLocal(this.value)) |
|||
|
|||
def getUTCMinutes(): |
|||
check_date(this) |
|||
if this.value is NaN: |
|||
return NaN |
|||
return MinFromTime(this.value) |
|||
|
|||
def getSeconds(): |
|||
check_date(this) |
|||
if this.value is NaN: |
|||
return NaN |
|||
return SecFromTime(UTCToLocal(this.value)) |
|||
|
|||
def getUTCSeconds(): |
|||
check_date(this) |
|||
if this.value is NaN: |
|||
return NaN |
|||
return SecFromTime(this.value) |
|||
|
|||
def getMilliseconds(): |
|||
check_date(this) |
|||
if this.value is NaN: |
|||
return NaN |
|||
return msFromTime(UTCToLocal(this.value)) |
|||
|
|||
def getUTCMilliseconds(): |
|||
check_date(this) |
|||
if this.value is NaN: |
|||
return NaN |
|||
return msFromTime(this.value) |
|||
|
|||
def getTimezoneOffset(): |
|||
check_date(this) |
|||
if this.value is NaN: |
|||
return NaN |
|||
return (this.value - UTCToLocal(this.value)) // 60000 |
|||
|
|||
def setTime(time): |
|||
check_date(this) |
|||
this.value = TimeClip(time.to_number().to_int()) |
|||
return this.value |
|||
|
|||
def setMilliseconds(ms): |
|||
check_date(this) |
|||
t = UTCToLocal(this.value) |
|||
tim = MakeTime( |
|||
HourFromTime(t), MinFromTime(t), SecFromTime(t), ms.to_int()) |
|||
u = TimeClip(LocalToUTC(MakeDate(Day(t), tim))) |
|||
this.value = u |
|||
return u |
|||
|
|||
def setUTCMilliseconds(ms): |
|||
check_date(this) |
|||
t = this.value |
|||
tim = MakeTime( |
|||
HourFromTime(t), MinFromTime(t), SecFromTime(t), ms.to_int()) |
|||
u = TimeClip(MakeDate(Day(t), tim)) |
|||
this.value = u |
|||
return u |
|||
|
|||
# todo Complete all setters! |
|||
|
|||
def toUTCString(): |
|||
check_date(this) |
|||
return this.utc_strftime('%d %B %Y %H:%M:%S') |
|||
|
|||
def toISOString(): |
|||
check_date(this) |
|||
t = this.value |
|||
year = YearFromTime(t) |
|||
month, day, hour, minute, second, milli = pad( |
|||
MonthFromTime(t) + 1), pad(DateFromTime(t)), pad( |
|||
HourFromTime(t)), pad(MinFromTime(t)), pad( |
|||
SecFromTime(t)), pad(msFromTime(t)) |
|||
return ISO_FORMAT % (unicode(year) if 0 <= year <= 9999 else pad( |
|||
year, 6, True), month, day, hour, minute, second, milli) |
|||
|
|||
def toJSON(key): |
|||
o = this.to_object() |
|||
tv = o.to_primitive('Number') |
|||
if tv.Class == 'Number' and not tv.is_finite(): |
|||
return this.null |
|||
toISO = o.get('toISOString') |
|||
if not toISO.is_callable(): |
|||
raise this.MakeError('TypeError', 'toISOString is not callable') |
|||
return toISO.call(o, ()) |
|||
|
|||
|
|||
def pad(num, n=2, sign=False): |
|||
'''returns n digit string representation of the num''' |
|||
s = unicode(abs(num)) |
|||
if len(s) < n: |
|||
s = '0' * (n - len(s)) + s |
|||
if not sign: |
|||
return s |
|||
if num >= 0: |
|||
return '+' + s |
|||
else: |
|||
return '-' + s |
|||
|
|||
|
|||
fill_prototype(DatePrototype, DateProto, default_attrs) |
|||
|
|||
Date.define_own_property( |
|||
'prototype', { |
|||
'value': DatePrototype, |
|||
'enumerable': False, |
|||
'writable': False, |
|||
'configurable': False |
|||
}) |
|||
|
|||
DatePrototype.define_own_property('constructor', { |
|||
'value': Date, |
|||
'enumerable': False, |
|||
'writable': True, |
|||
'configurable': True |
|||
}) |
|||
|
@ -1,49 +1,52 @@ |
|||
from ..base import * |
|||
try: |
|||
from ..translators.translator import translate_js |
|||
except: |
|||
pass |
|||
|
|||
|
|||
@Js |
|||
def Function(): |
|||
# convert arguments to python list of strings |
|||
a = [e.to_string().value for e in arguments.to_list()] |
|||
body = ';' |
|||
args = () |
|||
if len(a): |
|||
body = '%s;' % a[-1] |
|||
args = a[:-1] |
|||
# translate this function to js inline function |
|||
js_func = '(function (%s) {%s})' % (','.join(args), body) |
|||
# now translate js inline to python function |
|||
py_func = translate_js(js_func, '') |
|||
# add set func scope to global scope |
|||
# a but messy solution but works :) |
|||
globals()['var'] = PyJs.GlobalObject |
|||
# define py function and return it |
|||
temp = executor(py_func, globals()) |
|||
temp.source = '{%s}'%body |
|||
temp.func_name = 'anonymous' |
|||
return temp |
|||
|
|||
def executor(f, glob): |
|||
exec(f, globals()) |
|||
return globals()['PyJs_anonymous_0_'] |
|||
|
|||
|
|||
#new statement simply calls Function |
|||
Function.create = Function |
|||
|
|||
#set constructor property inside FunctionPrototype |
|||
|
|||
fill_in_props(FunctionPrototype, {'constructor':Function}, default_attrs) |
|||
|
|||
#attach prototype to Function constructor |
|||
Function.define_own_property('prototype', {'value': FunctionPrototype, |
|||
'enumerable': False, |
|||
'writable': False, |
|||
'configurable': False}) |
|||
#Fix Function length (its 0 and should be 1) |
|||
Function.own['length']['value'] = Js(1) |
|||
|
|||
from ..base import * |
|||
try: |
|||
from ..translators.translator import translate_js |
|||
except: |
|||
pass |
|||
|
|||
|
|||
@Js |
|||
def Function(): |
|||
# convert arguments to python list of strings |
|||
a = [e.to_string().value for e in arguments.to_list()] |
|||
body = ';' |
|||
args = () |
|||
if len(a): |
|||
body = '%s;' % a[-1] |
|||
args = a[:-1] |
|||
# translate this function to js inline function |
|||
js_func = '(function (%s) {%s})' % (','.join(args), body) |
|||
# now translate js inline to python function |
|||
py_func = translate_js(js_func, '') |
|||
# add set func scope to global scope |
|||
# a but messy solution but works :) |
|||
globals()['var'] = PyJs.GlobalObject |
|||
# define py function and return it |
|||
temp = executor(py_func, globals()) |
|||
temp.source = '{%s}' % body |
|||
temp.func_name = 'anonymous' |
|||
return temp |
|||
|
|||
|
|||
def executor(f, glob): |
|||
exec (f, globals()) |
|||
return globals()['PyJs_anonymous_0_'] |
|||
|
|||
|
|||
#new statement simply calls Function |
|||
Function.create = Function |
|||
|
|||
#set constructor property inside FunctionPrototype |
|||
|
|||
fill_in_props(FunctionPrototype, {'constructor': Function}, default_attrs) |
|||
|
|||
#attach prototype to Function constructor |
|||
Function.define_own_property( |
|||
'prototype', { |
|||
'value': FunctionPrototype, |
|||
'enumerable': False, |
|||
'writable': False, |
|||
'configurable': False |
|||
}) |
|||
#Fix Function length (its 0 and should be 1) |
|||
Function.own['length']['value'] = Js(1) |
|||
|
@ -1,151 +1,157 @@ |
|||
from ..base import * |
|||
import math |
|||
import random |
|||
|
|||
Math = PyJsObject(prototype=ObjectPrototype) |
|||
Math.Class = 'Math' |
|||
|
|||
CONSTANTS = {'E': 2.7182818284590452354, |
|||
'LN10': 2.302585092994046, |
|||
'LN2': 0.6931471805599453, |
|||
'LOG2E': 1.4426950408889634, |
|||
'LOG10E': 0.4342944819032518, |
|||
'PI': 3.1415926535897932, |
|||
'SQRT1_2': 0.7071067811865476, |
|||
'SQRT2': 1.4142135623730951} |
|||
|
|||
for constant, value in CONSTANTS.items(): |
|||
Math.define_own_property(constant, {'value': Js(value), |
|||
'writable': False, |
|||
'enumerable': False, |
|||
'configurable': False}) |
|||
|
|||
class MathFunctions: |
|||
def abs(x): |
|||
a = x.to_number().value |
|||
if a!=a: # it must be a nan |
|||
return NaN |
|||
return abs(a) |
|||
|
|||
def acos(x): |
|||
a = x.to_number().value |
|||
if a!=a: # it must be a nan |
|||
return NaN |
|||
try: |
|||
return math.acos(a) |
|||
except: |
|||
return NaN |
|||
|
|||
def asin(x): |
|||
a = x.to_number().value |
|||
if a!=a: # it must be a nan |
|||
return NaN |
|||
try: |
|||
return math.asin(a) |
|||
except: |
|||
return NaN |
|||
|
|||
def atan(x): |
|||
a = x.to_number().value |
|||
if a!=a: # it must be a nan |
|||
return NaN |
|||
return math.atan(a) |
|||
|
|||
def atan2(y, x): |
|||
a = x.to_number().value |
|||
b = y.to_number().value |
|||
if a!=a or b!=b: # it must be a nan |
|||
return NaN |
|||
return math.atan2(b, a) |
|||
|
|||
def ceil(x): |
|||
a = x.to_number().value |
|||
if a!=a: # it must be a nan |
|||
return NaN |
|||
return math.ceil(a) |
|||
|
|||
def floor(x): |
|||
a = x.to_number().value |
|||
if a!=a: # it must be a nan |
|||
return NaN |
|||
return math.floor(a) |
|||
|
|||
def round(x): |
|||
a = x.to_number().value |
|||
if a!=a: # it must be a nan |
|||
return NaN |
|||
return round(a) |
|||
|
|||
def sin(x): |
|||
a = x.to_number().value |
|||
if a!=a: # it must be a nan |
|||
return NaN |
|||
return math.sin(a) |
|||
|
|||
def cos(x): |
|||
a = x.to_number().value |
|||
if a!=a: # it must be a nan |
|||
return NaN |
|||
return math.cos(a) |
|||
|
|||
def tan(x): |
|||
a = x.to_number().value |
|||
if a!=a: # it must be a nan |
|||
return NaN |
|||
return math.tan(a) |
|||
|
|||
def log(x): |
|||
a = x.to_number().value |
|||
if a!=a: # it must be a nan |
|||
return NaN |
|||
try: |
|||
return math.log(a) |
|||
except: |
|||
return NaN |
|||
|
|||
def exp(x): |
|||
a = x.to_number().value |
|||
if a!=a: # it must be a nan |
|||
return NaN |
|||
return math.exp(a) |
|||
|
|||
def pow(x, y): |
|||
a = x.to_number().value |
|||
b = y.to_number().value |
|||
if a!=a or b!=b: # it must be a nan |
|||
return NaN |
|||
try: |
|||
return a**b |
|||
except: |
|||
return NaN |
|||
|
|||
def sqrt(x): |
|||
a = x.to_number().value |
|||
if a!=a: # it must be a nan |
|||
return NaN |
|||
try: |
|||
return a**0.5 |
|||
except: |
|||
return NaN |
|||
|
|||
def min(): |
|||
if not len(arguments): |
|||
return -Infinity |
|||
lis = tuple(e.to_number().value for e in arguments.to_list()) |
|||
if any(e!=e for e in lis): # we dont want NaNs |
|||
return NaN |
|||
return min(*lis) |
|||
|
|||
def max(): |
|||
if not len(arguments): |
|||
return -Infinity |
|||
lis = tuple(e.to_number().value for e in arguments.to_list()) |
|||
if any(e!=e for e in lis): # we dont want NaNs |
|||
return NaN |
|||
return max(*lis) |
|||
|
|||
def random(): |
|||
return random.random() |
|||
|
|||
|
|||
fill_prototype(Math, MathFunctions, default_attrs) |
|||
from ..base import * |
|||
import math |
|||
import random |
|||
|
|||
Math = PyJsObject(prototype=ObjectPrototype) |
|||
Math.Class = 'Math' |
|||
|
|||
CONSTANTS = { |
|||
'E': 2.7182818284590452354, |
|||
'LN10': 2.302585092994046, |
|||
'LN2': 0.6931471805599453, |
|||
'LOG2E': 1.4426950408889634, |
|||
'LOG10E': 0.4342944819032518, |
|||
'PI': 3.1415926535897932, |
|||
'SQRT1_2': 0.7071067811865476, |
|||
'SQRT2': 1.4142135623730951 |
|||
} |
|||
|
|||
for constant, value in CONSTANTS.items(): |
|||
Math.define_own_property( |
|||
constant, { |
|||
'value': Js(value), |
|||
'writable': False, |
|||
'enumerable': False, |
|||
'configurable': False |
|||
}) |
|||
|
|||
|
|||
class MathFunctions: |
|||
def abs(x): |
|||
a = x.to_number().value |
|||
if a != a: # it must be a nan |
|||
return NaN |
|||
return abs(a) |
|||
|
|||
def acos(x): |
|||
a = x.to_number().value |
|||
if a != a: # it must be a nan |
|||
return NaN |
|||
try: |
|||
return math.acos(a) |
|||
except: |
|||
return NaN |
|||
|
|||
def asin(x): |
|||
a = x.to_number().value |
|||
if a != a: # it must be a nan |
|||
return NaN |
|||
try: |
|||
return math.asin(a) |
|||
except: |
|||
return NaN |
|||
|
|||
def atan(x): |
|||
a = x.to_number().value |
|||
if a != a: # it must be a nan |
|||
return NaN |
|||
return math.atan(a) |
|||
|
|||
def atan2(y, x): |
|||
a = x.to_number().value |
|||
b = y.to_number().value |
|||
if a != a or b != b: # it must be a nan |
|||
return NaN |
|||
return math.atan2(b, a) |
|||
|
|||
def ceil(x): |
|||
a = x.to_number().value |
|||
if a != a: # it must be a nan |
|||
return NaN |
|||
return math.ceil(a) |
|||
|
|||
def floor(x): |
|||
a = x.to_number().value |
|||
if a != a: # it must be a nan |
|||
return NaN |
|||
return math.floor(a) |
|||
|
|||
def round(x): |
|||
a = x.to_number().value |
|||
if a != a: # it must be a nan |
|||
return NaN |
|||
return round(a) |
|||
|
|||
def sin(x): |
|||
a = x.to_number().value |
|||
if a != a: # it must be a nan |
|||
return NaN |
|||
return math.sin(a) |
|||
|
|||
def cos(x): |
|||
a = x.to_number().value |
|||
if a != a: # it must be a nan |
|||
return NaN |
|||
return math.cos(a) |
|||
|
|||
def tan(x): |
|||
a = x.to_number().value |
|||
if a != a: # it must be a nan |
|||
return NaN |
|||
return math.tan(a) |
|||
|
|||
def log(x): |
|||
a = x.to_number().value |
|||
if a != a: # it must be a nan |
|||
return NaN |
|||
try: |
|||
return math.log(a) |
|||
except: |
|||
return NaN |
|||
|
|||
def exp(x): |
|||
a = x.to_number().value |
|||
if a != a: # it must be a nan |
|||
return NaN |
|||
return math.exp(a) |
|||
|
|||
def pow(x, y): |
|||
a = x.to_number().value |
|||
b = y.to_number().value |
|||
if a != a or b != b: # it must be a nan |
|||
return NaN |
|||
try: |
|||
return a**b |
|||
except: |
|||
return NaN |
|||
|
|||
def sqrt(x): |
|||
a = x.to_number().value |
|||
if a != a: # it must be a nan |
|||
return NaN |
|||
try: |
|||
return a**0.5 |
|||
except: |
|||
return NaN |
|||
|
|||
def min(): |
|||
if not len(arguments): |
|||
return Infinity |
|||
lis = tuple(e.to_number().value for e in arguments.to_list()) |
|||
if any(e != e for e in lis): # we dont want NaNs |
|||
return NaN |
|||
return min(*lis) |
|||
|
|||
def max(): |
|||
if not len(arguments): |
|||
return -Infinity |
|||
lis = tuple(e.to_number().value for e in arguments.to_list()) |
|||
if any(e != e for e in lis): # we dont want NaNs |
|||
return NaN |
|||
return max(*lis) |
|||
|
|||
def random(): |
|||
return random.random() |
|||
|
|||
|
|||
fill_prototype(Math, MathFunctions, default_attrs) |
|||
|
@ -1,18 +1,23 @@ |
|||
from ..base import * |
|||
|
|||
|
|||
CONSTS = {'prototype': NumberPrototype, |
|||
'MAX_VALUE':1.7976931348623157e308, |
|||
'MIN_VALUE': 5.0e-324, |
|||
'NaN': NaN, |
|||
'NEGATIVE_INFINITY': float('-inf'), |
|||
'POSITIVE_INFINITY': float('inf')} |
|||
|
|||
fill_in_props(Number, CONSTS, {'enumerable': False, |
|||
'writable': False, |
|||
'configurable': False}) |
|||
|
|||
NumberPrototype.define_own_property('constructor', {'value': Number, |
|||
'enumerable': False, |
|||
'writable': True, |
|||
'configurable': True}) |
|||
from ..base import * |
|||
|
|||
CONSTS = { |
|||
'prototype': NumberPrototype, |
|||
'MAX_VALUE': 1.7976931348623157e308, |
|||
'MIN_VALUE': 5.0e-324, |
|||
'NaN': NaN, |
|||
'NEGATIVE_INFINITY': float('-inf'), |
|||
'POSITIVE_INFINITY': float('inf') |
|||
} |
|||
|
|||
fill_in_props(Number, CONSTS, { |
|||
'enumerable': False, |
|||
'writable': False, |
|||
'configurable': False |
|||
}) |
|||
|
|||
NumberPrototype.define_own_property('constructor', { |
|||
'value': Number, |
|||
'enumerable': False, |
|||
'writable': True, |
|||
'configurable': True |
|||
}) |
|||
|
@ -1,11 +1,16 @@ |
|||
from ..base import * |
|||
|
|||
RegExpPrototype.define_own_property('constructor', {'value': RegExp, |
|||
'enumerable': False, |
|||
'writable': True, |
|||
'configurable': True}) |
|||
|
|||
RegExp.define_own_property('prototype', {'value': RegExpPrototype, |
|||
'enumerable': False, |
|||
'writable': False, |
|||
'configurable': False}) |
|||
from ..base import * |
|||
|
|||
RegExpPrototype.define_own_property('constructor', { |
|||
'value': RegExp, |
|||
'enumerable': False, |
|||
'writable': True, |
|||
'configurable': True |
|||
}) |
|||
|
|||
RegExp.define_own_property( |
|||
'prototype', { |
|||
'value': RegExpPrototype, |
|||
'enumerable': False, |
|||
'writable': False, |
|||
'configurable': False |
|||
}) |
|||
|
@ -1,30 +1,40 @@ |
|||
from ..base import * |
|||
# python 3 support |
|||
import six |
|||
if six.PY3: |
|||
unichr = chr |
|||
|
|||
@Js |
|||
def fromCharCode(): |
|||
args = arguments.to_list() |
|||
res = u'' |
|||
for e in args: |
|||
res +=unichr(e.to_uint16()) |
|||
return this.Js(res) |
|||
|
|||
fromCharCode.own['length']['value'] = Js(1) |
|||
|
|||
String.define_own_property('fromCharCode', {'value': fromCharCode, |
|||
'enumerable': False, |
|||
'writable': True, |
|||
'configurable': True}) |
|||
|
|||
String.define_own_property('prototype', {'value': StringPrototype, |
|||
'enumerable': False, |
|||
'writable': False, |
|||
'configurable': False}) |
|||
|
|||
StringPrototype.define_own_property('constructor', {'value': String, |
|||
'enumerable': False, |
|||
'writable': True, |
|||
'configurable': True}) |
|||
from ..base import * |
|||
# python 3 support |
|||
import six |
|||
if six.PY3: |
|||
unichr = chr |
|||
|
|||
|
|||
@Js |
|||
def fromCharCode(): |
|||
args = arguments.to_list() |
|||
res = u'' |
|||
for e in args: |
|||
res += unichr(e.to_uint16()) |
|||
return this.Js(res) |
|||
|
|||
|
|||
fromCharCode.own['length']['value'] = Js(1) |
|||
|
|||
String.define_own_property( |
|||
'fromCharCode', { |
|||
'value': fromCharCode, |
|||
'enumerable': False, |
|||
'writable': True, |
|||
'configurable': True |
|||
}) |
|||
|
|||
String.define_own_property( |
|||
'prototype', { |
|||
'value': StringPrototype, |
|||
'enumerable': False, |
|||
'writable': False, |
|||
'configurable': False |
|||
}) |
|||
|
|||
StringPrototype.define_own_property('constructor', { |
|||
'value': String, |
|||
'enumerable': False, |
|||
'writable': True, |
|||
'configurable': True |
|||
}) |
|||
|
@ -1,11 +1,14 @@ |
|||
from ..base import * |
|||
|
|||
|
|||
@Js |
|||
def console(): |
|||
pass |
|||
|
|||
|
|||
@Js |
|||
def log(): |
|||
print(arguments[0]) |
|||
|
|||
console.put('log', log) |
|||
|
|||
console.put('log', log) |
|||
|
@ -1,47 +0,0 @@ |
|||
from js2py.base import * |
|||
|
|||
def _get_conts(idl): |
|||
def is_valid(c): |
|||
try: |
|||
exec(c) |
|||
return 1 |
|||
except: |
|||
pass |
|||
return '\n'.join(filter(is_valid, (' '.join(e.strip(' ;').split()[-3:]) for e in idl.splitlines()))) |
|||
|
|||
|
|||
default_attrs = {'writable':True, 'enumerable':True, 'configurable':True} |
|||
|
|||
|
|||
def compose_prototype(Class, attrs=default_attrs): |
|||
prototype = Class() |
|||
for i in dir(Class): |
|||
e = getattr(Class, i) |
|||
if hasattr(e, '__func__'): |
|||
temp = PyJsFunction(e.__func__, FunctionPrototype) |
|||
attrs = {k:v for k,v in attrs.iteritems()} |
|||
attrs['value'] = temp |
|||
prototype.define_own_property(i, attrs) |
|||
return prototype |
|||
|
|||
|
|||
# Error codes |
|||
|
|||
INDEX_SIZE_ERR = 1 |
|||
DOMSTRING_SIZE_ERR = 2 |
|||
HIERARCHY_REQUEST_ERR = 3 |
|||
WRONG_DOCUMENT_ERR = 4 |
|||
INVALID_CHARACTER_ERR = 5 |
|||
NO_DATA_ALLOWED_ERR = 6 |
|||
NO_MODIFICATION_ALLOWED_ERR = 7 |
|||
NOT_FOUND_ERR = 8 |
|||
NOT_SUPPORTED_ERR = 9 |
|||
INUSE_ATTRIBUTE_ERR = 10 |
|||
INVALID_STATE_ERR = 11 |
|||
SYNTAX_ERR = 12 |
|||
INVALID_MODIFICATION_ERR = 13 |
|||
NAMESPACE_ERR = 14 |
|||
INVALID_ACCESS_ERR = 15 |
|||
VALIDATION_ERR = 16 |
|||
TYPE_MISMATCH_ERR = 17 |
|||
|
@ -1,73 +0,0 @@ |
|||
from StringIO import StringIO |
|||
from constants import * |
|||
from bs4 import BeautifulSoup |
|||
from js2py.base import * |
|||
try: |
|||
import lxml |
|||
def parse(source): |
|||
return BeautifulSoup(source, 'lxml') |
|||
except: |
|||
def parse(source): |
|||
return BeautifulSoup(source) |
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
x = '''<table> |
|||
<tbody> |
|||
<tr> |
|||
<td>Shady Grove</td> |
|||
<td>Aeolian</td> |
|||
</tr> |
|||
<tr> |
|||
<td>Over the River, Charlie</td> |
|||
<td>Dorian</td> |
|||
</tr> |
|||
</tbody> |
|||
</table>''' |
|||
|
|||
|
|||
|
|||
class DOM(PyJs): |
|||
prototype = ObjectPrototype |
|||
def __init__(self): |
|||
self.own = {} |
|||
|
|||
def readonly(self, name, val): |
|||
self.define_own_property(name, {'writable':False, 'enumerable':False, 'configurable':False, 'value': Js(val)}) |
|||
|
|||
|
|||
|
|||
# DOMStringList |
|||
|
|||
class DOMStringListPrototype(DOM): |
|||
Class = 'DOMStringListPrototype' |
|||
|
|||
def contains(element): |
|||
return element.to_string().value in this._string_list |
|||
|
|||
def item(index): |
|||
return this._string_list[index.to_int()] if 0<=index.to_int()<len(this._string_list) else index.null |
|||
|
|||
|
|||
class DOMStringList(DOM): |
|||
Class = 'DOMStringList' |
|||
prototype = compose_prototype(DOMStringListPrototype) |
|||
def __init__(self, _string_list): |
|||
self.own = {} |
|||
|
|||
self._string_list = _string_list |
|||
|
|||
|
|||
# NameList |
|||
|
|||
class NameListPrototype(DOM): |
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
@ -0,0 +1,925 @@ |
|||
from __future__ import unicode_literals |
|||
import re |
|||
|
|||
import datetime |
|||
|
|||
from desc import * |
|||
from simplex import * |
|||
from conversions import * |
|||
import six |
|||
from pyjsparser import PyJsParser |
|||
from itertools import izip |
|||
|
|||
from conversions import * |
|||
from simplex import * |
|||
|
|||
|
|||
def Type(obj): |
|||
return obj.TYPE |
|||
|
|||
|
|||
# 8.6.2 |
|||
class PyJs(object): |
|||
TYPE = 'Object' |
|||
IS_CONSTRUCTOR = False |
|||
|
|||
prototype = None |
|||
Class = None |
|||
extensible = True |
|||
value = None |
|||
|
|||
own = {} |
|||
|
|||
def get_member(self, unconverted_prop): |
|||
return self.get(to_string(unconverted_prop)) |
|||
|
|||
def put_member(self, unconverted_prop, val): |
|||
return self.put(to_string(unconverted_prop), val) |
|||
|
|||
def get(self, prop): |
|||
assert type(prop) == unicode |
|||
cand = self.get_property(prop) |
|||
if cand is None: |
|||
return undefined |
|||
if is_data_descriptor(cand): |
|||
return cand['value'] |
|||
if is_undefined(cand['get']): |
|||
return undefined |
|||
return cand['get'].call(self) |
|||
|
|||
def get_own_property(self, prop): |
|||
assert type(prop) == unicode |
|||
# takes py returns py |
|||
return self.own.get(prop) |
|||
|
|||
def get_property(self, prop): |
|||
assert type(prop) == unicode |
|||
# take py returns py |
|||
cand = self.get_own_property(prop) |
|||
if cand: |
|||
return cand |
|||
if self.prototype is not None: |
|||
return self.prototype.get_property(prop) |
|||
|
|||
def put(self, prop, val, throw=False): |
|||
assert type(prop) == unicode |
|||
# takes py, returns none |
|||
if not self.can_put(prop): |
|||
if throw: |
|||
raise MakeError('TypeError', 'Could not define own property') |
|||
return |
|||
own_desc = self.get_own_property(prop) |
|||
if is_data_descriptor(own_desc): |
|||
self.own[prop]['value'] = val |
|||
return |
|||
desc = self.get_property(prop) |
|||
if is_accessor_descriptor(desc): |
|||
desc['set'].call( |
|||
self, (val, )) # calling setter on own or inherited element |
|||
else: # new property |
|||
self.own[prop] = { |
|||
'value': val, |
|||
'writable': True, |
|||
'configurable': True, |
|||
'enumerable': True |
|||
} |
|||
|
|||
def can_put(self, prop): # to check |
|||
assert type(prop) == unicode, type(prop) |
|||
# takes py returns py |
|||
desc = self.get_own_property(prop) |
|||
if desc: # if we have this property |
|||
if is_accessor_descriptor(desc): |
|||
return is_callable( |
|||
desc['set']) # Check if setter method is defined |
|||
else: # data desc |
|||
return desc['writable'] |
|||
if self.prototype is None: |
|||
return self.extensible |
|||
inherited = self.prototype.get_property(prop) |
|||
if inherited is None: |
|||
return self.extensible |
|||
if is_accessor_descriptor(inherited): |
|||
return not is_undefined(inherited['set']) |
|||
elif self.extensible: |
|||
return inherited['writable'] # weird... |
|||
return False |
|||
|
|||
def has_property(self, prop): |
|||
assert type(prop) == unicode |
|||
# takes py returns Py |
|||
return self.get_property(prop) is not None |
|||
|
|||
def delete(self, prop, throw=False): |
|||
assert type(prop) == unicode |
|||
# takes py, returns py |
|||
desc = self.get_own_property(prop) |
|||
if desc is None: |
|||
return True |
|||
if desc['configurable']: |
|||
del self.own[prop] |
|||
return True |
|||
if throw: |
|||
raise MakeError('TypeError', 'Could not define own property') |
|||
return False |
|||
|
|||
def default_value(self, hint=None): |
|||
order = ('valueOf', 'toString') |
|||
if hint == 'String' or (hint is None and self.Class == 'Date'): |
|||
order = ('toString', 'valueOf') |
|||
for meth_name in order: |
|||
method = self.get(meth_name) |
|||
if method is not None and is_callable(method): |
|||
cand = method.call(self, ()) |
|||
if is_primitive(cand): |
|||
return cand |
|||
raise MakeError('TypeError', |
|||
'Cannot convert object to primitive value') |
|||
|
|||
def define_own_property( |
|||
self, prop, desc, |
|||
throw): # Internal use only. External through Object |
|||
assert type(prop) == unicode |
|||
# takes Py, returns Py |
|||
# prop must be a Py string. Desc is either a descriptor or accessor. |
|||
# Messy method - raw translation from Ecma spec to prevent any bugs. # todo check this |
|||
current = self.get_own_property(prop) |
|||
|
|||
extensible = self.extensible |
|||
if not current: # We are creating a new OWN property |
|||
if not extensible: |
|||
if throw: |
|||
raise MakeError('TypeError', |
|||
'Could not define own property') |
|||
return False |
|||
# extensible must be True |
|||
if is_data_descriptor(desc) or is_generic_descriptor(desc): |
|||
DEFAULT_DATA_DESC = { |
|||
'value': undefined, # undefined |
|||
'writable': False, |
|||
'enumerable': False, |
|||
'configurable': False |
|||
} |
|||
DEFAULT_DATA_DESC.update(desc) |
|||
self.own[prop] = DEFAULT_DATA_DESC |
|||
else: |
|||
DEFAULT_ACCESSOR_DESC = { |
|||
'get': undefined, # undefined |
|||
'set': undefined, # undefined |
|||
'enumerable': False, |
|||
'configurable': False |
|||
} |
|||
DEFAULT_ACCESSOR_DESC.update(desc) |
|||
self.own[prop] = DEFAULT_ACCESSOR_DESC |
|||
return True |
|||
# therefore current exists! |
|||
if not desc or desc == current: # We don't need to change anything. |
|||
return True |
|||
configurable = current['configurable'] |
|||
if not configurable: # Prevent changing params |
|||
if desc.get('configurable'): |
|||
if throw: |
|||
raise MakeError('TypeError', |
|||
'Could not define own property') |
|||
return False |
|||
if 'enumerable' in desc and desc['enumerable'] != current[ |
|||
'enumerable']: |
|||
if throw: |
|||
raise MakeError('TypeError', |
|||
'Could not define own property') |
|||
return False |
|||
if is_generic_descriptor(desc): |
|||
pass |
|||
elif is_data_descriptor(current) != is_data_descriptor(desc): |
|||
# we want to change the current type of property |
|||
if not configurable: |
|||
if throw: |
|||
raise MakeError('TypeError', |
|||
'Could not define own property') |
|||
return False |
|||
if is_data_descriptor(current): # from data to setter |
|||
del current['value'] |
|||
del current['writable'] |
|||
current['set'] = undefined # undefined |
|||
current['get'] = undefined # undefined |
|||
else: # from setter to data |
|||
del current['set'] |
|||
del current['get'] |
|||
current['value'] = undefined # undefined |
|||
current['writable'] = False |
|||
elif is_data_descriptor(current) and is_data_descriptor(desc): |
|||
if not configurable: |
|||
if not current['writable'] and desc.get('writable'): |
|||
if throw: |
|||
raise MakeError('TypeError', |
|||
'Could not define own property') |
|||
return False |
|||
if not current['writable'] and 'value' in desc and current[ |
|||
'value'] != desc['value']: |
|||
if throw: |
|||
raise MakeError('TypeError', |
|||
'Could not define own property') |
|||
return False |
|||
elif is_accessor_descriptor(current) and is_accessor_descriptor(desc): |
|||
if not configurable: |
|||
if 'set' in desc and desc['set'] != current['set']: |
|||
if throw: |
|||
raise MakeError('TypeError', |
|||
'Could not define own property') |
|||
return False |
|||
if 'get' in desc and desc['get'] != current['get']: |
|||
if throw: |
|||
raise MakeError('TypeError', |
|||
'Could not define own property') |
|||
return False |
|||
current.update(desc) |
|||
return True |
|||
|
|||
def create(self, args, space): |
|||
'''Generally not a constructor, raise an error''' |
|||
raise MakeError('TypeError', '%s is not a constructor' % self.Class) |
|||
|
|||
|
|||
def get_member( |
|||
self, prop, space |
|||
): # general member getter, prop has to be unconverted prop. it is it can be any value |
|||
typ = type(self) |
|||
if typ not in PRIMITIVES: # most likely getter for object |
|||
return self.get_member( |
|||
prop |
|||
) # <- object can implement this to support faster prop getting. ex array. |
|||
elif typ == unicode: # then probably a String |
|||
if type(prop) == float and is_finite(prop): |
|||
index = int(prop) |
|||
if index == prop and 0 <= index < len(self): |
|||
return self[index] |
|||
s_prop = to_string(prop) |
|||
if s_prop == 'length': |
|||
return float(len(self)) |
|||
elif s_prop.isdigit(): |
|||
index = int(s_prop) |
|||
if 0 <= index < len(self): |
|||
return self[index] |
|||
# use standard string prototype |
|||
return space.StringPrototype.get(s_prop) |
|||
# maybe an index |
|||
elif typ == float: |
|||
# use standard number prototype |
|||
return space.NumberPrototype.get(to_string(prop)) |
|||
elif typ == bool: |
|||
return space.BooleanPrototype.get(to_string(prop)) |
|||
elif typ is UNDEFINED_TYPE: |
|||
raise MakeError('TypeError', |
|||
"Cannot read property '%s' of undefined" % prop) |
|||
elif typ is NULL_TYPE: |
|||
raise MakeError('TypeError', |
|||
"Cannot read property '%s' of null" % prop) |
|||
else: |
|||
raise RuntimeError('Unknown type! - ' + repr(typ)) |
|||
|
|||
|
|||
def get_member_dot(self, prop, space): |
|||
# dot member getter, prop has to be unicode |
|||
typ = type(self) |
|||
if typ not in PRIMITIVES: # most likely getter for object |
|||
return self.get(prop) |
|||
elif typ == unicode: # then probably a String |
|||
if prop == 'length': |
|||
return float(len(self)) |
|||
elif prop.isdigit(): |
|||
index = int(prop) |
|||
if 0 <= index < len(self): |
|||
return self[index] |
|||
else: |
|||
# use standard string prototype |
|||
return space.StringPrototype.get(prop) |
|||
# maybe an index |
|||
elif typ == float: |
|||
# use standard number prototype |
|||
return space.NumberPrototype.get(prop) |
|||
elif typ == bool: |
|||
return space.BooleanPrototype.get(prop) |
|||
elif typ in (UNDEFINED_TYPE, NULL_TYPE): |
|||
raise MakeError('TypeError', |
|||
"Cannot read property '%s' of undefined" % prop) |
|||
else: |
|||
raise RuntimeError('Unknown type! - ' + repr(typ)) |
|||
|
|||
|
|||
# Object |
|||
|
|||
|
|||
class PyJsObject(PyJs): |
|||
TYPE = 'Object' |
|||
Class = 'Object' |
|||
|
|||
def __init__(self, prototype=None): |
|||
self.prototype = prototype |
|||
self.own = {} |
|||
|
|||
def _init(self, props, vals): |
|||
i = 0 |
|||
for prop, kind in props: |
|||
if prop in self.own: # just check... probably will not happen very often. |
|||
if is_data_descriptor(self.own[prop]): |
|||
if kind != 'i': |
|||
raise MakeError( |
|||
'SyntaxError', |
|||
'Invalid object initializer! Duplicate property name "%s"' |
|||
% prop) |
|||
else: |
|||
if kind == 'i' or (kind == 'g' and 'get' in self.own[prop] |
|||
) or (kind == 's' |
|||
and 'set' in self.own[prop]): |
|||
raise MakeError( |
|||
'SyntaxError', |
|||
'Invalid object initializer! Duplicate setter/getter of prop: "%s"' |
|||
% prop) |
|||
|
|||
if kind == 'i': # init |
|||
self.own[prop] = { |
|||
'value': vals[i], |
|||
'writable': True, |
|||
'enumerable': True, |
|||
'configurable': True |
|||
} |
|||
elif kind == 'g': # get |
|||
self.define_own_property(prop, { |
|||
'get': vals[i], |
|||
'enumerable': True, |
|||
'configurable': True |
|||
}, False) |
|||
elif kind == 's': # get |
|||
self.define_own_property(prop, { |
|||
'get': vals[i], |
|||
'enumerable': True, |
|||
'configurable': True |
|||
}, False) |
|||
else: |
|||
raise RuntimeError( |
|||
'Invalid property kind - %s. Expected one of i, g, s.' % |
|||
repr(kind)) |
|||
i += 1 |
|||
|
|||
def _set_props(self, prop_descs): |
|||
for prop, desc in six.iteritems(prop_descs): |
|||
self.define_own_property(prop, desc) |
|||
|
|||
|
|||
# Array |
|||
|
|||
|
|||
# todo Optimise Array - extremely slow due to index conversions from str to int and back etc. |
|||
# solution - make get and put methods callable with any type of prop and handle conversions from inside |
|||
# if not array then use to_string(prop). In array if prop is integer then just use it |
|||
# also consider removal of these stupid writable, enumerable etc for ints. |
|||
class PyJsArray(PyJs): |
|||
Class = 'Array' |
|||
|
|||
def __init__(self, length, prototype=None): |
|||
self.prototype = prototype |
|||
self.own = { |
|||
'length': { |
|||
'value': float(length), |
|||
'writable': True, |
|||
'enumerable': False, |
|||
'configurable': False |
|||
} |
|||
} |
|||
|
|||
def _init(self, elements): |
|||
for i, ele in enumerate(elements): |
|||
if ele is None: continue |
|||
self.own[unicode(i)] = { |
|||
'value': ele, |
|||
'writable': True, |
|||
'enumerable': True, |
|||
'configurable': True |
|||
} |
|||
|
|||
def put(self, prop, val, throw=False): |
|||
assert type(val) != int |
|||
# takes py, returns none |
|||
if not self.can_put(prop): |
|||
if throw: |
|||
raise MakeError('TypeError', 'Could not define own property') |
|||
return |
|||
own_desc = self.get_own_property(prop) |
|||
if is_data_descriptor(own_desc): |
|||
self.define_own_property(prop, {'value': val}, False) |
|||
return |
|||
desc = self.get_property(prop) |
|||
if is_accessor_descriptor(desc): |
|||
desc['set'].call( |
|||
self, (val, )) # calling setter on own or inherited element |
|||
else: # new property |
|||
self.define_own_property( |
|||
prop, { |
|||
'value': val, |
|||
'writable': True, |
|||
'configurable': True, |
|||
'enumerable': True |
|||
}, False) |
|||
|
|||
def define_own_property(self, prop, desc, throw): |
|||
assert type(desc.get('value')) != int |
|||
old_len_desc = self.get_own_property('length') |
|||
old_len = old_len_desc['value'] # value is js type so convert to py. |
|||
if prop == 'length': |
|||
if 'value' not in desc: |
|||
return PyJs.define_own_property(self, prop, desc, False) |
|||
new_len = to_uint32(desc['value']) |
|||
if new_len != to_number(desc['value']): |
|||
raise MakeError('RangeError', 'Invalid range!') |
|||
new_desc = dict((k, v) for k, v in six.iteritems(desc)) |
|||
new_desc['value'] = float(new_len) |
|||
if new_len >= old_len: |
|||
return PyJs.define_own_property(self, prop, new_desc, False) |
|||
if not old_len_desc['writable']: |
|||
return False |
|||
if 'writable' not in new_desc or new_desc['writable'] == True: |
|||
new_writable = True |
|||
else: |
|||
new_writable = False |
|||
new_desc['writable'] = True |
|||
if not PyJs.define_own_property(self, prop, new_desc, False): |
|||
return False |
|||
if new_len < old_len: |
|||
# not very efficient for sparse arrays, so using different method for sparse: |
|||
if old_len > 30 * len(self.own): |
|||
for ele in self.own.keys(): |
|||
if ele.isdigit() and int(ele) >= new_len: |
|||
if not self.delete( |
|||
ele |
|||
): # if failed to delete set len to current len and reject. |
|||
new_desc['value'] = old_len + 1. |
|||
if not new_writable: |
|||
new_desc['writable'] = False |
|||
PyJs.define_own_property( |
|||
self, prop, new_desc, False) |
|||
return False |
|||
old_len = new_len |
|||
else: # standard method |
|||
while new_len < old_len: |
|||
old_len -= 1 |
|||
if not self.delete( |
|||
unicode(int(old_len)) |
|||
): # if failed to delete set len to current len and reject. |
|||
new_desc['value'] = old_len + 1. |
|||
if not new_writable: |
|||
new_desc['writable'] = False |
|||
PyJs.define_own_property(self, prop, new_desc, |
|||
False) |
|||
return False |
|||
if not new_writable: |
|||
self.own['length']['writable'] = False |
|||
return True |
|||
|
|||
elif prop.isdigit(): |
|||
index = to_uint32(prop) |
|||
if index >= old_len and not old_len_desc['writable']: |
|||
return False |
|||
if not PyJs.define_own_property(self, prop, desc, False): |
|||
return False |
|||
if index >= old_len: |
|||
old_len_desc['value'] = index + 1. |
|||
return True |
|||
else: |
|||
return PyJs.define_own_property(self, prop, desc, False) |
|||
|
|||
def to_list(self): |
|||
return [ |
|||
self.get(str(e)) for e in xrange(self.get('length').to_uint32()) |
|||
] |
|||
|
|||
|
|||
# database with compiled patterns. Js pattern -> Py pattern. |
|||
REGEXP_DB = {} |
|||
|
|||
|
|||
class PyJsRegExp(PyJs): |
|||
Class = 'RegExp' |
|||
|
|||
def __init__(self, body, flags, prototype=None): |
|||
self.prototype = prototype |
|||
self.glob = True if 'g' in flags else False |
|||
self.ignore_case = re.IGNORECASE if 'i' in flags else 0 |
|||
self.multiline = re.MULTILINE if 'm' in flags else 0 |
|||
self.value = body |
|||
|
|||
if (body, flags) in REGEXP_DB: |
|||
self.pat = REGEXP_DB[body, flags] |
|||
else: |
|||
comp = None |
|||
try: |
|||
# converting JS regexp pattern to Py pattern. |
|||
possible_fixes = [(u'[]', u'[\0]'), (u'[^]', u'[^\0]'), |
|||
(u'nofix1791', u'nofix1791')] |
|||
reg = self.value |
|||
for fix, rep in possible_fixes: |
|||
comp = PyJsParser()._interpret_regexp(reg, flags) |
|||
#print 'reg -> comp', reg, '->', comp |
|||
try: |
|||
self.pat = re.compile( |
|||
comp, self.ignore_case | self.multiline) |
|||
#print reg, '->', comp |
|||
break |
|||
except: |
|||
reg = reg.replace(fix, rep) |
|||
# print 'Fix', fix, '->', rep, '=', reg |
|||
else: |
|||
raise Exception() |
|||
REGEXP_DB[body, flags] = self.pat |
|||
except: |
|||
#print 'Invalid pattern...', self.value, comp |
|||
raise MakeError( |
|||
'SyntaxError', |
|||
'Invalid RegExp pattern: %s -> %s' % (repr(self.value), |
|||
repr(comp))) |
|||
# now set own properties: |
|||
self.own = { |
|||
'source': { |
|||
'value': self.value, |
|||
'enumerable': False, |
|||
'writable': False, |
|||
'configurable': False |
|||
}, |
|||
'global': { |
|||
'value': self.glob, |
|||
'enumerable': False, |
|||
'writable': False, |
|||
'configurable': False |
|||
}, |
|||
'ignoreCase': { |
|||
'value': bool(self.ignore_case), |
|||
'enumerable': False, |
|||
'writable': False, |
|||
'configurable': False |
|||
}, |
|||
'multiline': { |
|||
'value': bool(self.multiline), |
|||
'enumerable': False, |
|||
'writable': False, |
|||
'configurable': False |
|||
}, |
|||
'lastIndex': { |
|||
'value': 0., |
|||
'enumerable': False, |
|||
'writable': True, |
|||
'configurable': False |
|||
} |
|||
} |
|||
|
|||
def match(self, string, pos): |
|||
'''string is of course a py string''' |
|||
return self.pat.match(string, int(pos)) |
|||
|
|||
|
|||
class PyJsError(PyJs): |
|||
Class = 'Error' |
|||
extensible = True |
|||
|
|||
def __init__(self, message=None, prototype=None): |
|||
self.prototype = prototype |
|||
self.own = {} |
|||
if message is not None: |
|||
self.put('message', to_string(message)) |
|||
self.own['message']['enumerable'] = False |
|||
|
|||
|
|||
class PyJsDate(PyJs): |
|||
Class = 'Date' |
|||
UTCToLocal = None # todo UTC to local should be imported! |
|||
|
|||
def __init__(self, value, prototype=None): |
|||
self.value = value |
|||
self.own = {} |
|||
self.prototype = prototype |
|||
|
|||
# todo fix this problematic datetime part |
|||
def to_local_dt(self): |
|||
return datetime.datetime.utcfromtimestamp( |
|||
self.UTCToLocal(self.value) // 1000) |
|||
|
|||
def to_utc_dt(self): |
|||
return datetime.datetime.utcfromtimestamp(self.value // 1000) |
|||
|
|||
def local_strftime(self, pattern): |
|||
if self.value is NaN: |
|||
return 'Invalid Date' |
|||
try: |
|||
dt = self.to_local_dt() |
|||
except: |
|||
raise MakeError( |
|||
'TypeError', |
|||
'unsupported date range. Will fix in future versions') |
|||
try: |
|||
return dt.strftime(pattern) |
|||
except: |
|||
raise MakeError( |
|||
'TypeError', |
|||
'Could not generate date string from this date (limitations of python.datetime)' |
|||
) |
|||
|
|||
def utc_strftime(self, pattern): |
|||
if self.value is NaN: |
|||
return 'Invalid Date' |
|||
try: |
|||
dt = self.to_utc_dt() |
|||
except: |
|||
raise MakeError( |
|||
'TypeError', |
|||
'unsupported date range. Will fix in future versions') |
|||
try: |
|||
return dt.strftime(pattern) |
|||
except: |
|||
raise MakeError( |
|||
'TypeError', |
|||
'Could not generate date string from this date (limitations of python.datetime)' |
|||
) |
|||
|
|||
|
|||
# Scope class it will hold all the variables accessible to user |
|||
class Scope(PyJs): |
|||
Class = 'Global' |
|||
extensible = True |
|||
IS_CHILD_SCOPE = True |
|||
THIS_BINDING = None |
|||
space = None |
|||
exe = None |
|||
|
|||
# todo speed up! |
|||
# in order to speed up this very important class the top scope should behave differently than |
|||
# child scopes, child scope should not have this property descriptor thing because they cant be changed anyway |
|||
# they are all confugurable= False |
|||
|
|||
def __init__(self, scope, space, parent=None): |
|||
"""Doc""" |
|||
self.space = space |
|||
self.prototype = parent |
|||
if type(scope) is not dict: |
|||
assert parent is not None, 'You initialised the WITH_SCOPE without a parent scope.' |
|||
self.own = scope |
|||
self.is_with_scope = True |
|||
else: |
|||
self.is_with_scope = False |
|||
if parent is None: |
|||
# global, top level scope |
|||
self.own = {} |
|||
for k, v in six.iteritems(scope): |
|||
# set all the global items |
|||
self.define_own_property( |
|||
k, { |
|||
'value': v, |
|||
'configurable': False, |
|||
'writable': False, |
|||
'enumerable': False |
|||
}, False) |
|||
else: |
|||
# not global, less powerful but faster closure. |
|||
self.own = scope # simple dictionary which maps name directly to js object. |
|||
|
|||
self.par = super(Scope, self) |
|||
self.stack = [] |
|||
|
|||
def register(self, var): |
|||
# registered keeps only global registered variables |
|||
if self.prototype is None: |
|||
# define in global scope |
|||
if var in self.own: |
|||
self.own[var]['configurable'] = False |
|||
else: |
|||
self.define_own_property( |
|||
var, { |
|||
'value': undefined, |
|||
'configurable': False, |
|||
'writable': True, |
|||
'enumerable': True |
|||
}, False) |
|||
elif var not in self.own: |
|||
# define in local scope since it has not been defined yet |
|||
self.own[var] = undefined # default value |
|||
|
|||
def registers(self, vars): |
|||
"""register multiple variables""" |
|||
for var in vars: |
|||
self.register(var) |
|||
|
|||
def put(self, var, val, throw=False): |
|||
if self.prototype is None: |
|||
desc = self.own.get(var) # global scope |
|||
if desc is None: |
|||
self.par.put(var, val, False) |
|||
else: |
|||
if desc['writable']: # todo consider getters/setters |
|||
desc['value'] = val |
|||
else: |
|||
if self.is_with_scope: |
|||
if self.own.has_property(var): |
|||
return self.own.put(var, val, throw=throw) |
|||
else: |
|||
return self.prototype.put(var, val) |
|||
# trying to put in local scope |
|||
# we dont know yet in which scope we should place this var |
|||
elif var in self.own: |
|||
self.own[var] = val |
|||
return val |
|||
else: |
|||
# try to put in the lower scope since we cant put in this one (var wasn't registered) |
|||
return self.prototype.put(var, val) |
|||
|
|||
def get(self, var, throw=False): |
|||
if self.prototype is not None: |
|||
if self.is_with_scope: |
|||
cand = None if not self.own.has_property( |
|||
var) else self.own.get(var) |
|||
else: |
|||
# fast local scope |
|||
cand = self.own.get(var) |
|||
if cand is None: |
|||
return self.prototype.get(var, throw) |
|||
return cand |
|||
# slow, global scope |
|||
if var not in self.own: |
|||
# try in ObjectPrototype... |
|||
if var in self.space.ObjectPrototype.own: |
|||
return self.space.ObjectPrototype.get(var) |
|||
if throw: |
|||
raise MakeError('ReferenceError', '%s is not defined' % var) |
|||
return undefined |
|||
cand = self.own[var].get('value') |
|||
return cand if cand is not None else self.own[var]['get'].call(self) |
|||
|
|||
def delete(self, var, throw=False): |
|||
if self.prototype is not None: |
|||
if self.is_with_scope: |
|||
if self.own.has_property(var): |
|||
return self.own.delete(var) |
|||
elif var in self.own: |
|||
return False |
|||
return self.prototype.delete(var) |
|||
# we are in global scope here. Must exist and be configurable to delete |
|||
if var not in self.own: |
|||
# this var does not exist, why do you want to delete it??? |
|||
return True |
|||
if self.own[var]['configurable']: |
|||
del self.own[var] |
|||
return True |
|||
# not configurable, cant delete |
|||
return False |
|||
|
|||
|
|||
def get_new_arguments_obj(args, space): |
|||
obj = space.NewObject() |
|||
obj.Class = 'Arguments' |
|||
obj.define_own_property( |
|||
'length', { |
|||
'value': float(len(args)), |
|||
'writable': True, |
|||
'enumerable': False, |
|||
'configurable': True |
|||
}, False) |
|||
for i, e in enumerate(args): |
|||
obj.put(unicode(i), e) |
|||
return obj |
|||
|
|||
|
|||
#Function |
|||
class PyJsFunction(PyJs): |
|||
Class = 'Function' |
|||
source = '{ [native code] }' |
|||
IS_CONSTRUCTOR = True |
|||
|
|||
def __init__(self, |
|||
code, |
|||
ctx, |
|||
params, |
|||
name, |
|||
space, |
|||
is_declaration, |
|||
definitions, |
|||
prototype=None): |
|||
self.prototype = prototype |
|||
self.own = {} |
|||
|
|||
self.code = code |
|||
if type( |
|||
self.code |
|||
) == int: # just a label pointing to the beginning of the code. |
|||
self.is_native = False |
|||
else: |
|||
self.is_native = True # python function |
|||
|
|||
self.ctx = ctx |
|||
|
|||
self.params = params |
|||
self.arguments_in_params = 'arguments' in params |
|||
self.definitions = definitions |
|||
|
|||
# todo remove this check later |
|||
for p in params: |
|||
assert p in self.definitions |
|||
|
|||
self.name = name |
|||
self.space = space |
|||
self.is_declaration = is_declaration |
|||
|
|||
#set own property length to the number of arguments |
|||
self.own['length'] = { |
|||
'value': float(len(params)), |
|||
'writable': False, |
|||
'enumerable': False, |
|||
'configurable': False |
|||
} |
|||
|
|||
if name: |
|||
self.own['name'] = { |
|||
'value': name, |
|||
'writable': False, |
|||
'enumerable': False, |
|||
'configurable': True |
|||
} |
|||
|
|||
if not self.is_native: # set prototype for user defined functions |
|||
# constructor points to this function |
|||
proto = space.NewObject() |
|||
proto.own['constructor'] = { |
|||
'value': self, |
|||
'writable': True, |
|||
'enumerable': False, |
|||
'configurable': True |
|||
} |
|||
self.own['prototype'] = { |
|||
'value': proto, |
|||
'writable': True, |
|||
'enumerable': False, |
|||
'configurable': False |
|||
} |
|||
# todo set up throwers on callee and arguments if in strict mode |
|||
|
|||
def call(self, this, args=()): |
|||
''' Dont use this method from inside bytecode to call other bytecode. ''' |
|||
if self.is_native: |
|||
_args = SpaceTuple( |
|||
args |
|||
) # we have to do that unfortunately to pass all the necessary info to the funcs |
|||
_args.space = self.space |
|||
return self.code( |
|||
this, _args |
|||
) # must return valid js object - undefined, null, float, unicode, bool, or PyJs |
|||
else: |
|||
return self.space.exe._call(self, this, |
|||
args) # will run inside bytecode |
|||
|
|||
def has_instance(self, other): |
|||
# I am not sure here so instanceof may not work lol. |
|||
if not is_object(other): |
|||
return False |
|||
proto = self.get('prototype') |
|||
if not is_object(proto): |
|||
raise MakeError( |
|||
'TypeError', |
|||
'Function has non-object prototype in instanceof check') |
|||
while True: |
|||
other = other.prototype |
|||
if not other: # todo make sure that the condition is not None or null |
|||
return False |
|||
if other is proto: |
|||
return True |
|||
|
|||
def create(self, args, space): |
|||
proto = self.get('prototype') |
|||
if not is_object(proto): |
|||
proto = space.ObjectPrototype |
|||
new = PyJsObject(prototype=proto) |
|||
res = self.call(new, args) |
|||
if is_object(res): |
|||
return res |
|||
return new |
|||
|
|||
def _generate_my_context(self, this, args): |
|||
my_ctx = Scope( |
|||
dict(izip(self.params, args)), self.space, parent=self.ctx) |
|||
my_ctx.registers(self.definitions) |
|||
my_ctx.THIS_BINDING = this |
|||
if not self.arguments_in_params: |
|||
my_ctx.own['arguments'] = get_new_arguments_obj(args, self.space) |
|||
if not self.is_declaration and self.name and self.name not in my_ctx.own: |
|||
my_ctx.own[ |
|||
self. |
|||
name] = self # this should be immutable binding but come on! |
|||
return my_ctx |
|||
|
|||
|
|||
class SpaceTuple: |
|||
def __init__(self, tup): |
|||
self.tup = tup |
|||
|
|||
def __len__(self): |
|||
return len(self.tup) |
|||
|
|||
def __getitem__(self, item): |
|||
return self.tup[item] |
|||
|
|||
def __iter__(self): |
|||
return iter(self.tup) |
@ -0,0 +1,752 @@ |
|||
from code import Code |
|||
from simplex import MakeError |
|||
from opcodes import * |
|||
from operations import * |
|||
from trans_utils import * |
|||
|
|||
SPECIAL_IDENTIFIERS = {'true', 'false', 'this'} |
|||
|
|||
|
|||
class ByteCodeGenerator: |
|||
def __init__(self, exe): |
|||
self.exe = exe |
|||
|
|||
self.declared_continue_labels = {} |
|||
self.declared_break_labels = {} |
|||
|
|||
self.implicit_breaks = [] |
|||
self.implicit_continues = [] |
|||
|
|||
self.declared_vars = [] |
|||
|
|||
self.function_declaration_tape = [] |
|||
|
|||
self.states = [] |
|||
|
|||
def record_state(self): |
|||
self.states.append( |
|||
(self.declared_continue_labels, self.declared_break_labels, |
|||
self.implicit_breaks, self.implicit_continues, self.declared_vars, |
|||
self.function_declaration_tape)) |
|||
self.declared_continue_labels, self.declared_break_labels, \ |
|||
self.implicit_breaks, self.implicit_continues, \ |
|||
self.declared_vars, self.function_declaration_tape = {}, {}, [], [], [], [] |
|||
|
|||
def restore_state(self): |
|||
self.declared_continue_labels, self.declared_break_labels, \ |
|||
self.implicit_breaks, self.implicit_continues, \ |
|||
self.declared_vars, self.function_declaration_tape = self.states.pop() |
|||
|
|||
def ArrayExpression(self, elements, **kwargs): |
|||
for e in elements: |
|||
if e is None: |
|||
self.emit('LOAD_NONE') |
|||
else: |
|||
self.emit(e) |
|||
self.emit('LOAD_ARRAY', len(elements)) |
|||
|
|||
def AssignmentExpression(self, operator, left, right, **kwargs): |
|||
operator = operator[:-1] |
|||
if left['type'] == 'MemberExpression': |
|||
self.emit(left['object']) |
|||
if left['computed']: |
|||
self.emit(left['property']) |
|||
self.emit(right) |
|||
if operator: |
|||
self.emit('STORE_MEMBER_OP', operator) |
|||
else: |
|||
self.emit('STORE_MEMBER') |
|||
else: |
|||
self.emit(right) |
|||
if operator: |
|||
self.emit('STORE_MEMBER_DOT_OP', left['property']['name'], |
|||
operator) |
|||
else: |
|||
self.emit('STORE_MEMBER_DOT', left['property']['name']) |
|||
elif left['type'] == 'Identifier': |
|||
if left['name'] in SPECIAL_IDENTIFIERS: |
|||
raise MakeError('SyntaxError', |
|||
'Invalid left-hand side in assignment') |
|||
self.emit(right) |
|||
if operator: |
|||
self.emit('STORE_OP', left['name'], operator) |
|||
else: |
|||
self.emit('STORE', left['name']) |
|||
else: |
|||
raise MakeError('SyntaxError', |
|||
'Invalid left-hand side in assignment') |
|||
|
|||
def BinaryExpression(self, operator, left, right, **kwargs): |
|||
self.emit(left) |
|||
self.emit(right) |
|||
self.emit('BINARY_OP', operator) |
|||
|
|||
def BlockStatement(self, body, **kwargs): |
|||
self._emit_statement_list(body) |
|||
|
|||
def BreakStatement(self, label, **kwargs): |
|||
if label is None: |
|||
self.emit('JUMP', self.implicit_breaks[-1]) |
|||
else: |
|||
label = label.get('name') |
|||
if label not in self.declared_break_labels: |
|||
raise MakeError('SyntaxError', |
|||
'Undefined label \'%s\'' % label) |
|||
else: |
|||
self.emit('JUMP', self.declared_break_labels[label]) |
|||
|
|||
def CallExpression(self, callee, arguments, **kwargs): |
|||
if callee['type'] == 'MemberExpression': |
|||
self.emit(callee['object']) |
|||
if callee['computed']: |
|||
self.emit(callee['property']) |
|||
if arguments: |
|||
for e in arguments: |
|||
self.emit(e) |
|||
self.emit('LOAD_N_TUPLE', len(arguments)) |
|||
self.emit('CALL_METHOD') |
|||
else: |
|||
self.emit('CALL_METHOD_NO_ARGS') |
|||
else: |
|||
prop_name = to_key(callee['property']) |
|||
if arguments: |
|||
for e in arguments: |
|||
self.emit(e) |
|||
self.emit('LOAD_N_TUPLE', len(arguments)) |
|||
self.emit('CALL_METHOD_DOT', prop_name) |
|||
else: |
|||
self.emit('CALL_METHOD_DOT_NO_ARGS', prop_name) |
|||
else: |
|||
self.emit(callee) |
|||
if arguments: |
|||
for e in arguments: |
|||
self.emit(e) |
|||
self.emit('LOAD_N_TUPLE', len(arguments)) |
|||
self.emit('CALL') |
|||
else: |
|||
self.emit('CALL_NO_ARGS') |
|||
|
|||
def ClassBody(self, body, **kwargs): |
|||
raise NotImplementedError('Not available in ECMA 5.1') |
|||
|
|||
def ClassDeclaration(self, id, superClass, body, **kwargs): |
|||
raise NotImplementedError('Not available in ECMA 5.1') |
|||
|
|||
def ClassExpression(self, id, superClass, body, **kwargs): |
|||
raise NotImplementedError('Classes not available in ECMA 5.1') |
|||
|
|||
def ConditionalExpression(self, test, consequent, alternate, **kwargs): |
|||
alt = self.exe.get_new_label() |
|||
end = self.exe.get_new_label() |
|||
# ? |
|||
self.emit(test) |
|||
self.emit('JUMP_IF_FALSE', alt) |
|||
# first val |
|||
self.emit(consequent) |
|||
self.emit('JUMP', end) |
|||
# second val |
|||
self.emit('LABEL', alt) |
|||
self.emit(alternate) |
|||
# end of ?: statement |
|||
self.emit('LABEL', end) |
|||
|
|||
def ContinueStatement(self, label, **kwargs): |
|||
if label is None: |
|||
self.emit('JUMP', self.implicit_continues[-1]) |
|||
else: |
|||
label = label.get('name') |
|||
if label not in self.declared_continue_labels: |
|||
raise MakeError('SyntaxError', |
|||
'Undefined label \'%s\'' % label) |
|||
else: |
|||
self.emit('JUMP', self.declared_continue_labels[label]) |
|||
|
|||
def DebuggerStatement(self, **kwargs): |
|||
self.EmptyStatement(**kwargs) |
|||
|
|||
def DoWhileStatement(self, body, test, **kwargs): |
|||
continue_label = self.exe.get_new_label() |
|||
break_label = self.exe.get_new_label() |
|||
initial_do = self.exe.get_new_label() |
|||
|
|||
self.emit('JUMP', initial_do) |
|||
self.emit('LABEL', continue_label) |
|||
self.emit(test) |
|||
self.emit('JUMP_IF_FALSE', break_label) |
|||
self.emit('LABEL', initial_do) |
|||
|
|||
# translate the body, remember to add and afterwards remove implicit break/continue labels |
|||
|
|||
self.implicit_continues.append(continue_label) |
|||
self.implicit_breaks.append(break_label) |
|||
self.emit(body) |
|||
self.implicit_continues.pop() |
|||
self.implicit_breaks.pop() |
|||
|
|||
self.emit('JUMP', continue_label) # loop back |
|||
self.emit('LABEL', break_label) |
|||
|
|||
def EmptyStatement(self, **kwargs): |
|||
# do nothing |
|||
pass |
|||
|
|||
def ExpressionStatement(self, expression, **kwargs): |
|||
# change the final stack value |
|||
# pop the previous value and execute expression |
|||
self.emit('POP') |
|||
self.emit(expression) |
|||
|
|||
def ForStatement(self, init, test, update, body, **kwargs): |
|||
continue_label = self.exe.get_new_label() |
|||
break_label = self.exe.get_new_label() |
|||
first_start = self.exe.get_new_label() |
|||
|
|||
if init is not None: |
|||
self.emit(init) |
|||
if init['type'] != 'VariableDeclaration': |
|||
self.emit('POP') |
|||
|
|||
# skip first update and go straight to test |
|||
self.emit('JUMP', first_start) |
|||
|
|||
self.emit('LABEL', continue_label) |
|||
if update: |
|||
self.emit(update) |
|||
self.emit('POP') |
|||
self.emit('LABEL', first_start) |
|||
if test: |
|||
self.emit(test) |
|||
self.emit('JUMP_IF_FALSE', break_label) |
|||
|
|||
# translate the body, remember to add and afterwards to remove implicit break/continue labels |
|||
|
|||
self.implicit_continues.append(continue_label) |
|||
self.implicit_breaks.append(break_label) |
|||
self.emit(body) |
|||
self.implicit_continues.pop() |
|||
self.implicit_breaks.pop() |
|||
|
|||
self.emit('JUMP', continue_label) # loop back |
|||
self.emit('LABEL', break_label) |
|||
|
|||
def ForInStatement(self, left, right, body, **kwargs): |
|||
# prepare the needed labels |
|||
body_start_label = self.exe.get_new_label() |
|||
continue_label = self.exe.get_new_label() |
|||
break_label = self.exe.get_new_label() |
|||
|
|||
# prepare the name |
|||
if left['type'] == 'VariableDeclaration': |
|||
if len(left['declarations']) != 1: |
|||
raise MakeError( |
|||
'SyntaxError', |
|||
' Invalid left-hand side in for-in loop: Must have a single binding.' |
|||
) |
|||
self.emit(left) |
|||
name = left['declarations'][0]['id']['name'] |
|||
elif left['type'] == 'Identifier': |
|||
name = left['name'] |
|||
else: |
|||
raise MakeError('SyntaxError', |
|||
'Invalid left-hand side in for-loop') |
|||
|
|||
# prepare the iterable |
|||
self.emit(right) |
|||
|
|||
# emit ForIn Opcode |
|||
self.emit('FOR_IN', name, body_start_label, continue_label, |
|||
break_label) |
|||
|
|||
# a special continue position |
|||
self.emit('LABEL', continue_label) |
|||
self.emit('NOP') |
|||
|
|||
self.emit('LABEL', body_start_label) |
|||
self.implicit_continues.append(continue_label) |
|||
self.implicit_breaks.append(break_label) |
|||
self.emit('LOAD_UNDEFINED') |
|||
self.emit(body) |
|||
self.implicit_continues.pop() |
|||
self.implicit_breaks.pop() |
|||
self.emit('NOP') |
|||
self.emit('LABEL', break_label) |
|||
self.emit('NOP') |
|||
|
|||
def FunctionDeclaration(self, id, params, defaults, body, **kwargs): |
|||
if defaults: |
|||
raise NotImplementedError('Defaults not available in ECMA 5.1') |
|||
|
|||
# compile function |
|||
self.record_state( |
|||
) # cleans translator state and appends it to the stack so that it can be later restored |
|||
function_start = self.exe.get_new_label() |
|||
function_declarations = self.exe.get_new_label() |
|||
declarations_done = self.exe.get_new_label( |
|||
) # put jump to this place at the and of function tape! |
|||
function_end = self.exe.get_new_label() |
|||
|
|||
# skip the function if encountered externally |
|||
self.emit('JUMP', function_end) |
|||
|
|||
self.emit('LABEL', function_start) |
|||
# call is made with empty stack so load undefined to fill it |
|||
self.emit('LOAD_UNDEFINED') |
|||
# declare all functions |
|||
self.emit('JUMP', function_declarations) |
|||
self.emit('LABEL', declarations_done) |
|||
self.function_declaration_tape.append(LABEL(function_declarations)) |
|||
|
|||
self.emit(body) |
|||
self.ReturnStatement(None) |
|||
|
|||
self.function_declaration_tape.append(JUMP(declarations_done)) |
|||
self.exe.tape.extend(self.function_declaration_tape) |
|||
|
|||
self.emit('LABEL', function_end) |
|||
declared_vars = self.declared_vars |
|||
self.restore_state() |
|||
|
|||
# create function object and append to stack |
|||
name = id.get('name') |
|||
assert name is not None |
|||
self.declared_vars.append(name) |
|||
self.function_declaration_tape.append( |
|||
LOAD_FUNCTION(function_start, tuple(p['name'] for p in params), |
|||
name, True, tuple(declared_vars))) |
|||
self.function_declaration_tape.append(STORE(name)) |
|||
self.function_declaration_tape.append(POP()) |
|||
|
|||
def FunctionExpression(self, id, params, defaults, body, **kwargs): |
|||
if defaults: |
|||
raise NotImplementedError('Defaults not available in ECMA 5.1') |
|||
|
|||
# compile function |
|||
self.record_state( |
|||
) # cleans translator state and appends it to the stack so that it can be later restored |
|||
function_start = self.exe.get_new_label() |
|||
function_declarations = self.exe.get_new_label() |
|||
declarations_done = self.exe.get_new_label( |
|||
) # put jump to this place at the and of function tape! |
|||
function_end = self.exe.get_new_label() |
|||
|
|||
# skip the function if encountered externally |
|||
self.emit('JUMP', function_end) |
|||
|
|||
self.emit('LABEL', function_start) |
|||
# call is made with empty stack so load undefined to fill it |
|||
self.emit('LOAD_UNDEFINED') |
|||
# declare all functions |
|||
self.emit('JUMP', function_declarations) |
|||
self.emit('LABEL', declarations_done) |
|||
self.function_declaration_tape.append(LABEL(function_declarations)) |
|||
|
|||
self.emit(body) |
|||
self.ReturnStatement(None) |
|||
|
|||
self.function_declaration_tape.append(JUMP(declarations_done)) |
|||
self.exe.tape.extend(self.function_declaration_tape) |
|||
|
|||
self.emit('LABEL', function_end) |
|||
declared_vars = self.declared_vars |
|||
self.restore_state() |
|||
|
|||
# create function object and append to stack |
|||
name = id.get('name') if id else None |
|||
self.emit('LOAD_FUNCTION', function_start, |
|||
tuple(p['name'] for p in params), name, False, |
|||
tuple(declared_vars)) |
|||
|
|||
def Identifier(self, name, **kwargs): |
|||
if name == 'true': |
|||
self.emit('LOAD_BOOLEAN', 1) |
|||
elif name == 'false': |
|||
self.emit('LOAD_BOOLEAN', 0) |
|||
elif name == 'undefined': |
|||
self.emit('LOAD_UNDEFINED') |
|||
else: |
|||
self.emit('LOAD', name) |
|||
|
|||
def IfStatement(self, test, consequent, alternate, **kwargs): |
|||
alt = self.exe.get_new_label() |
|||
end = self.exe.get_new_label() |
|||
# if |
|||
self.emit(test) |
|||
self.emit('JUMP_IF_FALSE', alt) |
|||
# consequent |
|||
self.emit(consequent) |
|||
self.emit('JUMP', end) |
|||
# alternate |
|||
self.emit('LABEL', alt) |
|||
if alternate is not None: |
|||
self.emit(alternate) |
|||
# end of if statement |
|||
self.emit('LABEL', end) |
|||
|
|||
def LabeledStatement(self, label, body, **kwargs): |
|||
label = label['name'] |
|||
if body['type'] in ('WhileStatement', 'DoWhileStatement', |
|||
'ForStatement', 'ForInStatement'): |
|||
# Continue label available... Simply take labels defined by the loop. |
|||
# It is important that they request continue label first |
|||
self.declared_continue_labels[label] = self.exe._label_count + 1 |
|||
self.declared_break_labels[label] = self.exe._label_count + 2 |
|||
self.emit(body) |
|||
del self.declared_break_labels[label] |
|||
del self.declared_continue_labels[label] |
|||
else: |
|||
# only break label available |
|||
lbl = self.exe.get_new_label() |
|||
self.declared_break_labels[label] = lbl |
|||
self.emit(body) |
|||
self.emit('LABEL', lbl) |
|||
del self.declared_break_labels[label] |
|||
|
|||
def Literal(self, value, **kwargs): |
|||
if value is None: |
|||
self.emit('LOAD_NULL') |
|||
elif isinstance(value, bool): |
|||
self.emit('LOAD_BOOLEAN', int(value)) |
|||
elif isinstance(value, basestring): |
|||
self.emit('LOAD_STRING', unicode(value)) |
|||
elif isinstance(value, (float, int, long)): |
|||
self.emit('LOAD_NUMBER', float(value)) |
|||
elif isinstance(value, tuple): |
|||
self.emit('LOAD_REGEXP', *value) |
|||
else: |
|||
raise RuntimeError('Unsupported literal') |
|||
|
|||
def LogicalExpression(self, left, right, operator, **kwargs): |
|||
end = self.exe.get_new_label() |
|||
if operator == '&&': |
|||
# AND |
|||
self.emit(left) |
|||
self.emit('JUMP_IF_FALSE_WITHOUT_POP', end) |
|||
self.emit('POP') |
|||
self.emit(right) |
|||
self.emit('LABEL', end) |
|||
elif operator == '||': |
|||
# OR |
|||
self.emit(left) |
|||
self.emit('JUMP_IF_TRUE_WITHOUT_POP', end) |
|||
self.emit('POP') |
|||
self.emit(right) |
|||
self.emit('LABEL', end) |
|||
else: |
|||
raise RuntimeError("Unknown logical expression: %s" % operator) |
|||
|
|||
def MemberExpression(self, computed, object, property, **kwargs): |
|||
if computed: |
|||
self.emit(object) |
|||
self.emit(property) |
|||
self.emit('LOAD_MEMBER') |
|||
else: |
|||
self.emit(object) |
|||
self.emit('LOAD_MEMBER_DOT', property['name']) |
|||
|
|||
def NewExpression(self, callee, arguments, **kwargs): |
|||
self.emit(callee) |
|||
if arguments: |
|||
n = len(arguments) |
|||
for e in arguments: |
|||
self.emit(e) |
|||
self.emit('LOAD_N_TUPLE', n) |
|||
self.emit('NEW') |
|||
else: |
|||
self.emit('NEW_NO_ARGS') |
|||
|
|||
def ObjectExpression(self, properties, **kwargs): |
|||
data = [] |
|||
for prop in properties: |
|||
self.emit(prop['value']) |
|||
if prop['computed']: |
|||
raise NotImplementedError( |
|||
'ECMA 5.1 does not support computed object properties!') |
|||
data.append((to_key(prop['key']), prop['kind'][0])) |
|||
self.emit('LOAD_OBJECT', tuple(data)) |
|||
|
|||
def Program(self, body, **kwargs): |
|||
self.emit('LOAD_UNDEFINED') |
|||
self.emit(body) |
|||
# add function tape ! |
|||
self.exe.tape = self.function_declaration_tape + self.exe.tape |
|||
|
|||
def Pyimport(self, imp, **kwargs): |
|||
raise NotImplementedError( |
|||
'Not available for bytecode interpreter yet, use the Js2Py translator.' |
|||
) |
|||
|
|||
def Property(self, kind, key, computed, value, method, shorthand, |
|||
**kwargs): |
|||
raise NotImplementedError('Not available in ECMA 5.1') |
|||
|
|||
def RestElement(self, argument, **kwargs): |
|||
raise NotImplementedError('Not available in ECMA 5.1') |
|||
|
|||
def ReturnStatement(self, argument, **kwargs): |
|||
self.emit('POP') # pop result of expression statements |
|||
if argument is None: |
|||
self.emit('LOAD_UNDEFINED') |
|||
else: |
|||
self.emit(argument) |
|||
self.emit('RETURN') |
|||
|
|||
def SequenceExpression(self, expressions, **kwargs): |
|||
for e in expressions: |
|||
self.emit(e) |
|||
self.emit('POP') |
|||
del self.exe.tape[-1] |
|||
|
|||
def SwitchCase(self, test, consequent, **kwargs): |
|||
raise NotImplementedError('Already implemented in SwitchStatement') |
|||
|
|||
def SwitchStatement(self, discriminant, cases, **kwargs): |
|||
self.emit(discriminant) |
|||
labels = [self.exe.get_new_label() for case in cases] |
|||
tests = [case['test'] for case in cases] |
|||
consequents = [case['consequent'] for case in cases] |
|||
end_of_switch = self.exe.get_new_label() |
|||
|
|||
# translate test cases |
|||
for test, label in zip(tests, labels): |
|||
if test is not None: |
|||
self.emit(test) |
|||
self.emit('JUMP_IF_EQ', label) |
|||
else: |
|||
self.emit('POP') |
|||
self.emit('JUMP', label) |
|||
# this will be executed if none of the cases worked |
|||
self.emit('POP') |
|||
self.emit('JUMP', end_of_switch) |
|||
|
|||
# translate consequents |
|||
self.implicit_breaks.append(end_of_switch) |
|||
for consequent, label in zip(consequents, labels): |
|||
self.emit('LABEL', label) |
|||
self._emit_statement_list(consequent) |
|||
self.implicit_breaks.pop() |
|||
|
|||
self.emit('LABEL', end_of_switch) |
|||
|
|||
def ThisExpression(self, **kwargs): |
|||
self.emit('LOAD_THIS') |
|||
|
|||
def ThrowStatement(self, argument, **kwargs): |
|||
# throw with the empty stack |
|||
self.emit('POP') |
|||
self.emit(argument) |
|||
self.emit('THROW') |
|||
|
|||
def TryStatement(self, block, handler, finalizer, **kwargs): |
|||
try_label = self.exe.get_new_label() |
|||
catch_label = self.exe.get_new_label() |
|||
finally_label = self.exe.get_new_label() |
|||
end_label = self.exe.get_new_label() |
|||
|
|||
self.emit('JUMP', end_label) |
|||
|
|||
# try block |
|||
self.emit('LABEL', try_label) |
|||
self.emit('LOAD_UNDEFINED') |
|||
self.emit(block) |
|||
self.emit( |
|||
'NOP' |
|||
) # needed to distinguish from break/continue vs some internal jumps |
|||
|
|||
# catch block |
|||
self.emit('LABEL', catch_label) |
|||
self.emit('LOAD_UNDEFINED') |
|||
if handler: |
|||
self.emit(handler['body']) |
|||
self.emit('NOP') |
|||
|
|||
# finally block |
|||
self.emit('LABEL', finally_label) |
|||
self.emit('LOAD_UNDEFINED') |
|||
if finalizer: |
|||
self.emit(finalizer) |
|||
self.emit('NOP') |
|||
|
|||
self.emit('LABEL', end_label) |
|||
|
|||
# give life to the code |
|||
self.emit('TRY_CATCH_FINALLY', try_label, catch_label, |
|||
handler['param']['name'] if handler else None, finally_label, |
|||
bool(finalizer), end_label) |
|||
|
|||
def UnaryExpression(self, operator, argument, **kwargs): |
|||
if operator == 'typeof' and argument[ |
|||
'type'] == 'Identifier': # todo fix typeof |
|||
self.emit('TYPEOF', argument['name']) |
|||
elif operator == 'delete': |
|||
if argument['type'] == 'MemberExpression': |
|||
self.emit(argument['object']) |
|||
if argument['property']['type'] == 'Identifier': |
|||
self.emit('LOAD_STRING', |
|||
unicode(argument['property']['name'])) |
|||
else: |
|||
self.emit(argument['property']) |
|||
self.emit('DELETE_MEMBER') |
|||
elif argument['type'] == 'Identifier': |
|||
self.emit('DELETE', argument['name']) |
|||
else: |
|||
self.emit('LOAD_BOOLEAN', 1) |
|||
elif operator in UNARY_OPERATIONS: |
|||
self.emit(argument) |
|||
self.emit('UNARY_OP', operator) |
|||
else: |
|||
raise MakeError('SyntaxError', |
|||
'Unknown unary operator %s' % operator) |
|||
|
|||
def UpdateExpression(self, operator, argument, prefix, **kwargs): |
|||
incr = int(operator == "++") |
|||
post = int(not prefix) |
|||
if argument['type'] == 'MemberExpression': |
|||
if argument['computed']: |
|||
self.emit(argument['object']) |
|||
self.emit(argument['property']) |
|||
self.emit('POSTFIX_MEMBER', post, incr) |
|||
else: |
|||
self.emit(argument['object']) |
|||
name = to_key(argument['property']) |
|||
self.emit('POSTFIX_MEMBER_DOT', post, incr, name) |
|||
elif argument['type'] == 'Identifier': |
|||
name = to_key(argument) |
|||
self.emit('POSTFIX', post, incr, name) |
|||
else: |
|||
raise MakeError('SyntaxError', |
|||
'Invalid left-hand side in assignment') |
|||
|
|||
def VariableDeclaration(self, declarations, kind, **kwargs): |
|||
if kind != 'var': |
|||
raise NotImplementedError( |
|||
'Only var variable declaration is supported by ECMA 5.1') |
|||
for d in declarations: |
|||
self.emit(d) |
|||
|
|||
def LexicalDeclaration(self, declarations, kind, **kwargs): |
|||
raise NotImplementedError('Not supported by ECMA 5.1') |
|||
|
|||
def VariableDeclarator(self, id, init, **kwargs): |
|||
name = id['name'] |
|||
if name in SPECIAL_IDENTIFIERS: |
|||
raise MakeError('Invalid left-hand side in assignment') |
|||
self.declared_vars.append(name) |
|||
if init is not None: |
|||
self.emit(init) |
|||
self.emit('STORE', name) |
|||
self.emit('POP') |
|||
|
|||
def WhileStatement(self, test, body, **kwargs): |
|||
continue_label = self.exe.get_new_label() |
|||
break_label = self.exe.get_new_label() |
|||
|
|||
self.emit('LABEL', continue_label) |
|||
self.emit(test) |
|||
self.emit('JUMP_IF_FALSE', break_label) |
|||
|
|||
# translate the body, remember to add and afterwards remove implicit break/continue labels |
|||
|
|||
self.implicit_continues.append(continue_label) |
|||
self.implicit_breaks.append(break_label) |
|||
self.emit(body) |
|||
self.implicit_continues.pop() |
|||
self.implicit_breaks.pop() |
|||
|
|||
self.emit('JUMP', continue_label) # loop back |
|||
self.emit('LABEL', break_label) |
|||
|
|||
def WithStatement(self, object, body, **kwargs): |
|||
beg_label = self.exe.get_new_label() |
|||
end_label = self.exe.get_new_label() |
|||
# scope |
|||
self.emit(object) |
|||
|
|||
# now the body |
|||
self.emit('JUMP', end_label) |
|||
self.emit('LABEL', beg_label) |
|||
self.emit('LOAD_UNDEFINED') |
|||
self.emit(body) |
|||
self.emit('NOP') |
|||
self.emit('LABEL', end_label) |
|||
|
|||
# with statement implementation |
|||
self.emit('WITH', beg_label, end_label) |
|||
|
|||
def _emit_statement_list(self, statements): |
|||
for statement in statements: |
|||
self.emit(statement) |
|||
|
|||
def emit(self, what, *args): |
|||
''' what can be either name of the op, or node, or a list of statements.''' |
|||
if isinstance(what, basestring): |
|||
return self.exe.emit(what, *args) |
|||
elif isinstance(what, list): |
|||
self._emit_statement_list(what) |
|||
else: |
|||
return getattr(self, what['type'])(**what) |
|||
|
|||
|
|||
import os, codecs |
|||
|
|||
|
|||
def path_as_local(path): |
|||
if os.path.isabs(path): |
|||
return path |
|||
# relative to cwd |
|||
return os.path.join(os.getcwd(), path) |
|||
|
|||
|
|||
def get_file_contents(path_or_file): |
|||
if hasattr(path_or_file, 'read'): |
|||
js = path_or_file.read() |
|||
else: |
|||
with codecs.open(path_as_local(path_or_file), "r", "utf-8") as f: |
|||
js = f.read() |
|||
return js |
|||
|
|||
|
|||
def main(): |
|||
from space import Space |
|||
import fill_space |
|||
|
|||
from pyjsparser import parse |
|||
import json |
|||
a = ByteCodeGenerator(Code()) |
|||
|
|||
s = Space() |
|||
fill_space.fill_space(s, a) |
|||
|
|||
a.exe.space = s |
|||
s.exe = a.exe |
|||
con = get_file_contents('internals/esprima.js') |
|||
d = parse(con + ( |
|||
''';JSON.stringify(exports.parse(%s), 4, 4)''' % json.dumps(con))) |
|||
# d = parse(''' |
|||
# function x(n) { |
|||
# log(n) |
|||
# return x(n+1) |
|||
# } |
|||
# x(0) |
|||
# ''') |
|||
|
|||
# var v = 333333; |
|||
# while (v) { |
|||
# v-- |
|||
# |
|||
# } |
|||
a.emit(d) |
|||
print a.declared_vars |
|||
print a.exe.tape |
|||
print len(a.exe.tape) |
|||
|
|||
a.exe.compile() |
|||
|
|||
def log(this, args): |
|||
print args[0] |
|||
return 999 |
|||
|
|||
print a.exe.run(a.exe.space.GlobalObj) |
|||
|
|||
|
|||
if __name__ == '__main__': |
|||
main() |
@ -0,0 +1,197 @@ |
|||
from opcodes import * |
|||
from space import * |
|||
from base import * |
|||
|
|||
|
|||
class Code: |
|||
'''Can generate, store and run sequence of ops representing js code''' |
|||
|
|||
def __init__(self, is_strict=False): |
|||
self.tape = [] |
|||
self.compiled = False |
|||
self.label_locs = None |
|||
self.is_strict = is_strict |
|||
|
|||
self.contexts = [] |
|||
self.current_ctx = None |
|||
self.return_locs = [] |
|||
self._label_count = 0 |
|||
self.label_locs = None |
|||
|
|||
# useful references |
|||
self.GLOBAL_THIS = None |
|||
self.space = None |
|||
|
|||
def get_new_label(self): |
|||
self._label_count += 1 |
|||
return self._label_count |
|||
|
|||
def emit(self, op_code, *args): |
|||
''' Adds op_code with specified args to tape ''' |
|||
self.tape.append(OP_CODES[op_code](*args)) |
|||
|
|||
def compile(self, start_loc=0): |
|||
''' Records locations of labels and compiles the code ''' |
|||
self.label_locs = {} if self.label_locs is None else self.label_locs |
|||
loc = start_loc |
|||
while loc < len(self.tape): |
|||
if type(self.tape[loc]) == LABEL: |
|||
self.label_locs[self.tape[loc].num] = loc |
|||
del self.tape[loc] |
|||
continue |
|||
loc += 1 |
|||
self.compiled = True |
|||
|
|||
def _call(self, func, this, args): |
|||
''' Calls a bytecode function func |
|||
NOTE: use !ONLY! when calling functions from native methods! ''' |
|||
assert not func.is_native |
|||
# fake call - the the runner to return to the end of the file |
|||
old_contexts = self.contexts |
|||
old_return_locs = self.return_locs |
|||
old_curr_ctx = self.current_ctx |
|||
|
|||
self.contexts = [FakeCtx()] |
|||
self.return_locs = [len(self.tape)] # target line after return |
|||
|
|||
# prepare my ctx |
|||
my_ctx = func._generate_my_context(this, args) |
|||
self.current_ctx = my_ctx |
|||
|
|||
# execute dunction |
|||
ret = self.run(my_ctx, starting_loc=self.label_locs[func.code]) |
|||
|
|||
# bring back old execution |
|||
self.current_ctx = old_curr_ctx |
|||
self.contexts = old_contexts |
|||
self.return_locs = old_return_locs |
|||
|
|||
return ret |
|||
|
|||
def execute_fragment_under_context(self, ctx, start_label, end_label): |
|||
''' just like run but returns if moved outside of the specified fragment |
|||
# 4 different exectution results |
|||
# 0=normal, 1=return, 2=jump_outside, 3=errors |
|||
# execute_fragment_under_context returns: |
|||
# (return_value, typ, return_value/jump_loc/py_error) |
|||
# ctx.stack must be len 1 and its always empty after the call. |
|||
''' |
|||
old_curr_ctx = self.current_ctx |
|||
try: |
|||
self.current_ctx = ctx |
|||
return self._execute_fragment_under_context( |
|||
ctx, start_label, end_label) |
|||
except JsException as err: |
|||
# undo the things that were put on the stack (if any) |
|||
# don't worry, I know the recovery is possible through try statement and for this reason try statement |
|||
# has its own context and stack so it will not delete the contents of the outer stack |
|||
del ctx.stack[:] |
|||
return undefined, 3, err |
|||
finally: |
|||
self.current_ctx = old_curr_ctx |
|||
|
|||
def _execute_fragment_under_context(self, ctx, start_label, end_label): |
|||
start, end = self.label_locs[start_label], self.label_locs[end_label] |
|||
initial_len = len(ctx.stack) |
|||
loc = start |
|||
entry_level = len(self.contexts) |
|||
# for e in self.tape[start:end]: |
|||
# print e |
|||
|
|||
while loc < len(self.tape): |
|||
#print loc, self.tape[loc] |
|||
if len(self.contexts) == entry_level and loc >= end: |
|||
assert loc == end |
|||
assert len(ctx.stack) == ( |
|||
1 + initial_len), 'Stack change must be equal to +1!' |
|||
return ctx.stack.pop(), 0, None # means normal return |
|||
|
|||
# execute instruction |
|||
status = self.tape[loc].eval(ctx) |
|||
|
|||
# check status for special actions |
|||
if status is not None: |
|||
if type(status) == int: # jump to label |
|||
loc = self.label_locs[status] |
|||
if len(self.contexts) == entry_level: |
|||
# check if jumped outside of the fragment and break if so |
|||
if not start <= loc < end: |
|||
assert len(ctx.stack) == ( |
|||
1 + initial_len |
|||
), 'Stack change must be equal to +1!' |
|||
return ctx.stack.pop(), 2, status # jump outside |
|||
continue |
|||
|
|||
elif len(status) == 2: # a call or a return! |
|||
# call: (new_ctx, func_loc_label_num) |
|||
if status[0] is not None: |
|||
# append old state to the stack |
|||
self.contexts.append(ctx) |
|||
self.return_locs.append(loc + 1) |
|||
# set new state |
|||
loc = self.label_locs[status[1]] |
|||
ctx = status[0] |
|||
self.current_ctx = ctx |
|||
continue |
|||
|
|||
# return: (None, None) |
|||
else: |
|||
if len(self.contexts) == entry_level: |
|||
assert len(ctx.stack) == 1 + initial_len |
|||
return undefined, 1, ctx.stack.pop( |
|||
) # return signal |
|||
return_value = ctx.stack.pop() |
|||
ctx = self.contexts.pop() |
|||
self.current_ctx = ctx |
|||
ctx.stack.append(return_value) |
|||
|
|||
loc = self.return_locs.pop() |
|||
continue |
|||
# next instruction |
|||
loc += 1 |
|||
assert False, 'Remember to add NOP at the end!' |
|||
|
|||
def run(self, ctx, starting_loc=0): |
|||
loc = starting_loc |
|||
self.current_ctx = ctx |
|||
while loc < len(self.tape): |
|||
# execute instruction |
|||
#print loc, self.tape[loc] |
|||
status = self.tape[loc].eval(ctx) |
|||
|
|||
# check status for special actions |
|||
if status is not None: |
|||
if type(status) == int: # jump to label |
|||
loc = self.label_locs[status] |
|||
continue |
|||
|
|||
elif len(status) == 2: # a call or a return! |
|||
# call: (new_ctx, func_loc_label_num) |
|||
if status[0] is not None: |
|||
# append old state to the stack |
|||
self.contexts.append(ctx) |
|||
self.return_locs.append(loc + 1) |
|||
# set new state |
|||
loc = self.label_locs[status[1]] |
|||
ctx = status[0] |
|||
self.current_ctx = ctx |
|||
continue |
|||
|
|||
# return: (None, None) |
|||
else: |
|||
return_value = ctx.stack.pop() |
|||
ctx = self.contexts.pop() |
|||
self.current_ctx = ctx |
|||
ctx.stack.append(return_value) |
|||
|
|||
loc = self.return_locs.pop() |
|||
continue |
|||
# next instruction |
|||
loc += 1 |
|||
assert len(ctx.stack) == 1, ctx.stack |
|||
return ctx.stack.pop() |
|||
|
|||
|
|||
class FakeCtx(object): |
|||
def __init__(self): |
|||
self.stack = [] |
@ -0,0 +1 @@ |
|||
__author__ = 'Piotr Dabkowski' |
@ -0,0 +1,28 @@ |
|||
from ..conversions import * |
|||
from ..func_utils import * |
|||
|
|||
|
|||
def Array(this, args): |
|||
return ArrayConstructor(args, args.space) |
|||
|
|||
|
|||
def ArrayConstructor(args, space): |
|||
if len(args) == 1: |
|||
l = get_arg(args, 0) |
|||
if type(l) == float: |
|||
if to_uint32(l) == l: |
|||
return space.NewArray(l) |
|||
else: |
|||
raise MakeError( |
|||
'RangeError', |
|||
'Invalid length specified for Array constructor (must be uint32)' |
|||
) |
|||
else: |
|||
return space.ConstructArray([l]) |
|||
else: |
|||
return space.ConstructArray(list(args)) |
|||
|
|||
|
|||
def isArray(this, args): |
|||
x = get_arg(args, 0) |
|||
return is_object(x) and x.Class == u'Array' |
@ -0,0 +1,14 @@ |
|||
from ..conversions import * |
|||
from ..func_utils import * |
|||
|
|||
|
|||
def Boolean(this, args): |
|||
return to_boolean(get_arg(args, 0)) |
|||
|
|||
|
|||
def BooleanConstructor(args, space): |
|||
temp = space.NewObject() |
|||
temp.prototype = space.BooleanPrototype |
|||
temp.Class = 'Boolean' |
|||
temp.value = to_boolean(get_arg(args, 0)) |
|||
return temp |
@ -0,0 +1,11 @@ |
|||
from __future__ import unicode_literals |
|||
|
|||
from js2py.internals.conversions import * |
|||
from js2py.internals.func_utils import * |
|||
|
|||
|
|||
class ConsoleMethods: |
|||
def log(this, args): |
|||
x = ' '.join(to_string(e) for e in args) |
|||
print(x) |
|||
return undefined |
@ -0,0 +1,405 @@ |
|||
from ..base import * |
|||
from .time_helpers import * |
|||
|
|||
TZ_OFFSET = (time.altzone // 3600) |
|||
ABS_OFFSET = abs(TZ_OFFSET) |
|||
TZ_NAME = time.tzname[1] |
|||
ISO_FORMAT = '%s-%s-%sT%s:%s:%s.%sZ' |
|||
|
|||
|
|||
@Js |
|||
def Date(year, month, date, hours, minutes, seconds, ms): |
|||
return now().to_string() |
|||
|
|||
|
|||
Date.Class = 'Date' |
|||
|
|||
|
|||
def now(): |
|||
return PyJsDate(int(time.time() * 1000), prototype=DatePrototype) |
|||
|
|||
|
|||
@Js |
|||
def UTC(year, month, date, hours, minutes, seconds, ms): # todo complete this |
|||
args = arguments |
|||
y = args[0].to_number() |
|||
m = args[1].to_number() |
|||
l = len(args) |
|||
dt = args[2].to_number() if l > 2 else Js(1) |
|||
h = args[3].to_number() if l > 3 else Js(0) |
|||
mi = args[4].to_number() if l > 4 else Js(0) |
|||
sec = args[5].to_number() if l > 5 else Js(0) |
|||
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) |
|||
|
|||
|
|||
@Js |
|||
def parse(string): |
|||
return PyJsDate( |
|||
TimeClip(parse_date(string.to_string().value)), |
|||
prototype=DatePrototype) |
|||
|
|||
|
|||
Date.define_own_property('now', { |
|||
'value': Js(now), |
|||
'enumerable': False, |
|||
'writable': True, |
|||
'configurable': True |
|||
}) |
|||
|
|||
Date.define_own_property('parse', { |
|||
'value': parse, |
|||
'enumerable': False, |
|||
'writable': True, |
|||
'configurable': True |
|||
}) |
|||
|
|||
Date.define_own_property('UTC', { |
|||
'value': UTC, |
|||
'enumerable': False, |
|||
'writable': True, |
|||
'configurable': True |
|||
}) |
|||
|
|||
|
|||
class PyJsDate(PyJs): |
|||
Class = 'Date' |
|||
extensible = True |
|||
|
|||
def __init__(self, value, prototype=None): |
|||
self.value = value |
|||
self.own = {} |
|||
self.prototype = prototype |
|||
|
|||
# todo fix this problematic datetime part |
|||
def to_local_dt(self): |
|||
return datetime.datetime.utcfromtimestamp( |
|||
UTCToLocal(self.value) // 1000) |
|||
|
|||
def to_utc_dt(self): |
|||
return datetime.datetime.utcfromtimestamp(self.value // 1000) |
|||
|
|||
def local_strftime(self, pattern): |
|||
if self.value is NaN: |
|||
return 'Invalid Date' |
|||
try: |
|||
dt = self.to_local_dt() |
|||
except: |
|||
raise MakeError( |
|||
'TypeError', |
|||
'unsupported date range. Will fix in future versions') |
|||
try: |
|||
return dt.strftime(pattern) |
|||
except: |
|||
raise MakeError( |
|||
'TypeError', |
|||
'Could not generate date string from this date (limitations of python.datetime)' |
|||
) |
|||
|
|||
def utc_strftime(self, pattern): |
|||
if self.value is NaN: |
|||
return 'Invalid Date' |
|||
try: |
|||
dt = self.to_utc_dt() |
|||
except: |
|||
raise MakeError( |
|||
'TypeError', |
|||
'unsupported date range. Will fix in future versions') |
|||
try: |
|||
return dt.strftime(pattern) |
|||
except: |
|||
raise MakeError( |
|||
'TypeError', |
|||
'Could not generate date string from this date (limitations of python.datetime)' |
|||
) |
|||
|
|||
|
|||
def parse_date(py_string): # todo support all date string formats |
|||
try: |
|||
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) |
|||
|
|||
|
|||
def date_constructor(*args): |
|||
if len(args) >= 2: |
|||
return date_constructor2(*args) |
|||
elif len(args) == 1: |
|||
return date_constructor1(args[0]) |
|||
else: |
|||
return date_constructor0() |
|||
|
|||
|
|||
def date_constructor0(): |
|||
return now() |
|||
|
|||
|
|||
def date_constructor1(value): |
|||
v = value.to_primitive() |
|||
if v._type() == 'String': |
|||
v = parse_date(v.value) |
|||
else: |
|||
v = v.to_int() |
|||
return PyJsDate(TimeClip(v), prototype=DatePrototype) |
|||
|
|||
|
|||
def date_constructor2(*args): |
|||
y = args[0].to_number() |
|||
m = args[1].to_number() |
|||
l = len(args) |
|||
dt = args[2].to_number() if l > 2 else Js(1) |
|||
h = args[3].to_number() if l > 3 else Js(0) |
|||
mi = args[4].to_number() if l > 4 else Js(0) |
|||
sec = args[5].to_number() if l > 5 else Js(0) |
|||
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( |
|||
LocalToUTC(MakeDate(MakeDay(y, m, dt), MakeTime(h, mi, sec, mili)))) |
|||
return PyJsDate(t, prototype=DatePrototype) |
|||
|
|||
|
|||
Date.create = date_constructor |
|||
|
|||
DatePrototype = PyJsDate(float('nan'), prototype=ObjectPrototype) |
|||
|
|||
|
|||
def check_date(obj): |
|||
if obj.Class != 'Date': |
|||
raise MakeError('TypeError', 'this is not a Date object') |
|||
|
|||
|
|||
class DateProto: |
|||
def toString(): |
|||
check_date(this) |
|||
if this.value is NaN: |
|||
return 'Invalid Date' |
|||
offset = (UTCToLocal(this.value) - this.value) // msPerHour |
|||
return this.local_strftime( |
|||
'%a %b %d %Y %H:%M:%S GMT') + '%s00 (%s)' % (pad( |
|||
offset, 2, True), GetTimeZoneName(this.value)) |
|||
|
|||
def toDateString(): |
|||
check_date(this) |
|||
return this.local_strftime('%d %B %Y') |
|||
|
|||
def toTimeString(): |
|||
check_date(this) |
|||
return this.local_strftime('%H:%M:%S') |
|||
|
|||
def toLocaleString(): |
|||
check_date(this) |
|||
return this.local_strftime('%d %B %Y %H:%M:%S') |
|||
|
|||
def toLocaleDateString(): |
|||
check_date(this) |
|||
return this.local_strftime('%d %B %Y') |
|||
|
|||
def toLocaleTimeString(): |
|||
check_date(this) |
|||
return this.local_strftime('%H:%M:%S') |
|||
|
|||
def valueOf(): |
|||
check_date(this) |
|||
return this.value |
|||
|
|||
def getTime(): |
|||
check_date(this) |
|||
return this.value |
|||
|
|||
def getFullYear(): |
|||
check_date(this) |
|||
if this.value is NaN: |
|||
return NaN |
|||
return YearFromTime(UTCToLocal(this.value)) |
|||
|
|||
def getUTCFullYear(): |
|||
check_date(this) |
|||
if this.value is NaN: |
|||
return NaN |
|||
return YearFromTime(this.value) |
|||
|
|||
def getMonth(): |
|||
check_date(this) |
|||
if this.value is NaN: |
|||
return NaN |
|||
return MonthFromTime(UTCToLocal(this.value)) |
|||
|
|||
def getDate(): |
|||
check_date(this) |
|||
if this.value is NaN: |
|||
return NaN |
|||
return DateFromTime(UTCToLocal(this.value)) |
|||
|
|||
def getUTCMonth(): |
|||
check_date(this) |
|||
if this.value is NaN: |
|||
return NaN |
|||
return MonthFromTime(this.value) |
|||
|
|||
def getUTCDate(): |
|||
check_date(this) |
|||
if this.value is NaN: |
|||
return NaN |
|||
return DateFromTime(this.value) |
|||
|
|||
def getDay(): |
|||
check_date(this) |
|||
if this.value is NaN: |
|||
return NaN |
|||
return WeekDay(UTCToLocal(this.value)) |
|||
|
|||
def getUTCDay(): |
|||
check_date(this) |
|||
if this.value is NaN: |
|||
return NaN |
|||
return WeekDay(this.value) |
|||
|
|||
def getHours(): |
|||
check_date(this) |
|||
if this.value is NaN: |
|||
return NaN |
|||
return HourFromTime(UTCToLocal(this.value)) |
|||
|
|||
def getUTCHours(): |
|||
check_date(this) |
|||
if this.value is NaN: |
|||
return NaN |
|||
return HourFromTime(this.value) |
|||
|
|||
def getMinutes(): |
|||
check_date(this) |
|||
if this.value is NaN: |
|||
return NaN |
|||
return MinFromTime(UTCToLocal(this.value)) |
|||
|
|||
def getUTCMinutes(): |
|||
check_date(this) |
|||
if this.value is NaN: |
|||
return NaN |
|||
return MinFromTime(this.value) |
|||
|
|||
def getSeconds(): |
|||
check_date(this) |
|||
if this.value is NaN: |
|||
return NaN |
|||
return SecFromTime(UTCToLocal(this.value)) |
|||
|
|||
def getUTCSeconds(): |
|||
check_date(this) |
|||
if this.value is NaN: |
|||
return NaN |
|||
return SecFromTime(this.value) |
|||
|
|||
def getMilliseconds(): |
|||
check_date(this) |
|||
if this.value is NaN: |
|||
return NaN |
|||
return msFromTime(UTCToLocal(this.value)) |
|||
|
|||
def getUTCMilliseconds(): |
|||
check_date(this) |
|||
if this.value is NaN: |
|||
return NaN |
|||
return msFromTime(this.value) |
|||
|
|||
def getTimezoneOffset(): |
|||
check_date(this) |
|||
if this.value is NaN: |
|||
return NaN |
|||
return (this.value - UTCToLocal(this.value)) // 60000 |
|||
|
|||
def setTime(time): |
|||
check_date(this) |
|||
this.value = TimeClip(time.to_number().to_int()) |
|||
return this.value |
|||
|
|||
def setMilliseconds(ms): |
|||
check_date(this) |
|||
t = UTCToLocal(this.value) |
|||
tim = MakeTime( |
|||
HourFromTime(t), MinFromTime(t), SecFromTime(t), ms.to_int()) |
|||
u = TimeClip(LocalToUTC(MakeDate(Day(t), tim))) |
|||
this.value = u |
|||
return u |
|||
|
|||
def setUTCMilliseconds(ms): |
|||
check_date(this) |
|||
t = this.value |
|||
tim = MakeTime( |
|||
HourFromTime(t), MinFromTime(t), SecFromTime(t), ms.to_int()) |
|||
u = TimeClip(MakeDate(Day(t), tim)) |
|||
this.value = u |
|||
return u |
|||
|
|||
# todo Complete all setters! |
|||
|
|||
def toUTCString(): |
|||
check_date(this) |
|||
return this.utc_strftime('%d %B %Y %H:%M:%S') |
|||
|
|||
def toISOString(): |
|||
check_date(this) |
|||
t = this.value |
|||
year = YearFromTime(t) |
|||
month, day, hour, minute, second, milli = pad( |
|||
MonthFromTime(t) + 1), pad(DateFromTime(t)), pad( |
|||
HourFromTime(t)), pad(MinFromTime(t)), pad( |
|||
SecFromTime(t)), pad(msFromTime(t)) |
|||
return ISO_FORMAT % (unicode(year) if 0 <= year <= 9999 else pad( |
|||
year, 6, True), month, day, hour, minute, second, milli) |
|||
|
|||
def toJSON(key): |
|||
o = this.to_object() |
|||
tv = o.to_primitive('Number') |
|||
if tv.Class == 'Number' and not tv.is_finite(): |
|||
return this.null |
|||
toISO = o.get('toISOString') |
|||
if not toISO.is_callable(): |
|||
raise this.MakeError('TypeError', 'toISOString is not callable') |
|||
return toISO.call(o, ()) |
|||
|
|||
|
|||
def pad(num, n=2, sign=False): |
|||
'''returns n digit string representation of the num''' |
|||
s = unicode(abs(num)) |
|||
if len(s) < n: |
|||
s = '0' * (n - len(s)) + s |
|||
if not sign: |
|||
return s |
|||
if num >= 0: |
|||
return '+' + s |
|||
else: |
|||
return '-' + s |
|||
|
|||
|
|||
fill_prototype(DatePrototype, DateProto, default_attrs) |
|||
|
|||
Date.define_own_property( |
|||
'prototype', { |
|||
'value': DatePrototype, |
|||
'enumerable': False, |
|||
'writable': False, |
|||
'configurable': False |
|||
}) |
|||
|
|||
DatePrototype.define_own_property('constructor', { |
|||
'value': Date, |
|||
'enumerable': False, |
|||
'writable': True, |
|||
'configurable': True |
|||
}) |
@ -0,0 +1,75 @@ |
|||
from ..base import * |
|||
from ..conversions import * |
|||
from ..func_utils import * |
|||
from pyjsparser import parse |
|||
from ..byte_trans import ByteCodeGenerator, Code |
|||
|
|||
|
|||
def Function(this, args): |
|||
# convert arguments to python list of strings |
|||
a = map(to_string, tuple(args)) |
|||
_body = u';' |
|||
_args = () |
|||
if len(a): |
|||
_body = u'%s;' % a[-1] |
|||
_args = a[:-1] |
|||
return executable_function(_body, _args, args.space, global_context=True) |
|||
|
|||
|
|||
def executable_function(_body, _args, space, global_context=True): |
|||
func_str = u'(function (%s) { ; %s ; });' % (u', '.join(_args), _body) |
|||
|
|||
co = executable_code( |
|||
code_str=func_str, space=space, global_context=global_context) |
|||
return co() |
|||
|
|||
|
|||
# you can use this one lovely piece of function to compile and execute code on the fly! Watch out though as it may generate lots of code. |
|||
# todo tape cleanup? we dont know which pieces are needed and which are not so rather impossible without smarter machinery something like GC, |
|||
# a one solution would be to have a separate tape for functions |
|||
def executable_code(code_str, space, global_context=True): |
|||
# parse first to check if any SyntaxErrors |
|||
parsed = parse(code_str) |
|||
|
|||
old_tape_len = len(space.byte_generator.exe.tape) |
|||
space.byte_generator.record_state() |
|||
start = space.byte_generator.exe.get_new_label() |
|||
skip = space.byte_generator.exe.get_new_label() |
|||
space.byte_generator.emit('JUMP', skip) |
|||
space.byte_generator.emit('LABEL', start) |
|||
space.byte_generator.emit(parsed) |
|||
space.byte_generator.emit('NOP') |
|||
space.byte_generator.emit('LABEL', skip) |
|||
space.byte_generator.emit('NOP') |
|||
space.byte_generator.restore_state() |
|||
space.byte_generator.exe.compile( |
|||
start_loc=old_tape_len |
|||
) # dont read the code from the beginning, dont be stupid! |
|||
|
|||
ctx = space.GlobalObj if global_context else space.exe.current_ctx |
|||
|
|||
def ex_code(): |
|||
ret, status, token = space.byte_generator.exe.execute_fragment_under_context( |
|||
ctx, start, skip) |
|||
# todo Clean up the tape! |
|||
# this is NOT a way to do that because the fragment may contain the executable code! We dont want to remove it |
|||
#del space.byte_generator.exe.tape[old_tape_len:] |
|||
if status == 0: |
|||
return ret |
|||
elif status == 3: |
|||
raise token |
|||
else: |
|||
raise RuntimeError( |
|||
'Unexpected return status during JIT execution: %d' % status) |
|||
|
|||
return ex_code |
|||
|
|||
|
|||
def _eval(this, args): |
|||
code_str = to_string(get_arg(args, 0)) |
|||
return executable_code(code_str, args.space, global_context=True)() |
|||
|
|||
|
|||
def log(this, args): |
|||
print ' '.join(map(to_string, args)) |
|||
return undefined |
@ -0,0 +1,157 @@ |
|||
from __future__ import unicode_literals |
|||
|
|||
from ..conversions import * |
|||
from ..func_utils import * |
|||
|
|||
import math |
|||
import random |
|||
|
|||
CONSTANTS = { |
|||
'E': 2.7182818284590452354, |
|||
'LN10': 2.302585092994046, |
|||
'LN2': 0.6931471805599453, |
|||
'LOG2E': 1.4426950408889634, |
|||
'LOG10E': 0.4342944819032518, |
|||
'PI': 3.1415926535897932, |
|||
'SQRT1_2': 0.7071067811865476, |
|||
'SQRT2': 1.4142135623730951 |
|||
} |
|||
|
|||
|
|||
class MathFunctions: |
|||
def abs(this, args): |
|||
x = get_arg(args, 0) |
|||
a = to_number(x) |
|||
if a != a: # it must be a nan |
|||
return NaN |
|||
return abs(a) |
|||
|
|||
def acos(this, args): |
|||
x = get_arg(args, 0) |
|||
a = to_number(x) |
|||
if a != a: # it must be a nan |
|||
return NaN |
|||
try: |
|||
return math.acos(a) |
|||
except: |
|||
return NaN |
|||
|
|||
def asin(this, args): |
|||
x = get_arg(args, 0) |
|||
a = to_number(x) |
|||
if a != a: # it must be a nan |
|||
return NaN |
|||
try: |
|||
return math.asin(a) |
|||
except: |
|||
return NaN |
|||
|
|||
def atan(this, args): |
|||
x = get_arg(args, 0) |
|||
a = to_number(x) |
|||
if a != a: # it must be a nan |
|||
return NaN |
|||
return math.atan(a) |
|||
|
|||
def atan2(this, args): |
|||
x = get_arg(args, 0) |
|||
y = get_arg(args, 1) |
|||
a = to_number(x) |
|||
b = to_number(y) |
|||
if a != a or b != b: # it must be a nan |
|||
return NaN |
|||
return math.atan2(a, b) |
|||
|
|||
def ceil(this, args): |
|||
x = get_arg(args, 0) |
|||
a = to_number(x) |
|||
if a != a: # it must be a nan |
|||
return NaN |
|||
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 |
|||
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 |
|||
return float(round(a)) |
|||
|
|||
def sin(this, args): |
|||
x = get_arg(args, 0) |
|||
a = to_number(x) |
|||
if not is_finite(a): # it must be a nan |
|||
return NaN |
|||
return math.sin(a) |
|||
|
|||
def cos(this, args): |
|||
x = get_arg(args, 0) |
|||
a = to_number(x) |
|||
if not is_finite(a): # it must be a nan |
|||
return NaN |
|||
return math.cos(a) |
|||
|
|||
def tan(this, args): |
|||
x = get_arg(args, 0) |
|||
a = to_number(x) |
|||
if not is_finite(a): # it must be a nan |
|||
return NaN |
|||
return math.tan(a) |
|||
|
|||
def log(this, args): |
|||
x = get_arg(args, 0) |
|||
a = to_number(x) |
|||
if a != a: # it must be a nan |
|||
return NaN |
|||
try: |
|||
return math.log(a) |
|||
except: |
|||
return NaN |
|||
|
|||
def exp(this, args): |
|||
x = get_arg(args, 0) |
|||
a = to_number(x) |
|||
if a != a: # it must be a nan |
|||
return NaN |
|||
return math.exp(a) |
|||
|
|||
def pow(this, args): |
|||
x = get_arg(args, 0) |
|||
y = get_arg(args, 1) |
|||
a = to_number(x) |
|||
b = to_number(y) |
|||
if a != a or b != b: # it must be a nan |
|||
return NaN |
|||
try: |
|||
return a**b |
|||
except: |
|||
return NaN |
|||
|
|||
def sqrt(this, args): |
|||
x = get_arg(args, 0) |
|||
a = to_number(x) |
|||
if a != a: # it must be a nan |
|||
return NaN |
|||
try: |
|||
return a**0.5 |
|||
except: |
|||
return NaN |
|||
|
|||
def min(this, args): |
|||
if len(args) == 0: |
|||
return Infinity |
|||
return min(map(to_number, tuple(args))) |
|||
|
|||
def max(this, args): |
|||
if len(args) == 0: |
|||
return -Infinity |
|||
return max(map(to_number, tuple(args))) |
|||
|
|||
def random(this, args): |
|||
return random.random() |
@ -0,0 +1,27 @@ |
|||
from __future__ import unicode_literals |
|||
|
|||
from ..conversions import * |
|||
from ..func_utils import * |
|||
|
|||
|
|||
def Number(this, args): |
|||
if len(args) == 0: |
|||
return 0. |
|||
return to_number(args[0]) |
|||
|
|||
|
|||
def NumberConstructor(args, space): |
|||
temp = space.NewObject() |
|||
temp.prototype = space.NumberPrototype |
|||
temp.Class = 'Number' |
|||
temp.value = float(to_number(get_arg(args, 0)) if len(args) > 0 else 0.) |
|||
return temp |
|||
|
|||
|
|||
CONSTS = { |
|||
'MAX_VALUE': 1.7976931348623157e308, |
|||
'MIN_VALUE': 5.0e-324, |
|||
'NaN': NaN, |
|||
'NEGATIVE_INFINITY': Infinity, |
|||
'POSITIVE_INFINITY': -Infinity |
|||
} |
@ -0,0 +1,204 @@ |
|||
from __future__ import unicode_literals |
|||
from ..conversions import * |
|||
from ..func_utils import * |
|||
from ..base import is_data_descriptor |
|||
import six |
|||
|
|||
|
|||
def Object(this, args): |
|||
val = get_arg(args, 0) |
|||
if is_null(val) or is_undefined(val): |
|||
return args.space.NewObject() |
|||
return to_object(val, args.space) |
|||
|
|||
|
|||
def ObjectCreate(args, space): |
|||
if len(args): |
|||
val = get_arg(args, 0) |
|||
if is_object(val): |
|||
# Implementation dependent, but my will simply return :) |
|||
return val |
|||
elif type(val) in (NUMBER_TYPE, STRING_TYPE, BOOLEAN_TYPE): |
|||
return to_object(val, space) |
|||
return space.NewObject() |
|||
|
|||
|
|||
class ObjectMethods: |
|||
def getPrototypeOf(this, args): |
|||
obj = get_arg(args, 0) |
|||
if not is_object(obj): |
|||
raise MakeError('TypeError', |
|||
'Object.getPrototypeOf called on non-object') |
|||
return null if obj.prototype is None else obj.prototype |
|||
|
|||
def getOwnPropertyDescriptor(this, args): |
|||
obj = get_arg(args, 0) |
|||
prop = get_arg(args, 1) |
|||
if not is_object(obj): |
|||
raise MakeError( |
|||
'TypeError', |
|||
'Object.getOwnPropertyDescriptor called on non-object') |
|||
desc = obj.own.get(to_string(prop)) |
|||
return convert_to_js_type(desc, args.space) |
|||
|
|||
def getOwnPropertyNames(this, args): |
|||
obj = get_arg(args, 0) |
|||
if not is_object(obj): |
|||
raise MakeError( |
|||
'TypeError', |
|||
'Object.getOwnPropertyDescriptor called on non-object') |
|||
return args.space.ConstructArray(obj.own.keys()) |
|||
|
|||
def create(this, args): |
|||
obj = get_arg(args, 0) |
|||
if not (is_object(obj) or is_null(obj)): |
|||
raise MakeError('TypeError', |
|||
'Object prototype may only be an Object or null') |
|||
temp = args.space.NewObject() |
|||
temp.prototype = None if is_null(obj) else obj |
|||
if len(args) > 1 and not is_undefined(args[1]): |
|||
if six.PY2: |
|||
args.tup = (args[1], ) |
|||
ObjectMethods.defineProperties.__func__(temp, args) |
|||
else: |
|||
args.tup = (args[1], ) |
|||
ObjectMethods.defineProperties(temp, args) |
|||
return temp |
|||
|
|||
def defineProperty(this, args): |
|||
obj = get_arg(args, 0) |
|||
prop = get_arg(args, 1) |
|||
attrs = get_arg(args, 2) |
|||
if not is_object(obj): |
|||
raise MakeError('TypeError', |
|||
'Object.defineProperty called on non-object') |
|||
name = to_string(prop) |
|||
if not obj.define_own_property(name, ToPropertyDescriptor(attrs), |
|||
False): |
|||
raise MakeError('TypeError', 'Cannot redefine property: %s' % name) |
|||
return obj |
|||
|
|||
def defineProperties(this, args): |
|||
obj = get_arg(args, 0) |
|||
properties = get_arg(args, 1) |
|||
if not is_object(obj): |
|||
raise MakeError('TypeError', |
|||
'Object.defineProperties called on non-object') |
|||
props = to_object(properties, args.space) |
|||
for k, v in props.own.items(): |
|||
if not v.get('enumerable'): |
|||
continue |
|||
desc = ToPropertyDescriptor(props.get(unicode(k))) |
|||
if not obj.define_own_property(unicode(k), desc, False): |
|||
raise MakeError('TypeError', |
|||
'Failed to define own property: %s' % k) |
|||
return obj |
|||
|
|||
def seal(this, args): |
|||
obj = get_arg(args, 0) |
|||
if not is_object(obj): |
|||
raise MakeError('TypeError', 'Object.seal called on non-object') |
|||
for desc in obj.own.values(): |
|||
desc['configurable'] = False |
|||
obj.extensible = False |
|||
return obj |
|||
|
|||
def freeze(this, args): |
|||
obj = get_arg(args, 0) |
|||
if not is_object(obj): |
|||
raise MakeError('TypeError', 'Object.freeze called on non-object') |
|||
for desc in obj.own.values(): |
|||
desc['configurable'] = False |
|||
if is_data_descriptor(desc): |
|||
desc['writable'] = False |
|||
obj.extensible = False |
|||
return obj |
|||
|
|||
def preventExtensions(this, args): |
|||
obj = get_arg(args, 0) |
|||
if not is_object(obj): |
|||
raise MakeError('TypeError', |
|||
'Object.preventExtensions on non-object') |
|||
obj.extensible = False |
|||
return obj |
|||
|
|||
def isSealed(this, args): |
|||
obj = get_arg(args, 0) |
|||
if not is_object(obj): |
|||
raise MakeError('TypeError', |
|||
'Object.isSealed called on non-object') |
|||
if obj.extensible: |
|||
return False |
|||
for desc in obj.own.values(): |
|||
if desc.get('configurable'): |
|||
return False |
|||
return True |
|||
|
|||
def isFrozen(this, args): |
|||
obj = get_arg(args, 0) |
|||
if not is_object(obj): |
|||
raise MakeError('TypeError', |
|||
'Object.isFrozen called on non-object') |
|||
if obj.extensible: |
|||
return False |
|||
for desc in obj.own.values(): |
|||
if desc.get('configurable'): |
|||
return False |
|||
if is_data_descriptor(desc) and desc.get('writable'): |
|||
return False |
|||
return True |
|||
|
|||
def isExtensible(this, args): |
|||
obj = get_arg(args, 0) |
|||
if not is_object(obj): |
|||
raise MakeError('TypeError', |
|||
'Object.isExtensible called on non-object') |
|||
return obj.extensible |
|||
|
|||
def keys(this, args): |
|||
obj = get_arg(args, 0) |
|||
if not is_object(obj): |
|||
raise MakeError('TypeError', 'Object.keys called on non-object') |
|||
return args.space.ConstructArray([ |
|||
unicode(e) for e, d in six.iteritems(obj.own) |
|||
if d.get('enumerable') |
|||
]) |
|||
|
|||
|
|||
# some utility functions: |
|||
|
|||
|
|||
def ToPropertyDescriptor(obj): # page 38 (50 absolute) |
|||
if not is_object(obj): |
|||
raise MakeError('TypeError', |
|||
'Can\'t convert non-object to property descriptor') |
|||
desc = {} |
|||
if obj.has_property('enumerable'): |
|||
desc['enumerable'] = to_boolean(obj.get('enumerable')) |
|||
if obj.has_property('configurable'): |
|||
desc['configurable'] = to_boolean(obj.get('configurable')) |
|||
if obj.has_property('value'): |
|||
desc['value'] = obj.get('value') |
|||
if obj.has_property('writable'): |
|||
desc['writable'] = to_boolean(obj.get('writable')) |
|||
if obj.has_property('get'): |
|||
cand = obj.get('get') |
|||
if not (is_undefined(cand) or is_callable(cand)): |
|||
raise MakeError( |
|||
'TypeError', |
|||
'Invalid getter (it has to be a function or undefined)') |
|||
desc['get'] = cand |
|||
if obj.has_property('set'): |
|||
cand = obj.get('set') |
|||
if not (is_undefined(cand) or is_callable(cand)): |
|||
raise MakeError( |
|||
'TypeError', |
|||
'Invalid setter (it has to be a function or undefined)') |
|||
desc['set'] = cand |
|||
if ('get' in desc or 'set' in desc) and ('value' in desc |
|||
or 'writable' in desc): |
|||
raise MakeError( |
|||
'TypeError', |
|||
'Invalid property. A property cannot both have accessors and be writable or have a value.' |
|||
) |
|||
return desc |
@ -0,0 +1,41 @@ |
|||
from __future__ import unicode_literals |
|||
from ..conversions import * |
|||
from ..func_utils import * |
|||
from ..base import SpaceTuple |
|||
|
|||
REG_EXP_FLAGS = ('g', 'i', 'm') |
|||
|
|||
|
|||
def RegExp(this, args): |
|||
pattern = get_arg(args, 0) |
|||
flags = get_arg(args, 1) |
|||
if GetClass(pattern) == 'RegExp': |
|||
if not is_undefined(flags): |
|||
raise MakeError( |
|||
'TypeError', |
|||
'Cannot supply flags when constructing one RegExp from another' |
|||
) |
|||
# return unchanged |
|||
return pattern |
|||
#pattern is not a regexp |
|||
if is_undefined(pattern): |
|||
pattern = u'' |
|||
else: |
|||
pattern = to_string(pattern) |
|||
flags = to_string(flags) if not is_undefined(flags) else u'' |
|||
for flag in flags: |
|||
if flag not in REG_EXP_FLAGS: |
|||
raise MakeError( |
|||
'SyntaxError', |
|||
'Invalid flags supplied to RegExp constructor "%s"' % flag) |
|||
if len(set(flags)) != len(flags): |
|||
raise MakeError( |
|||
'SyntaxError', |
|||
'Invalid flags supplied to RegExp constructor "%s"' % flags) |
|||
return args.space.NewRegExp(pattern, flags) |
|||
|
|||
|
|||
def RegExpCreate(args, space): |
|||
_args = SpaceTuple(args) |
|||
_args.space = space |
|||
return RegExp(undefined, _args) |
@ -0,0 +1,23 @@ |
|||
from ..conversions import * |
|||
from ..func_utils import * |
|||
|
|||
|
|||
def fromCharCode(this, args): |
|||
res = u'' |
|||
for e in args: |
|||
res += unichr(to_uint16(e)) |
|||
return res |
|||
|
|||
|
|||
def String(this, args): |
|||
if len(args) == 0: |
|||
return u'' |
|||
return to_string(args[0]) |
|||
|
|||
|
|||
def StringConstructor(args, space): |
|||
temp = space.NewObject() |
|||
temp.prototype = space.StringPrototype |
|||
temp.Class = 'String' |
|||
temp.value = to_string(get_arg(args, 0)) if len(args) > 0 else u'' |
|||
return temp |
@ -0,0 +1,209 @@ |
|||
from __future__ import unicode_literals |
|||
|
|||
# NOTE: t must be INT!!! |
|||
import time |
|||
import datetime |
|||
import warnings |
|||
|
|||
try: |
|||
from tzlocal import get_localzone |
|||
LOCAL_ZONE = get_localzone() |
|||
except: # except all problems... |
|||
warnings.warn( |
|||
'Please install or fix tzlocal library (pip install tzlocal) in order to make Date object work better. Otherwise I will assume DST is in effect all the time' |
|||
) |
|||
|
|||
class LOCAL_ZONE: |
|||
@staticmethod |
|||
def dst(*args): |
|||
return 1 |
|||
|
|||
|
|||
from js2py.base import MakeError |
|||
CUM = (0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365) |
|||
msPerDay = 86400000 |
|||
msPerYear = int(86400000 * 365.242) |
|||
msPerSecond = 1000 |
|||
msPerMinute = 60000 |
|||
msPerHour = 3600000 |
|||
HoursPerDay = 24 |
|||
MinutesPerHour = 60 |
|||
SecondsPerMinute = 60 |
|||
NaN = float('nan') |
|||
LocalTZA = -time.timezone * msPerSecond |
|||
|
|||
|
|||
def DaylightSavingTA(t): |
|||
if t is NaN: |
|||
return t |
|||
try: |
|||
return int( |
|||
LOCAL_ZONE.dst(datetime.datetime.utcfromtimestamp( |
|||
t // 1000)).seconds) * 1000 |
|||
except: |
|||
warnings.warn( |
|||
'Invalid datetime date, assumed DST time, may be inaccurate...', |
|||
Warning) |
|||
return 1 |
|||
#raise MakeError('TypeError', 'date not supported by python.datetime. I will solve it in future versions') |
|||
|
|||
|
|||
def GetTimeZoneName(t): |
|||
return time.tzname[DaylightSavingTA(t) > 0] |
|||
|
|||
|
|||
def LocalToUTC(t): |
|||
return t - LocalTZA - DaylightSavingTA(t - LocalTZA) |
|||
|
|||
|
|||
def UTCToLocal(t): |
|||
return t + LocalTZA + DaylightSavingTA(t) |
|||
|
|||
|
|||
def Day(t): |
|||
return t // 86400000 |
|||
|
|||
|
|||
def TimeWithinDay(t): |
|||
return t % 86400000 |
|||
|
|||
|
|||
def DaysInYear(y): |
|||
if y % 4: |
|||
return 365 |
|||
elif y % 100: |
|||
return 366 |
|||
elif y % 400: |
|||
return 365 |
|||
else: |
|||
return 366 |
|||
|
|||
|
|||
def DayFromYear(y): |
|||
return 365 * (y - 1970) + (y - 1969) // 4 - (y - 1901) // 100 + ( |
|||
y - 1601) // 400 |
|||
|
|||
|
|||
def TimeFromYear(y): |
|||
return 86400000 * DayFromYear(y) |
|||
|
|||
|
|||
def YearFromTime(t): |
|||
guess = 1970 - t // 31556908800 # msPerYear |
|||
gt = TimeFromYear(guess) |
|||
if gt <= t: |
|||
while gt <= t: |
|||
guess += 1 |
|||
gt = TimeFromYear(guess) |
|||
return guess - 1 |
|||
else: |
|||
while gt > t: |
|||
guess -= 1 |
|||
gt = TimeFromYear(guess) |
|||
return guess |
|||
|
|||
|
|||
def DayWithinYear(t): |
|||
return Day(t) - DayFromYear(YearFromTime(t)) |
|||
|
|||
|
|||
def InLeapYear(t): |
|||
y = YearFromTime(t) |
|||
if y % 4: |
|||
return 0 |
|||
elif y % 100: |
|||
return 1 |
|||
elif y % 400: |
|||
return 0 |
|||
else: |
|||
return 1 |
|||
|
|||
|
|||
def MonthFromTime(t): |
|||
day = DayWithinYear(t) |
|||
leap = InLeapYear(t) |
|||
if day < 31: |
|||
return 0 |
|||
day -= leap |
|||
if day < 59: |
|||
return 1 |
|||
elif day < 90: |
|||
return 2 |
|||
elif day < 120: |
|||
return 3 |
|||
elif day < 151: |
|||
return 4 |
|||
elif day < 181: |
|||
return 5 |
|||
elif day < 212: |
|||
return 6 |
|||
elif day < 243: |
|||
return 7 |
|||
elif day < 273: |
|||
return 8 |
|||
elif day < 304: |
|||
return 9 |
|||
elif day < 334: |
|||
return 10 |
|||
else: |
|||
return 11 |
|||
|
|||
|
|||
def DateFromTime(t): |
|||
mon = MonthFromTime(t) |
|||
day = DayWithinYear(t) |
|||
return day - CUM[mon] - (1 if InLeapYear(t) and mon >= 2 else 0) + 1 |
|||
|
|||
|
|||
def WeekDay(t): |
|||
# 0 == sunday |
|||
return (Day(t) + 4) % 7 |
|||
|
|||
|
|||
def msFromTime(t): |
|||
return t % 1000 |
|||
|
|||
|
|||
def SecFromTime(t): |
|||
return (t // 1000) % 60 |
|||
|
|||
|
|||
def MinFromTime(t): |
|||
return (t // 60000) % 60 |
|||
|
|||
|
|||
def HourFromTime(t): |
|||
return (t // 3600000) % 24 |
|||
|
|||
|
|||
def MakeTime(hour, Min, sec, ms): |
|||
# takes PyJs objects and returns t |
|||
if not (hour.is_finite() and Min.is_finite() and sec.is_finite() |
|||
and ms.is_finite()): |
|||
return NaN |
|||
h, m, s, milli = hour.to_int(), Min.to_int(), sec.to_int(), ms.to_int() |
|||
return h * 3600000 + m * 60000 + s * 1000 + milli |
|||
|
|||
|
|||
def MakeDay(year, month, date): |
|||
# takes PyJs objects and returns t |
|||
if not (year.is_finite() and month.is_finite() and date.is_finite()): |
|||
return NaN |
|||
y, m, dt = year.to_int(), month.to_int(), date.to_int() |
|||
y += m // 12 |
|||
mn = m % 12 |
|||
d = DayFromYear(y) + CUM[mn] + dt - 1 + (1 if DaysInYear(y) == 366 |
|||
and mn >= 2 else 0) |
|||
return d # ms per day |
|||
|
|||
|
|||
def MakeDate(day, time): |
|||
return 86400000 * day + time |
|||
|
|||
|
|||
def TimeClip(t): |
|||
if t != t or abs(t) == float('inf'): |
|||
return NaN |
|||
if abs(t) > 8.64 * 10**15: |
|||
return NaN |
|||
return int(t) |
@ -0,0 +1,148 @@ |
|||
from __future__ import unicode_literals |
|||
# Type Conversions. to_type. All must return PyJs subclass instance |
|||
from simplex import * |
|||
|
|||
|
|||
def to_primitive(self, hint=None): |
|||
if is_primitive(self): |
|||
return self |
|||
if hint is None and (self.Class == 'Number' or self.Class == 'Boolean'): |
|||
# favour number for Class== Number or Boolean default = String |
|||
hint = 'Number' |
|||
return self.default_value(hint) |
|||
|
|||
|
|||
def to_boolean(self): |
|||
typ = Type(self) |
|||
if typ == 'Boolean': # no need to convert |
|||
return self |
|||
elif typ == 'Null' or typ == 'Undefined': # they are both always false |
|||
return False |
|||
elif typ == 'Number': # false only for 0, and NaN |
|||
return self and self == self # test for nan (nan -> flase) |
|||
elif typ == 'String': |
|||
return bool(self) |
|||
else: # object - always True |
|||
return True |
|||
|
|||
|
|||
def to_number(self): |
|||
typ = Type(self) |
|||
if typ == 'Number': # or self.Class=='Number': # no need to convert |
|||
return self |
|||
elif typ == 'Null': # null is 0 |
|||
return 0. |
|||
elif typ == 'Undefined': # undefined is NaN |
|||
return NaN |
|||
elif typ == 'Boolean': # 1 for True 0 for false |
|||
return float(self) |
|||
elif typ == 'String': |
|||
s = self.strip() # Strip white space |
|||
if not s: # '' is simply 0 |
|||
return 0. |
|||
if 'x' in s or 'X' in s[:3]: # hex (positive only) |
|||
try: # try to convert |
|||
num = int(s, 16) |
|||
except ValueError: # could not convert -> NaN |
|||
return NaN |
|||
return float(num) |
|||
sign = 1 # get sign |
|||
if s[0] in '+-': |
|||
if s[0] == '-': |
|||
sign = -1 |
|||
s = s[1:] |
|||
if s == 'Infinity': # Check for infinity keyword. 'NaN' will be NaN anyway. |
|||
return sign * Infinity |
|||
try: # decimal try |
|||
num = sign * float(s) # Converted |
|||
except ValueError: |
|||
return NaN # could not convert to decimal > return NaN |
|||
return float(num) |
|||
else: # object - most likely it will be NaN. |
|||
return to_number(to_primitive(self, 'Number')) |
|||
|
|||
|
|||
def to_string(self): |
|||
typ = Type(self) |
|||
if typ == 'String': |
|||
return self |
|||
elif typ == 'Null': |
|||
return 'null' |
|||
elif typ == 'Undefined': |
|||
return 'undefined' |
|||
elif typ == 'Boolean': |
|||
return 'true' if self else 'false' |
|||
elif typ == 'Number': # or self.Class=='Number': |
|||
if is_nan(self): |
|||
return 'NaN' |
|||
elif is_infinity(self): |
|||
sign = '-' if self < 0 else '' |
|||
return sign + 'Infinity' |
|||
elif int(self) == self: # integer value! |
|||
return unicode(int(self)) |
|||
return unicode(self) # todo make it print exactly like node.js |
|||
else: # object |
|||
return to_string(to_primitive(self, 'String')) |
|||
|
|||
|
|||
def to_object(self, space): |
|||
typ = Type(self) |
|||
if typ == 'Object': |
|||
return self |
|||
elif typ == 'Boolean': # Unsure ... todo check here |
|||
return space.Boolean.create((self, ), space) |
|||
elif typ == 'Number': # ? |
|||
return space.Number.create((self, ), space) |
|||
elif typ == 'String': # ? |
|||
return space.String.create((self, ), space) |
|||
elif typ == 'Null' or typ == 'Undefined': |
|||
raise MakeError('TypeError', |
|||
'undefined or null can\'t be converted to object') |
|||
else: |
|||
raise RuntimeError() |
|||
|
|||
|
|||
def to_int32(self): |
|||
num = to_number(self) |
|||
if is_nan(num) or is_infinity(num): |
|||
return 0 |
|||
int32 = int(num) % 2**32 |
|||
return int(int32 - 2**32 if int32 >= 2**31 else int32) |
|||
|
|||
|
|||
def to_int(self): |
|||
num = to_number(self) |
|||
if is_nan(num): |
|||
return 0 |
|||
elif is_infinity(num): |
|||
return 10**20 if num > 0 else -10**20 |
|||
return int(num) |
|||
|
|||
|
|||
def to_uint32(self): |
|||
num = to_number(self) |
|||
if is_nan(num) or is_infinity(num): |
|||
return 0 |
|||
return int(num) % 2**32 |
|||
|
|||
|
|||
def to_uint16(self): |
|||
num = to_number(self) |
|||
if is_nan(num) or is_infinity(num): |
|||
return 0 |
|||
return int(num) % 2**16 |
|||
|
|||
|
|||
def to_int16(self): |
|||
num = to_number(self) |
|||
if is_nan(num) or is_infinity(num): |
|||
return 0 |
|||
int16 = int(num) % 2**16 |
|||
return int(int16 - 2**16 if int16 >= 2**15 else int16) |
|||
|
|||
|
|||
def cok(self): |
|||
"""Check object coercible""" |
|||
if type(self) in (UNDEFINED_TYPE, NULL_TYPE): |
|||
raise MakeError('TypeError', |
|||
'undefined or null can\'t be converted to object') |
@ -0,0 +1,90 @@ |
|||
# todo make sure what they mean by desc undefined? None or empty? Answer: None :) it can never be empty but None is sometimes returned. |
|||
|
|||
# I am implementing everything as dicts to speed up property creation |
|||
|
|||
# Warning: value, get, set props of dest are PyJs types. Rest is Py! |
|||
|
|||
|
|||
def is_data_descriptor(desc): |
|||
return desc and ('value' in desc or 'writable' in desc) |
|||
|
|||
|
|||
def is_accessor_descriptor(desc): |
|||
return desc and ('get' in desc or 'set' in desc) |
|||
|
|||
|
|||
def is_generic_descriptor( |
|||
desc |
|||
): # generic means not the data and not the setter - therefore it must be one that changes only enum and conf |
|||
return desc and not (is_data_descriptor(desc) |
|||
or is_accessor_descriptor(desc)) |
|||
|
|||
|
|||
def from_property_descriptor(desc, space): |
|||
if not desc: |
|||
return {} |
|||
obj = space.NewObject() |
|||
if is_data_descriptor(desc): |
|||
obj.define_own_property( |
|||
'value', { |
|||
'value': desc['value'], |
|||
'writable': True, |
|||
'enumerable': True, |
|||
'configurable': True |
|||
}, False) |
|||
obj.define_own_property( |
|||
'writable', { |
|||
'value': desc['writable'], |
|||
'writable': True, |
|||
'enumerable': True, |
|||
'configurable': True |
|||
}, False) |
|||
else: |
|||
obj.define_own_property( |
|||
'get', { |
|||
'value': desc['get'], |
|||
'writable': True, |
|||
'enumerable': True, |
|||
'configurable': True |
|||
}, False) |
|||
obj.define_own_property( |
|||
'set', { |
|||
'value': desc['set'], |
|||
'writable': True, |
|||
'enumerable': True, |
|||
'configurable': True |
|||
}, False) |
|||
obj.define_own_property( |
|||
'writable', { |
|||
'value': desc['writable'], |
|||
'writable': True, |
|||
'enumerable': True, |
|||
'configurable': True |
|||
}, False) |
|||
obj.define_own_property( |
|||
'enumerable', { |
|||
'value': desc['enumerable'], |
|||
'writable': True, |
|||
'enumerable': True, |
|||
'configurable': True |
|||
}, False) |
|||
return obj |
|||
|
|||
|
|||
def to_property_descriptor(obj): |
|||
if obj._type() != 'Object': |
|||
raise TypeError() |
|||
desc = {} |
|||
for e in ('enumerable', 'configurable', 'writable'): |
|||
if obj.has_property(e): |
|||
desc[e] = obj.get(e).to_boolean().value |
|||
if obj.has_property('value'): |
|||
desc['value'] = obj.get('value') |
|||
for e in ('get', 'set'): |
|||
if obj.has_property(e): |
|||
cand = obj.get(e) |
|||
if not (cand.is_callable() or cand.is_undefined()): |
|||
raise TypeError() |
|||
if ('get' in desc or 'set' in desc) and ('value' in desc |
|||
or 'writable' in desc): |
|||
raise TypeError() |
@ -0,0 +1,284 @@ |
|||
from __future__ import unicode_literals |
|||
|
|||
from base import Scope |
|||
from func_utils import * |
|||
from conversions import * |
|||
import six |
|||
from prototypes.jsboolean import BooleanPrototype |
|||
from prototypes.jserror import ErrorPrototype |
|||
from prototypes.jsfunction import FunctionPrototype |
|||
from prototypes.jsnumber import NumberPrototype |
|||
from prototypes.jsobject import ObjectPrototype |
|||
from prototypes.jsregexp import RegExpPrototype |
|||
from prototypes.jsstring import StringPrototype |
|||
from prototypes.jsarray import ArrayPrototype |
|||
import prototypes.jsjson as jsjson |
|||
import prototypes.jsutils as jsutils |
|||
|
|||
from constructors import jsnumber |
|||
from constructors import jsstring |
|||
from constructors import jsarray |
|||
from constructors import jsboolean |
|||
from constructors import jsregexp |
|||
from constructors import jsmath |
|||
from constructors import jsobject |
|||
from constructors import jsfunction |
|||
from constructors import jsconsole |
|||
|
|||
|
|||
def fill_proto(proto, proto_class, space): |
|||
for i in dir(proto_class): |
|||
e = getattr(proto_class, i) |
|||
if six.PY2: |
|||
if hasattr(e, '__func__'): |
|||
meth = e.__func__ |
|||
else: |
|||
continue |
|||
else: |
|||
if hasattr(e, '__call__') and not i.startswith('__'): |
|||
meth = e |
|||
else: |
|||
continue |
|||
meth_name = meth.__name__.strip('_') # RexExp._exec -> RegExp.exec |
|||
js_meth = space.NewFunction(meth, space.ctx, (), meth_name, False, ()) |
|||
set_non_enumerable(proto, meth_name, js_meth) |
|||
return proto |
|||
|
|||
|
|||
def easy_func(f, space): |
|||
return space.NewFunction(f, space.ctx, (), f.__name__, False, ()) |
|||
|
|||
|
|||
def Empty(this, args): |
|||
return undefined |
|||
|
|||
|
|||
def set_non_enumerable(obj, name, prop): |
|||
obj.define_own_property( |
|||
unicode(name), { |
|||
'value': prop, |
|||
'writable': True, |
|||
'enumerable': False, |
|||
'configurable': True |
|||
}, True) |
|||
|
|||
|
|||
def set_protected(obj, name, prop): |
|||
obj.define_own_property( |
|||
unicode(name), { |
|||
'value': prop, |
|||
'writable': False, |
|||
'enumerable': False, |
|||
'configurable': False |
|||
}, True) |
|||
|
|||
|
|||
def fill_space(space, byte_generator): |
|||
# set global scope |
|||
global_scope = Scope({}, space, parent=None) |
|||
global_scope.THIS_BINDING = global_scope |
|||
global_scope.registers(byte_generator.declared_vars) |
|||
space.GlobalObj = global_scope |
|||
|
|||
space.byte_generator = byte_generator |
|||
|
|||
# first init all protos, later take care of constructors and details |
|||
|
|||
# Function must be first obviously, we have to use a small trick to do that... |
|||
function_proto = space.NewFunction(Empty, space.ctx, (), 'Empty', False, |
|||
()) |
|||
space.FunctionPrototype = function_proto # this will fill the prototypes of the methods! |
|||
fill_proto(function_proto, FunctionPrototype, space) |
|||
|
|||
# Object next |
|||
object_proto = space.NewObject() # no proto |
|||
fill_proto(object_proto, ObjectPrototype, space) |
|||
space.ObjectPrototype = object_proto |
|||
function_proto.prototype = object_proto |
|||
|
|||
# Number |
|||
number_proto = space.NewObject() |
|||
number_proto.prototype = object_proto |
|||
fill_proto(number_proto, NumberPrototype, space) |
|||
number_proto.value = 0. |
|||
number_proto.Class = 'Number' |
|||
space.NumberPrototype = number_proto |
|||
|
|||
# String |
|||
string_proto = space.NewObject() |
|||
string_proto.prototype = object_proto |
|||
fill_proto(string_proto, StringPrototype, space) |
|||
string_proto.value = u'' |
|||
string_proto.Class = 'String' |
|||
space.StringPrototype = string_proto |
|||
|
|||
# Boolean |
|||
boolean_proto = space.NewObject() |
|||
boolean_proto.prototype = object_proto |
|||
fill_proto(boolean_proto, BooleanPrototype, space) |
|||
boolean_proto.value = False |
|||
boolean_proto.Class = 'Boolean' |
|||
space.BooleanPrototype = boolean_proto |
|||
|
|||
# Array |
|||
array_proto = space.NewArray(0) |
|||
array_proto.prototype = object_proto |
|||
fill_proto(array_proto, ArrayPrototype, space) |
|||
space.ArrayPrototype = array_proto |
|||
|
|||
# JSON |
|||
json = space.NewObject() |
|||
json.put(u'stringify', easy_func(jsjson.stringify, space)) |
|||
json.put(u'parse', easy_func(jsjson.parse, space)) |
|||
|
|||
# Utils |
|||
parseFloat = easy_func(jsutils.parseFloat, space) |
|||
parseInt = easy_func(jsutils.parseInt, space) |
|||
isNaN = easy_func(jsutils.isNaN, space) |
|||
isFinite = easy_func(jsutils.isFinite, space) |
|||
|
|||
# Error |
|||
error_proto = space.NewError(u'Error', u'') |
|||
error_proto.prototype = object_proto |
|||
error_proto.put(u'name', u'Error') |
|||
fill_proto(error_proto, ErrorPrototype, space) |
|||
space.ErrorPrototype = error_proto |
|||
|
|||
def construct_constructor(typ): |
|||
def creator(this, args): |
|||
message = get_arg(args, 0) |
|||
if not is_undefined(message): |
|||
msg = to_string(message) |
|||
else: |
|||
msg = u'' |
|||
return space.NewError(typ, msg) |
|||
|
|||
j = easy_func(creator, space) |
|||
j.name = unicode(typ) |
|||
j.prototype = space.ERROR_TYPES[typ] |
|||
|
|||
def new_create(args, space): |
|||
message = get_arg(args, 0) |
|||
if not is_undefined(message): |
|||
msg = to_string(message) |
|||
else: |
|||
msg = u'' |
|||
return space.NewError(typ, msg) |
|||
|
|||
j.create = new_create |
|||
return j |
|||
|
|||
# fill remaining error types |
|||
error_constructors = {} |
|||
for err_type_name in (u'Error', u'EvalError', u'RangeError', |
|||
u'ReferenceError', u'SyntaxError', u'TypeError', |
|||
u'URIError'): |
|||
extra_err = space.NewError(u'Error', u'') |
|||
extra_err.put(u'name', err_type_name) |
|||
setattr(space, err_type_name + u'Prototype', extra_err) |
|||
error_constructors[err_type_name] = construct_constructor( |
|||
err_type_name) |
|||
assert space.TypeErrorPrototype is not None |
|||
|
|||
# RegExp |
|||
regexp_proto = space.NewRegExp(u'(?:)', u'') |
|||
regexp_proto.prototype = object_proto |
|||
fill_proto(regexp_proto, RegExpPrototype, space) |
|||
space.RegExpPrototype = regexp_proto |
|||
|
|||
# Json |
|||
|
|||
# now all these boring constructors... |
|||
|
|||
# Number |
|||
number = easy_func(jsnumber.Number, space) |
|||
space.Number = number |
|||
number.create = jsnumber.NumberConstructor |
|||
set_non_enumerable(number_proto, 'constructor', number) |
|||
set_protected(number, 'prototype', number_proto) |
|||
# number has some extra constants |
|||
for k, v in jsnumber.CONSTS.items(): |
|||
set_protected(number, k, v) |
|||
|
|||
# String |
|||
string = easy_func(jsstring.String, space) |
|||
space.String = string |
|||
string.create = jsstring.StringConstructor |
|||
set_non_enumerable(string_proto, 'constructor', string) |
|||
set_protected(string, 'prototype', string_proto) |
|||
# string has an extra function |
|||
set_non_enumerable(string, 'fromCharCode', |
|||
easy_func(jsstring.fromCharCode, space)) |
|||
|
|||
# Boolean |
|||
boolean = easy_func(jsboolean.Boolean, space) |
|||
space.Boolean = boolean |
|||
boolean.create = jsboolean.BooleanConstructor |
|||
set_non_enumerable(boolean_proto, 'constructor', boolean) |
|||
set_protected(boolean, 'prototype', boolean_proto) |
|||
|
|||
# Array |
|||
array = easy_func(jsarray.Array, space) |
|||
space.Array = array |
|||
array.create = jsarray.ArrayConstructor |
|||
set_non_enumerable(array_proto, 'constructor', array) |
|||
set_protected(array, 'prototype', array_proto) |
|||
array.put(u'isArray', easy_func(jsarray.isArray, space)) |
|||
|
|||
# RegExp |
|||
regexp = easy_func(jsregexp.RegExp, space) |
|||
space.RegExp = regexp |
|||
regexp.create = jsregexp.RegExpCreate |
|||
set_non_enumerable(regexp_proto, 'constructor', regexp) |
|||
set_protected(regexp, 'prototype', regexp_proto) |
|||
|
|||
# Object |
|||
_object = easy_func(jsobject.Object, space) |
|||
space.Object = _object |
|||
_object.create = jsobject.ObjectCreate |
|||
set_non_enumerable(object_proto, 'constructor', _object) |
|||
set_protected(_object, 'prototype', object_proto) |
|||
fill_proto(_object, jsobject.ObjectMethods, space) |
|||
|
|||
# Function |
|||
function = easy_func(jsfunction.Function, space) |
|||
space.Function = function |
|||
|
|||
# Math |
|||
math = space.NewObject() |
|||
math.Class = 'Math' |
|||
fill_proto(math, jsmath.MathFunctions, space) |
|||
for k, v in jsmath.CONSTANTS.items(): |
|||
set_protected(math, k, v) |
|||
|
|||
console = space.NewObject() |
|||
fill_proto(console, jsconsole.ConsoleMethods, space) |
|||
|
|||
# set global object |
|||
builtins = { |
|||
'String': string, |
|||
'Number': number, |
|||
'Boolean': boolean, |
|||
'RegExp': regexp, |
|||
'exports': convert_to_js_type({}, space), |
|||
'Math': math, |
|||
#'Date', |
|||
'Object': _object, |
|||
'Function': function, |
|||
'JSON': json, |
|||
'Array': array, |
|||
'parseFloat': parseFloat, |
|||
'parseInt': parseInt, |
|||
'isFinite': isFinite, |
|||
'isNaN': isNaN, |
|||
'eval': easy_func(jsfunction._eval, space), |
|||
'console': console, |
|||
'log': console.get(u'log'), |
|||
} |
|||
|
|||
builtins.update(error_constructors) |
|||
|
|||
set_protected(global_scope, 'NaN', NaN) |
|||
set_protected(global_scope, 'Infinity', Infinity) |
|||
for k, v in builtins.items(): |
|||
set_non_enumerable(global_scope, k, v) |
@ -0,0 +1,73 @@ |
|||
from simplex import * |
|||
from conversions import * |
|||
|
|||
import six |
|||
if six.PY3: |
|||
basestring = str |
|||
long = int |
|||
xrange = range |
|||
unicode = str |
|||
|
|||
|
|||
def get_arg(arguments, n): |
|||
if len(arguments) <= n: |
|||
return undefined |
|||
return arguments[n] |
|||
|
|||
|
|||
def ensure_js_types(args, space=None): |
|||
return tuple(convert_to_js_type(e, space=space) for e in args) |
|||
|
|||
|
|||
def convert_to_js_type(e, space=None): |
|||
t = type(e) |
|||
if is_js_type(e): |
|||
return e |
|||
if t in (int, long, float): |
|||
return float(e) |
|||
elif isinstance(t, basestring): |
|||
return unicode(t) |
|||
elif t in (list, tuple): |
|||
if space is None: |
|||
raise MakeError( |
|||
'TypeError', |
|||
'Actually an internal error, could not convert to js type because space not specified' |
|||
) |
|||
return space.ConstructArray(ensure_js_types(e, space=space)) |
|||
elif t == dict: |
|||
if space is None: |
|||
raise MakeError( |
|||
'TypeError', |
|||
'Actually an internal error, could not convert to js type because space not specified' |
|||
) |
|||
new = {} |
|||
for k, v in e.items(): |
|||
new[to_string(convert_to_js_type(k, space))] = convert_to_js_type( |
|||
v, space) |
|||
return space.ConstructObject(new) |
|||
else: |
|||
raise MakeError('TypeError', 'Could not convert to js type!') |
|||
|
|||
|
|||
def is_js_type(e): |
|||
if type(e) in PRIMITIVES: |
|||
return True |
|||
elif hasattr(e, 'Class') and hasattr(e, 'value'): # not perfect but works |
|||
return True |
|||
else: |
|||
return False |
|||
|
|||
|
|||
# todo optimise these 2! |
|||
def js_array_to_tuple(arr): |
|||
length = to_uint32(arr.get(u'length')) |
|||
return tuple(arr.get(unicode(e)) for e in xrange(length)) |
|||
|
|||
|
|||
def js_array_to_list(arr): |
|||
length = to_uint32(arr.get(u'length')) |
|||
return [arr.get(unicode(e)) for e in xrange(length)] |
|||
|
|||
|
|||
def js_arr_length(arr): |
|||
return to_uint32(arr.get(u'length')) |
@ -0,0 +1,805 @@ |
|||
from operations import * |
|||
from base import get_member, get_member_dot, PyJsFunction, Scope |
|||
|
|||
|
|||
class OP_CODE(object): |
|||
_params = [] |
|||
|
|||
# def eval(self, ctx): |
|||
# raise |
|||
|
|||
def __repr__(self): |
|||
return self.__class__.__name__ + str( |
|||
tuple([getattr(self, e) for e in self._params])) |
|||
|
|||
|
|||
# --------------------- UNARY ---------------------- |
|||
|
|||
|
|||
class UNARY_OP(OP_CODE): |
|||
_params = ['operator'] |
|||
|
|||
def __init__(self, operator): |
|||
self.operator = operator |
|||
|
|||
def eval(self, ctx): |
|||
val = ctx.stack.pop() |
|||
ctx.stack.append(UNARY_OPERATIONS[self.operator](val)) |
|||
|
|||
|
|||
# special unary operations |
|||
|
|||
|
|||
class TYPEOF(OP_CODE): |
|||
_params = ['identifier'] |
|||
|
|||
def __init__(self, identifier): |
|||
self.identifier = identifier |
|||
|
|||
def eval(self, ctx): |
|||
# typeof something_undefined does not throw reference error |
|||
val = ctx.get(self.identifier, |
|||
False) # <= this makes it slightly different! |
|||
ctx.stack.append(typeof_uop(val)) |
|||
|
|||
|
|||
class POSTFIX(OP_CODE): |
|||
_params = ['cb', 'ca', 'identifier'] |
|||
|
|||
def __init__(self, post, incr, identifier): |
|||
self.identifier = identifier |
|||
self.cb = 1 if incr else -1 |
|||
self.ca = -self.cb if post else 0 |
|||
|
|||
def eval(self, ctx): |
|||
target = to_number(ctx.get(self.identifier)) + self.cb |
|||
ctx.put(self.identifier, target) |
|||
ctx.stack.append(target + self.ca) |
|||
|
|||
|
|||
class POSTFIX_MEMBER(OP_CODE): |
|||
_params = ['cb', 'ca'] |
|||
|
|||
def __init__(self, post, incr): |
|||
self.cb = 1 if incr else -1 |
|||
self.ca = -self.cb if post else 0 |
|||
|
|||
def eval(self, ctx): |
|||
name = ctx.stack.pop() |
|||
left = ctx.stack.pop() |
|||
|
|||
target = to_number(get_member(left, name, ctx.space)) + self.cb |
|||
if type(left) not in PRIMITIVES: |
|||
left.put_member(name, target) |
|||
|
|||
ctx.stack.append(target + self.ca) |
|||
|
|||
|
|||
class POSTFIX_MEMBER_DOT(OP_CODE): |
|||
_params = ['cb', 'ca', 'prop'] |
|||
|
|||
def __init__(self, post, incr, prop): |
|||
self.cb = 1 if incr else -1 |
|||
self.ca = -self.cb if post else 0 |
|||
self.prop = prop |
|||
|
|||
def eval(self, ctx): |
|||
left = ctx.stack.pop() |
|||
|
|||
target = to_number(get_member_dot(left, self.prop, |
|||
ctx.space)) + self.cb |
|||
if type(left) not in PRIMITIVES: |
|||
left.put(self.prop, target) |
|||
|
|||
ctx.stack.append(target + self.ca) |
|||
|
|||
|
|||
class DELETE(OP_CODE): |
|||
_params = ['name'] |
|||
|
|||
def __init__(self, name): |
|||
self.name = name |
|||
|
|||
def eval(self, ctx): |
|||
ctx.stack.append(ctx.delete(self.name)) |
|||
|
|||
|
|||
class DELETE_MEMBER(OP_CODE): |
|||
def eval(self, ctx): |
|||
prop = to_string(ctx.stack.pop()) |
|||
obj = to_object(ctx.stack.pop(), ctx) |
|||
ctx.stack.append(obj.delete(prop, False)) |
|||
|
|||
|
|||
# --------------------- BITWISE ---------------------- |
|||
|
|||
|
|||
class BINARY_OP(OP_CODE): |
|||
_params = ['operator'] |
|||
|
|||
def __init__(self, operator): |
|||
self.operator = operator |
|||
|
|||
def eval(self, ctx): |
|||
right = ctx.stack.pop() |
|||
left = ctx.stack.pop() |
|||
ctx.stack.append(BINARY_OPERATIONS[self.operator](left, right)) |
|||
|
|||
|
|||
# &&, || and conditional are implemented in bytecode |
|||
|
|||
# --------------------- JUMPS ---------------------- |
|||
|
|||
|
|||
# simple label that will be removed from code after compilation. labels ID will be translated |
|||
# to source code position. |
|||
class LABEL(OP_CODE): |
|||
_params = ['num'] |
|||
|
|||
def __init__(self, num): |
|||
self.num = num |
|||
|
|||
|
|||
# I implemented interpreter in the way that when an integer is returned by eval operation the execution will jump |
|||
# to the location of the label (it is loc = label_locations[label]) |
|||
|
|||
|
|||
class BASE_JUMP(OP_CODE): |
|||
_params = ['label'] |
|||
|
|||
def __init__(self, label): |
|||
self.label = label |
|||
|
|||
|
|||
class JUMP(BASE_JUMP): |
|||
def eval(self, ctx): |
|||
return self.label |
|||
|
|||
|
|||
class JUMP_IF_TRUE(BASE_JUMP): |
|||
def eval(self, ctx): |
|||
val = ctx.stack.pop() |
|||
if to_boolean(val): |
|||
return self.label |
|||
|
|||
|
|||
class JUMP_IF_EQ(BASE_JUMP): |
|||
# this one is used in switch statement - compares last 2 values using === operator and jumps popping both if true else pops last. |
|||
def eval(self, ctx): |
|||
cmp = ctx.stack.pop() |
|||
if strict_equality_op(ctx.stack[-1], cmp): |
|||
ctx.stack.pop() |
|||
return self.label |
|||
|
|||
|
|||
class JUMP_IF_TRUE_WITHOUT_POP(BASE_JUMP): |
|||
def eval(self, ctx): |
|||
val = ctx.stack[-1] |
|||
if to_boolean(val): |
|||
return self.label |
|||
|
|||
|
|||
class JUMP_IF_FALSE(BASE_JUMP): |
|||
def eval(self, ctx): |
|||
val = ctx.stack.pop() |
|||
if not to_boolean(val): |
|||
return self.label |
|||
|
|||
|
|||
class JUMP_IF_FALSE_WITHOUT_POP(BASE_JUMP): |
|||
def eval(self, ctx): |
|||
val = ctx.stack[-1] |
|||
if not to_boolean(val): |
|||
return self.label |
|||
|
|||
|
|||
class POP(OP_CODE): |
|||
def eval(self, ctx): |
|||
# todo remove this check later |
|||
assert len(ctx.stack), 'Popped from empty stack!' |
|||
del ctx.stack[-1] |
|||
|
|||
|
|||
# class REDUCE(OP_CODE): |
|||
# def eval(self, ctx): |
|||
# assert len(ctx.stack)==2 |
|||
# ctx.stack[0] = ctx.stack[1] |
|||
# del ctx.stack[1] |
|||
|
|||
# --------------- LOADING -------------- |
|||
|
|||
|
|||
class LOAD_NONE(OP_CODE): # be careful with this :) |
|||
_params = [] |
|||
|
|||
def eval(self, ctx): |
|||
ctx.stack.append(None) |
|||
|
|||
|
|||
class LOAD_N_TUPLE( |
|||
OP_CODE |
|||
): # loads the tuple composed of n last elements on stack. elements are popped. |
|||
_params = ['n'] |
|||
|
|||
def __init__(self, n): |
|||
self.n = n |
|||
|
|||
def eval(self, ctx): |
|||
tup = tuple(ctx.stack[-self.n:]) |
|||
del ctx.stack[-self.n:] |
|||
ctx.stack.append(tup) |
|||
|
|||
|
|||
class LOAD_UNDEFINED(OP_CODE): |
|||
def eval(self, ctx): |
|||
ctx.stack.append(undefined) |
|||
|
|||
|
|||
class LOAD_NULL(OP_CODE): |
|||
def eval(self, ctx): |
|||
ctx.stack.append(null) |
|||
|
|||
|
|||
class LOAD_BOOLEAN(OP_CODE): |
|||
_params = ['val'] |
|||
|
|||
def __init__(self, val): |
|||
assert val in (0, 1) |
|||
self.val = bool(val) |
|||
|
|||
def eval(self, ctx): |
|||
ctx.stack.append(self.val) |
|||
|
|||
|
|||
class LOAD_STRING(OP_CODE): |
|||
_params = ['val'] |
|||
|
|||
def __init__(self, val): |
|||
assert isinstance(val, basestring) |
|||
self.val = unicode(val) |
|||
|
|||
def eval(self, ctx): |
|||
ctx.stack.append(self.val) |
|||
|
|||
|
|||
class LOAD_NUMBER(OP_CODE): |
|||
_params = ['val'] |
|||
|
|||
def __init__(self, val): |
|||
assert isinstance(val, (float, int, long)) |
|||
self.val = float(val) |
|||
|
|||
def eval(self, ctx): |
|||
ctx.stack.append(self.val) |
|||
|
|||
|
|||
class LOAD_REGEXP(OP_CODE): |
|||
_params = ['body', 'flags'] |
|||
|
|||
def __init__(self, body, flags): |
|||
self.body = body |
|||
self.flags = flags |
|||
|
|||
def eval(self, ctx): |
|||
# we have to generate a new regexp - they are mutable |
|||
ctx.stack.append(ctx.space.NewRegExp(self.body, self.flags)) |
|||
|
|||
|
|||
class LOAD_FUNCTION(OP_CODE): |
|||
_params = ['start', 'params', 'name', 'is_declaration', 'definitions'] |
|||
|
|||
def __init__(self, start, params, name, is_declaration, definitions): |
|||
assert type(start) == int |
|||
self.start = start # its an ID of label pointing to the beginning of the function bytecode |
|||
self.params = params |
|||
self.name = name |
|||
self.is_declaration = bool(is_declaration) |
|||
self.definitions = tuple(set(definitions + params)) |
|||
|
|||
def eval(self, ctx): |
|||
ctx.stack.append( |
|||
ctx.space.NewFunction(self.start, ctx, self.params, self.name, |
|||
self.is_declaration, self.definitions)) |
|||
|
|||
|
|||
class LOAD_OBJECT(OP_CODE): |
|||
_params = [ |
|||
'props' |
|||
] # props are py string pairs (prop_name, kind): kind can be either i, g or s. (init, get, set) |
|||
|
|||
def __init__(self, props): |
|||
self.num = len(props) |
|||
self.props = props |
|||
|
|||
def eval(self, ctx): |
|||
obj = ctx.space.NewObject() |
|||
if self.num: |
|||
obj._init(self.props, ctx.stack[-self.num:]) |
|||
del ctx.stack[-self.num:] |
|||
|
|||
ctx.stack.append(obj) |
|||
|
|||
|
|||
class LOAD_ARRAY(OP_CODE): |
|||
_params = ['num'] |
|||
|
|||
def __init__(self, num): |
|||
self.num = num |
|||
|
|||
def eval(self, ctx): |
|||
arr = ctx.space.NewArray(self.num) |
|||
if self.num: |
|||
arr._init(ctx.stack[-self.num:]) |
|||
del ctx.stack[-self.num:] |
|||
ctx.stack.append(arr) |
|||
|
|||
|
|||
class LOAD_THIS(OP_CODE): |
|||
def eval(self, ctx): |
|||
ctx.stack.append(ctx.THIS_BINDING) |
|||
|
|||
|
|||
class LOAD(OP_CODE): # todo check! |
|||
_params = ['identifier'] |
|||
|
|||
def __init__(self, identifier): |
|||
self.identifier = identifier |
|||
|
|||
# 11.1.2 |
|||
def eval(self, ctx): |
|||
ctx.stack.append(ctx.get(self.identifier, throw=True)) |
|||
|
|||
|
|||
class LOAD_MEMBER(OP_CODE): |
|||
def eval(self, ctx): |
|||
prop = ctx.stack.pop() |
|||
obj = ctx.stack.pop() |
|||
ctx.stack.append(get_member(obj, prop, ctx.space)) |
|||
|
|||
|
|||
class LOAD_MEMBER_DOT(OP_CODE): |
|||
_params = ['prop'] |
|||
|
|||
def __init__(self, prop): |
|||
self.prop = prop |
|||
|
|||
def eval(self, ctx): |
|||
obj = ctx.stack.pop() |
|||
ctx.stack.append(get_member_dot(obj, self.prop, ctx.space)) |
|||
|
|||
|
|||
# --------------- STORING -------------- |
|||
|
|||
|
|||
class STORE(OP_CODE): |
|||
_params = ['identifier'] |
|||
|
|||
def __init__(self, identifier): |
|||
self.identifier = identifier |
|||
|
|||
def eval(self, ctx): |
|||
value = ctx.stack[-1] # don't pop |
|||
ctx.put(self.identifier, value) |
|||
|
|||
|
|||
class STORE_MEMBER(OP_CODE): |
|||
def eval(self, ctx): |
|||
value = ctx.stack.pop() |
|||
name = ctx.stack.pop() |
|||
left = ctx.stack.pop() |
|||
|
|||
typ = type(left) |
|||
if typ in PRIMITIVES: |
|||
prop = to_string(name) |
|||
if typ == NULL_TYPE: |
|||
raise MakeError('TypeError', |
|||
"Cannot set property '%s' of null" % prop) |
|||
elif typ == UNDEFINED_TYPE: |
|||
raise MakeError('TypeError', |
|||
"Cannot set property '%s' of undefined" % prop) |
|||
# just ignore... |
|||
else: |
|||
left.put_member(name, value) |
|||
|
|||
ctx.stack.append(value) |
|||
|
|||
|
|||
class STORE_MEMBER_DOT(OP_CODE): |
|||
_params = ['prop'] |
|||
|
|||
def __init__(self, prop): |
|||
self.prop = prop |
|||
|
|||
def eval(self, ctx): |
|||
value = ctx.stack.pop() |
|||
left = ctx.stack.pop() |
|||
|
|||
typ = type(left) |
|||
if typ in PRIMITIVES: |
|||
if typ == NULL_TYPE: |
|||
raise MakeError('TypeError', |
|||
"Cannot set property '%s' of null" % self.prop) |
|||
elif typ == UNDEFINED_TYPE: |
|||
raise MakeError( |
|||
'TypeError', |
|||
"Cannot set property '%s' of undefined" % self.prop) |
|||
# just ignore... |
|||
else: |
|||
left.put(self.prop, value) |
|||
ctx.stack.append(value) |
|||
|
|||
|
|||
class STORE_OP(OP_CODE): |
|||
_params = ['identifier', 'op'] |
|||
|
|||
def __init__(self, identifier, op): |
|||
self.identifier = identifier |
|||
self.op = op |
|||
|
|||
def eval(self, ctx): |
|||
value = ctx.stack.pop() |
|||
new_value = BINARY_OPERATIONS[self.op](ctx.get(self.identifier), value) |
|||
ctx.put(self.identifier, new_value) |
|||
ctx.stack.append(new_value) |
|||
|
|||
|
|||
class STORE_MEMBER_OP(OP_CODE): |
|||
_params = ['op'] |
|||
|
|||
def __init__(self, op): |
|||
self.op = op |
|||
|
|||
def eval(self, ctx): |
|||
value = ctx.stack.pop() |
|||
name = ctx.stack.pop() |
|||
left = ctx.stack.pop() |
|||
|
|||
typ = type(left) |
|||
if typ in PRIMITIVES: |
|||
if typ is NULL_TYPE: |
|||
raise MakeError( |
|||
'TypeError', |
|||
"Cannot set property '%s' of null" % to_string(name)) |
|||
elif typ is UNDEFINED_TYPE: |
|||
raise MakeError( |
|||
'TypeError', |
|||
"Cannot set property '%s' of undefined" % to_string(name)) |
|||
ctx.stack.append(BINARY_OPERATIONS[self.op](get_member( |
|||
left, name, ctx.space), value)) |
|||
return |
|||
else: |
|||
ctx.stack.append(BINARY_OPERATIONS[self.op](get_member( |
|||
left, name, ctx.space), value)) |
|||
left.put_member(name, ctx.stack[-1]) |
|||
|
|||
|
|||
class STORE_MEMBER_DOT_OP(OP_CODE): |
|||
_params = ['prop', 'op'] |
|||
|
|||
def __init__(self, prop, op): |
|||
self.prop = prop |
|||
self.op = op |
|||
|
|||
def eval(self, ctx): |
|||
value = ctx.stack.pop() |
|||
left = ctx.stack.pop() |
|||
|
|||
typ = type(left) |
|||
if typ in PRIMITIVES: |
|||
if typ == NULL_TYPE: |
|||
raise MakeError('TypeError', |
|||
"Cannot set property '%s' of null" % self.prop) |
|||
elif typ == UNDEFINED_TYPE: |
|||
raise MakeError( |
|||
'TypeError', |
|||
"Cannot set property '%s' of undefined" % self.prop) |
|||
ctx.stack.append(BINARY_OPERATIONS[self.op](get_member_dot( |
|||
left, self.prop, ctx.space), value)) |
|||
return |
|||
else: |
|||
ctx.stack.append(BINARY_OPERATIONS[self.op](get_member_dot( |
|||
left, self.prop, ctx.space), value)) |
|||
left.put(self.prop, ctx.stack[-1]) |
|||
|
|||
|
|||
# --------------- CALLS -------------- |
|||
|
|||
|
|||
def bytecode_call(ctx, func, this, args): |
|||
if type(func) is not PyJsFunction: |
|||
raise MakeError('TypeError', "%s is not a function" % Type(func)) |
|||
if func.is_native: # call to built-in function or method |
|||
ctx.stack.append(func.call(this, args)) |
|||
return None |
|||
|
|||
# therefore not native. we have to return (new_context, function_label) to instruct interpreter to call |
|||
return func._generate_my_context(this, args), func.code |
|||
|
|||
|
|||
class CALL(OP_CODE): |
|||
def eval(self, ctx): |
|||
args = ctx.stack.pop() |
|||
func = ctx.stack.pop() |
|||
|
|||
return bytecode_call(ctx, func, ctx.space.GlobalObj, args) |
|||
|
|||
|
|||
class CALL_METHOD(OP_CODE): |
|||
def eval(self, ctx): |
|||
args = ctx.stack.pop() |
|||
prop = ctx.stack.pop() |
|||
base = ctx.stack.pop() |
|||
|
|||
func = get_member(base, prop, ctx.space) |
|||
|
|||
return bytecode_call(ctx, func, base, args) |
|||
|
|||
|
|||
class CALL_METHOD_DOT(OP_CODE): |
|||
_params = ['prop'] |
|||
|
|||
def __init__(self, prop): |
|||
self.prop = prop |
|||
|
|||
def eval(self, ctx): |
|||
args = ctx.stack.pop() |
|||
base = ctx.stack.pop() |
|||
|
|||
func = get_member_dot(base, self.prop, ctx.space) |
|||
|
|||
return bytecode_call(ctx, func, base, args) |
|||
|
|||
|
|||
class CALL_NO_ARGS(OP_CODE): |
|||
def eval(self, ctx): |
|||
func = ctx.stack.pop() |
|||
|
|||
return bytecode_call(ctx, func, ctx.space.GlobalObj, ()) |
|||
|
|||
|
|||
class CALL_METHOD_NO_ARGS(OP_CODE): |
|||
def eval(self, ctx): |
|||
prop = ctx.stack.pop() |
|||
base = ctx.stack.pop() |
|||
|
|||
func = get_member(base, prop, ctx.space) |
|||
|
|||
return bytecode_call(ctx, func, base, ()) |
|||
|
|||
|
|||
class CALL_METHOD_DOT_NO_ARGS(OP_CODE): |
|||
_params = ['prop'] |
|||
|
|||
def __init__(self, prop): |
|||
self.prop = prop |
|||
|
|||
def eval(self, ctx): |
|||
base = ctx.stack.pop() |
|||
|
|||
func = get_member_dot(base, self.prop, ctx.space) |
|||
|
|||
return bytecode_call(ctx, func, base, ()) |
|||
|
|||
|
|||
class NOP(OP_CODE): |
|||
def eval(self, ctx): |
|||
pass |
|||
|
|||
|
|||
class RETURN(OP_CODE): |
|||
def eval( |
|||
self, ctx |
|||
): # remember to load the return value on stack before using RETURN op. |
|||
return (None, None) |
|||
|
|||
|
|||
class NEW(OP_CODE): |
|||
def eval(self, ctx): |
|||
args = ctx.stack.pop() |
|||
constructor = ctx.stack.pop() |
|||
if type(constructor) in PRIMITIVES or not hasattr( |
|||
constructor, 'create'): |
|||
raise MakeError('TypeError', |
|||
'%s is not a constructor' % Type(constructor)) |
|||
ctx.stack.append(constructor.create(args, space=ctx.space)) |
|||
|
|||
|
|||
class NEW_NO_ARGS(OP_CODE): |
|||
def eval(self, ctx): |
|||
constructor = ctx.stack.pop() |
|||
if type(constructor) in PRIMITIVES or not hasattr( |
|||
constructor, 'create'): |
|||
raise MakeError('TypeError', |
|||
'%s is not a constructor' % Type(constructor)) |
|||
ctx.stack.append(constructor.create((), space=ctx.space)) |
|||
|
|||
|
|||
# --------------- EXCEPTIONS -------------- |
|||
|
|||
|
|||
class THROW(OP_CODE): |
|||
def eval(self, ctx): |
|||
raise MakeError(None, None, ctx.stack.pop()) |
|||
|
|||
|
|||
class TRY_CATCH_FINALLY(OP_CODE): |
|||
_params = [ |
|||
'try_label', 'catch_label', 'catch_variable', 'finally_label', |
|||
'finally_present', 'end_label' |
|||
] |
|||
|
|||
def __init__(self, try_label, catch_label, catch_variable, finally_label, |
|||
finally_present, end_label): |
|||
self.try_label = try_label |
|||
self.catch_label = catch_label |
|||
self.catch_variable = catch_variable |
|||
self.finally_label = finally_label |
|||
self.finally_present = finally_present |
|||
self.end_label = end_label |
|||
|
|||
def eval(self, ctx): |
|||
# 4 different exectution results |
|||
# 0=normal, 1=return, 2=jump_outside, 3=errors |
|||
# execute_fragment_under_context returns: |
|||
# (return_value, typ, jump_loc/error) |
|||
|
|||
ctx.stack.pop() |
|||
|
|||
# execute try statement |
|||
try_status = ctx.space.exe.execute_fragment_under_context( |
|||
ctx, self.try_label, self.catch_label) |
|||
|
|||
errors = try_status[1] == 3 |
|||
|
|||
# catch |
|||
if errors and self.catch_variable is not None: |
|||
# generate catch block context... |
|||
catch_context = Scope({ |
|||
self.catch_variable: |
|||
try_status[2].get_thrown_value(ctx.space) |
|||
}, ctx.space, ctx) |
|||
catch_context.THIS_BINDING = ctx.THIS_BINDING |
|||
catch_status = ctx.space.exe.execute_fragment_under_context( |
|||
catch_context, self.catch_label, self.finally_label) |
|||
else: |
|||
catch_status = None |
|||
|
|||
# finally |
|||
if self.finally_present: |
|||
finally_status = ctx.space.exe.execute_fragment_under_context( |
|||
ctx, self.finally_label, self.end_label) |
|||
else: |
|||
finally_status = None |
|||
|
|||
# now return controls |
|||
other_status = catch_status or try_status |
|||
if finally_status is None or (finally_status[1] == 0 |
|||
and other_status[1] != 0): |
|||
winning_status = other_status |
|||
else: |
|||
winning_status = finally_status |
|||
|
|||
val, typ, spec = winning_status |
|||
if typ == 0: # normal |
|||
ctx.stack.append(val) |
|||
return |
|||
elif typ == 1: # return |
|||
ctx.stack.append(spec) |
|||
return None, None # send return signal |
|||
elif typ == 2: # jump outside |
|||
ctx.stack.append(val) |
|||
return spec |
|||
elif typ == 3: |
|||
# throw is made with empty stack as usual |
|||
raise spec |
|||
else: |
|||
raise RuntimeError('Invalid return code') |
|||
|
|||
|
|||
# ------------ WITH + ITERATORS ---------- |
|||
|
|||
|
|||
class WITH(OP_CODE): |
|||
_params = ['beg_label', 'end_label'] |
|||
|
|||
def __init__(self, beg_label, end_label): |
|||
self.beg_label = beg_label |
|||
self.end_label = end_label |
|||
|
|||
def eval(self, ctx): |
|||
obj = to_object(ctx.stack.pop(), ctx.space) |
|||
|
|||
with_context = Scope( |
|||
obj, ctx.space, ctx) # todo actually use the obj to modify the ctx |
|||
with_context.THIS_BINDING = ctx.THIS_BINDING |
|||
status = ctx.space.exe.execute_fragment_under_context( |
|||
with_context, self.beg_label, self.end_label) |
|||
|
|||
val, typ, spec = status |
|||
|
|||
if typ != 3: # exception |
|||
ctx.stack.pop() |
|||
|
|||
if typ == 0: # normal |
|||
ctx.stack.append(val) |
|||
return |
|||
elif typ == 1: # return |
|||
ctx.stack.append(spec) |
|||
return None, None # send return signal |
|||
elif typ == 2: # jump outside |
|||
ctx.stack.append(val) |
|||
return spec |
|||
elif typ == 3: # exception |
|||
# throw is made with empty stack as usual |
|||
raise spec |
|||
else: |
|||
raise RuntimeError('Invalid return code') |
|||
|
|||
|
|||
class FOR_IN(OP_CODE): |
|||
_params = ['name', 'body_start_label', 'continue_label', 'break_label'] |
|||
|
|||
def __init__(self, name, body_start_label, continue_label, break_label): |
|||
self.name = name |
|||
self.body_start_label = body_start_label |
|||
self.continue_label = continue_label |
|||
self.break_label = break_label |
|||
|
|||
def eval(self, ctx): |
|||
iterable = ctx.stack.pop() |
|||
if is_null(iterable) or is_undefined(iterable): |
|||
ctx.stack.pop() |
|||
ctx.stack.append(undefined) |
|||
return self.break_label |
|||
|
|||
obj = to_object(iterable, ctx.space) |
|||
|
|||
for e in sorted(obj.own): |
|||
if not obj.own[e]['enumerable']: |
|||
continue |
|||
|
|||
ctx.put( |
|||
self.name, e |
|||
) # JS would have been so much nicer if this was ctx.space.put(self.name, obj.get(e)) |
|||
|
|||
# evaluate the body |
|||
status = ctx.space.exe.execute_fragment_under_context( |
|||
ctx, self.body_start_label, self.break_label) |
|||
|
|||
val, typ, spec = status |
|||
|
|||
if typ != 3: # exception |
|||
ctx.stack.pop() |
|||
|
|||
if typ == 0: # normal |
|||
ctx.stack.append(val) |
|||
continue |
|||
elif typ == 1: # return |
|||
ctx.stack.append(spec) |
|||
return None, None # send return signal |
|||
elif typ == 2: # jump outside |
|||
# now have to figure out whether this is a continue or something else... |
|||
ctx.stack.append(val) |
|||
if spec == self.continue_label: |
|||
# just a continue, perform next iteration as normal |
|||
continue |
|||
return spec # break or smth, go there and finish the iteration |
|||
elif typ == 3: # exception |
|||
# throw is made with empty stack as usual |
|||
raise spec |
|||
else: |
|||
raise RuntimeError('Invalid return code') |
|||
|
|||
return self.break_label |
|||
|
|||
|
|||
# all opcodes... |
|||
OP_CODES = {} |
|||
g = '' |
|||
for g in globals(): |
|||
try: |
|||
if not issubclass(globals()[g], OP_CODE) or g is 'OP_CODE': |
|||
continue |
|||
except: |
|||
continue |
|||
OP_CODES[g] = globals()[g] |
@ -0,0 +1,314 @@ |
|||
from __future__ import unicode_literals |
|||
from simplex import * |
|||
from conversions import * |
|||
|
|||
# ------------------------------------------------------------------------------ |
|||
# Unary operations |
|||
|
|||
|
|||
# -x |
|||
def minus_uop(self): |
|||
return -to_number(self) |
|||
|
|||
|
|||
# +x |
|||
def plus_uop(self): # +u |
|||
return to_number(self) |
|||
|
|||
|
|||
# !x |
|||
def logical_negation_uop(self): # !u cant do 'not u' :( |
|||
return not to_boolean(self) |
|||
|
|||
|
|||
# typeof x |
|||
def typeof_uop(self): |
|||
if is_callable(self): |
|||
return u'function' |
|||
typ = Type(self).lower() |
|||
if typ == u'null': |
|||
typ = u'object' # absolutely idiotic... |
|||
return typ |
|||
|
|||
|
|||
# ~u |
|||
def bit_invert_uop(self): |
|||
return float(to_int32(float(~to_int32(self)))) |
|||
|
|||
|
|||
# void |
|||
def void_op(self): |
|||
return undefined |
|||
|
|||
|
|||
UNARY_OPERATIONS = { |
|||
'+': plus_uop, |
|||
'-': minus_uop, |
|||
'!': logical_negation_uop, |
|||
'~': bit_invert_uop, |
|||
'void': void_op, |
|||
'typeof': |
|||
typeof_uop, # this one only for member expressions! for identifiers its slightly different... |
|||
} |
|||
|
|||
# ------------------------------------------------------------------------------ |
|||
# ----- binary ops ------- |
|||
|
|||
# Bitwise operators |
|||
# <<, >>, &, ^, |, ~ |
|||
|
|||
|
|||
# << |
|||
def bit_lshift_op(self, other): |
|||
lnum = to_int32(self) |
|||
rnum = to_uint32(other) |
|||
shiftCount = rnum & 0x1F |
|||
return float(to_int32(float(lnum << shiftCount))) |
|||
|
|||
|
|||
# >> |
|||
def bit_rshift_op(self, other): |
|||
lnum = to_int32(self) |
|||
rnum = to_uint32(other) |
|||
shiftCount = rnum & 0x1F |
|||
return float(to_int32(float(lnum >> shiftCount))) |
|||
|
|||
|
|||
# >>> |
|||
def bit_bshift_op(self, other): |
|||
lnum = to_uint32(self) |
|||
rnum = to_uint32(other) |
|||
shiftCount = rnum & 0x1F |
|||
return float(to_uint32(float(lnum >> shiftCount))) |
|||
|
|||
|
|||
# & |
|||
def bit_and_op(self, other): |
|||
lnum = to_int32(self) |
|||
rnum = to_int32(other) |
|||
return float(to_int32(float(lnum & rnum))) |
|||
|
|||
|
|||
# ^ |
|||
def bit_xor_op(self, other): |
|||
lnum = to_int32(self) |
|||
rnum = to_int32(other) |
|||
return float(to_int32(float(lnum ^ rnum))) |
|||
|
|||
|
|||
# | |
|||
def bit_or_op(self, other): |
|||
lnum = to_int32(self) |
|||
rnum = to_int32(other) |
|||
return float(to_int32(float(lnum | rnum))) |
|||
|
|||
|
|||
# Additive operators |
|||
# + and - are implemented here |
|||
|
|||
|
|||
# + |
|||
def add_op(self, other): |
|||
if type(self) is float and type(other) is float: |
|||
return self + other |
|||
if type(self) is unicode and type(other) is unicode: |
|||
return self + other |
|||
# standard way... |
|||
a = to_primitive(self) |
|||
b = to_primitive(other) |
|||
if type(a) is unicode or type(b) is unicode: # string wins hehe |
|||
return to_string(a) + to_string(b) |
|||
return to_number(a) + to_number(b) |
|||
|
|||
|
|||
# - |
|||
def sub_op(self, other): |
|||
return to_number(self) - to_number(other) |
|||
|
|||
|
|||
# Multiplicative operators |
|||
# *, / and % are implemented here |
|||
|
|||
|
|||
# * |
|||
def mul_op(self, other): |
|||
return to_number(self) * to_number(other) |
|||
|
|||
|
|||
# / |
|||
def div_op(self, other): |
|||
a = to_number(self) |
|||
b = to_number(other) |
|||
if b: |
|||
return a / float(b) # ensure at least one is a float. |
|||
if not a or a != a: |
|||
return NaN |
|||
return Infinity if a > 0 else -Infinity |
|||
|
|||
|
|||
# % |
|||
def mod_op(self, other): |
|||
a = to_number(self) |
|||
b = to_number(other) |
|||
if abs(a) == Infinity or not b: |
|||
return NaN |
|||
if abs(b) == Infinity: |
|||
return a |
|||
pyres = a % b # different signs in python and javascript |
|||
# python has the same sign as b and js has the same |
|||
# sign as a. |
|||
if a < 0 and pyres > 0: |
|||
pyres -= abs(b) |
|||
elif a > 0 and pyres < 0: |
|||
pyres += abs(b) |
|||
return float(pyres) |
|||
|
|||
|
|||
# Comparisons |
|||
# <, <=, !=, ==, >=, > are implemented here. |
|||
def abstract_relational_comparison(self, other, |
|||
self_first=True): # todo speed up! |
|||
''' self<other if self_first else other<self. |
|||
Returns the result of the question: is self smaller than other? |
|||
in case self_first is false it returns the answer of: |
|||
is other smaller than self. |
|||
result is PyJs type: bool or undefined''' |
|||
|
|||
px = to_primitive(self, 'Number') |
|||
py = to_primitive(other, 'Number') |
|||
if not self_first: # reverse order |
|||
px, py = py, px |
|||
if not (Type(px) == 'String' and Type(py) == 'String'): |
|||
px, py = to_number(px), to_number(py) |
|||
if is_nan(px) or is_nan(py): |
|||
return None # watch out here! |
|||
return px < py # same cmp algorithm |
|||
else: |
|||
# I am pretty sure that python has the same |
|||
# string cmp algorithm but I have to confirm it |
|||
return px < py |
|||
|
|||
|
|||
# < |
|||
def less_op(self, other): |
|||
res = abstract_relational_comparison(self, other, True) |
|||
if res is None: |
|||
return False |
|||
return res |
|||
|
|||
|
|||
# <= |
|||
def less_eq_op(self, other): |
|||
res = abstract_relational_comparison(self, other, False) |
|||
if res is None: |
|||
return False |
|||
return not res |
|||
|
|||
|
|||
# >= |
|||
def greater_eq_op(self, other): |
|||
res = abstract_relational_comparison(self, other, True) |
|||
if res is None: |
|||
return False |
|||
return not res |
|||
|
|||
|
|||
# > |
|||
def greater_op(self, other): |
|||
res = abstract_relational_comparison(self, other, False) |
|||
if res is None: |
|||
return False |
|||
return res |
|||
|
|||
|
|||
# equality |
|||
|
|||
|
|||
def abstract_equality_op(self, other): |
|||
''' returns the result of JS == compare. |
|||
result is PyJs type: bool''' |
|||
tx, ty = Type(self), Type(other) |
|||
if tx == ty: |
|||
if tx == 'Undefined' or tx == 'Null': |
|||
return True |
|||
if tx == 'Number' or tx == 'String' or tx == 'Boolean': |
|||
return self == other |
|||
return self is other # Object |
|||
elif (tx == 'Undefined' and ty == 'Null') or (ty == 'Undefined' |
|||
and tx == 'Null'): |
|||
return True |
|||
elif tx == 'Number' and ty == 'String': |
|||
return abstract_equality_op(self, to_number(other)) |
|||
elif tx == 'String' and ty == 'Number': |
|||
return abstract_equality_op(to_number(self), other) |
|||
elif tx == 'Boolean': |
|||
return abstract_equality_op(to_number(self), other) |
|||
elif ty == 'Boolean': |
|||
return abstract_equality_op(self, to_number(other)) |
|||
elif (tx == 'String' or tx == 'Number') and is_object(other): |
|||
return abstract_equality_op(self, to_primitive(other)) |
|||
elif (ty == 'String' or ty == 'Number') and is_object(self): |
|||
return abstract_equality_op(to_primitive(self), other) |
|||
else: |
|||
return False |
|||
|
|||
|
|||
def abstract_inequality_op(self, other): |
|||
return not abstract_equality_op(self, other) |
|||
|
|||
|
|||
def strict_equality_op(self, other): |
|||
typ = Type(self) |
|||
if typ != Type(other): |
|||
return False |
|||
if typ == 'Undefined' or typ == 'Null': |
|||
return True |
|||
if typ == 'Boolean' or typ == 'String' or typ == 'Number': |
|||
return self == other |
|||
else: # object |
|||
return self is other # Id compare. |
|||
|
|||
|
|||
def strict_inequality_op(self, other): |
|||
return not strict_equality_op(self, other) |
|||
|
|||
|
|||
def instanceof_op(self, other): |
|||
'''checks if self is instance of other''' |
|||
if not hasattr(other, 'has_instance'): |
|||
return False |
|||
return other.has_instance(self) |
|||
|
|||
|
|||
def in_op(self, other): |
|||
'''checks if self is in other''' |
|||
if not is_object(other): |
|||
raise MakeError( |
|||
'TypeError', |
|||
"You can\'t use 'in' operator to search in non-objects") |
|||
return other.has_property(to_string(self)) |
|||
|
|||
|
|||
BINARY_OPERATIONS = { |
|||
'+': add_op, |
|||
'-': sub_op, |
|||
'*': mul_op, |
|||
'/': div_op, |
|||
'%': mod_op, |
|||
'<<': bit_lshift_op, |
|||
'>>': bit_rshift_op, |
|||
'>>>': bit_bshift_op, |
|||
'|': bit_or_op, |
|||
'&': bit_and_op, |
|||
'^': bit_xor_op, |
|||
'==': abstract_equality_op, |
|||
'!=': abstract_inequality_op, |
|||
'===': strict_equality_op, |
|||
'!==': strict_inequality_op, |
|||
'<': less_op, |
|||
'<=': less_eq_op, |
|||
'>': greater_op, |
|||
'>=': greater_eq_op, |
|||
'in': in_op, |
|||
'instanceof': instanceof_op, |
|||
} |
@ -0,0 +1 @@ |
|||
__author__ = 'Piotr Dabkowski' |
@ -0,0 +1,489 @@ |
|||
from __future__ import unicode_literals |
|||
from ..conversions import * |
|||
from ..func_utils import * |
|||
from ..operations import strict_equality_op |
|||
|
|||
import six |
|||
|
|||
if six.PY3: |
|||
xrange = range |
|||
import functools |
|||
|
|||
ARR_STACK = set({}) |
|||
|
|||
|
|||
class ArrayPrototype: |
|||
def toString(this, args): |
|||
arr = to_object(this, args.space) |
|||
func = arr.get('join') |
|||
if not is_callable(func): |
|||
return u'[object %s]' % GetClass(arr) |
|||
return func.call(this, ()) |
|||
|
|||
def toLocaleString(this, args): |
|||
array = to_object(this, args.space) |
|||
arr_len = js_arr_length(array) |
|||
# separator is simply a comma ',' |
|||
if not arr_len: |
|||
return '' |
|||
res = [] |
|||
for i in xrange(arr_len): |
|||
element = array.get(unicode(i)) |
|||
if is_undefined(element) or is_null(element): |
|||
res.append('') |
|||
else: |
|||
cand = to_object(element, args.space) |
|||
str_func = cand.get('toLocaleString') |
|||
if not is_callable(str_func): |
|||
raise MakeError( |
|||
'TypeError', |
|||
'toLocaleString method of item at index %d is not callable' |
|||
% i) |
|||
res.append(to_string(str_func.call(cand, ()))) |
|||
return ','.join(res) |
|||
|
|||
def concat(this, args): |
|||
array = to_object(this, args.space) |
|||
items = [array] |
|||
items.extend(tuple(args)) |
|||
A = [] |
|||
for E in items: |
|||
if GetClass(E) == 'Array': |
|||
k = 0 |
|||
e_len = js_arr_length(E) |
|||
while k < e_len: |
|||
if E.has_property(unicode(k)): |
|||
A.append(E.get(unicode(k))) |
|||
k += 1 |
|||
else: |
|||
A.append(E) |
|||
return args.space.ConstructArray(A) |
|||
|
|||
def join(this, args): |
|||
ARR_STACK.add(this) |
|||
array = to_object(this, args.space) |
|||
separator = get_arg(args, 0) |
|||
arr_len = js_arr_length(array) |
|||
separator = ',' if is_undefined(separator) else to_string(separator) |
|||
elems = [] |
|||
for e in xrange(arr_len): |
|||
elem = array.get(unicode(e)) |
|||
if elem in ARR_STACK: |
|||
s = '' |
|||
else: |
|||
s = to_string(elem) |
|||
elems.append( |
|||
s if not (is_undefined(elem) or is_null(elem)) else '') |
|||
res = separator.join(elems) |
|||
ARR_STACK.remove(this) |
|||
return res |
|||
|
|||
def pop(this, args): #todo check |
|||
array = to_object(this, args.space) |
|||
arr_len = js_arr_length(array) |
|||
if not arr_len: |
|||
array.put('length', float(arr_len)) |
|||
return undefined |
|||
ind = unicode(arr_len - 1) |
|||
element = array.get(ind) |
|||
array.delete(ind) |
|||
array.put('length', float(arr_len - 1)) |
|||
return element |
|||
|
|||
def push(this, args): |
|||
array = to_object(this, args.space) |
|||
arr_len = js_arr_length(array) |
|||
to_put = tuple(args) |
|||
i = arr_len |
|||
for i, e in enumerate(to_put, arr_len): |
|||
array.put(unicode(i), e, True) |
|||
array.put('length', float(arr_len + len(to_put)), True) |
|||
return float(i) |
|||
|
|||
def reverse(this, args): |
|||
array = to_object(this, args.space) |
|||
vals = js_array_to_list(array) |
|||
has_props = [ |
|||
array.has_property(unicode(e)) |
|||
for e in xrange(js_arr_length(array)) |
|||
] |
|||
vals.reverse() |
|||
has_props.reverse() |
|||
for i, val in enumerate(vals): |
|||
if has_props[i]: |
|||
array.put(unicode(i), val) |
|||
else: |
|||
array.delete(unicode(i)) |
|||
return array |
|||
|
|||
def shift(this, args): |
|||
array = to_object(this, args.space) |
|||
arr_len = js_arr_length(array) |
|||
if not arr_len: |
|||
array.put('length', 0.) |
|||
return undefined |
|||
first = array.get('0') |
|||
for k in xrange(1, arr_len): |
|||
from_s, to_s = unicode(k), unicode(k - 1) |
|||
if array.has_property(from_s): |
|||
array.put(to_s, array.get(from_s)) |
|||
else: |
|||
array.delete(to_s) |
|||
array.delete(unicode(arr_len - 1)) |
|||
array.put('length', float(arr_len - 1)) |
|||
return first |
|||
|
|||
def slice(this, args): # todo check |
|||
array = to_object(this, args.space) |
|||
start = get_arg(args, 0) |
|||
end = get_arg(args, 1) |
|||
arr_len = js_arr_length(array) |
|||
relative_start = to_int(start) |
|||
k = max((arr_len + relative_start), 0) if relative_start < 0 else min( |
|||
relative_start, arr_len) |
|||
relative_end = arr_len if is_undefined(end) else to_int(end) |
|||
final = max((arr_len + relative_end), 0) if relative_end < 0 else min( |
|||
relative_end, arr_len) |
|||
res = [] |
|||
n = 0 |
|||
while k < final: |
|||
pk = unicode(k) |
|||
if array.has_property(pk): |
|||
res.append(array.get(pk)) |
|||
k += 1 |
|||
n += 1 |
|||
return args.space.ConstructArray(res) |
|||
|
|||
def sort( |
|||
this, args |
|||
): # todo: this assumes array continous (not sparse) - fix for sparse arrays |
|||
cmpfn = get_arg(args, 0) |
|||
if not GetClass(this) in ('Array', 'Arguments'): |
|||
return to_object(this, args.space) # do nothing |
|||
arr_len = js_arr_length(this) |
|||
if not arr_len: |
|||
return this |
|||
arr = [ |
|||
(this.get(unicode(e)) if this.has_property(unicode(e)) else None) |
|||
for e in xrange(arr_len) |
|||
] |
|||
if not is_callable(cmpfn): |
|||
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(arr_len): |
|||
if arr[i] is None: |
|||
this.delete(unicode(i)) |
|||
else: |
|||
this.put(unicode(i), arr[i]) |
|||
return this |
|||
|
|||
def splice(this, args): |
|||
# 1-8 |
|||
array = to_object(this, args.space) |
|||
start = get_arg(args, 0) |
|||
deleteCount = get_arg(args, 1) |
|||
arr_len = js_arr_length(this) |
|||
relative_start = to_int(start) |
|||
actual_start = max( |
|||
(arr_len + relative_start), 0) if relative_start < 0 else min( |
|||
relative_start, arr_len) |
|||
actual_delete_count = min( |
|||
max(to_int(deleteCount), 0), arr_len - actual_start) |
|||
k = 0 |
|||
A = args.space.NewArray(0) |
|||
# 9 |
|||
while k < actual_delete_count: |
|||
if array.has_property(unicode(actual_start + k)): |
|||
A.put(unicode(k), array.get(unicode(actual_start + k))) |
|||
k += 1 |
|||
# 10-11 |
|||
items = list(args)[2:] |
|||
items_len = len(items) |
|||
# 12 |
|||
if items_len < actual_delete_count: |
|||
k = actual_start |
|||
while k < (arr_len - actual_delete_count): |
|||
fr = unicode(k + actual_delete_count) |
|||
to = unicode(k + items_len) |
|||
if array.has_property(fr): |
|||
array.put(to, array.get(fr)) |
|||
else: |
|||
array.delete(to) |
|||
k += 1 |
|||
k = arr_len |
|||
while k > (arr_len - actual_delete_count + items_len): |
|||
array.delete(unicode(k - 1)) |
|||
k -= 1 |
|||
# 13 |
|||
elif items_len > actual_delete_count: |
|||
k = arr_len - actual_delete_count |
|||
while k > actual_start: |
|||
fr = unicode(k + actual_delete_count - 1) |
|||
to = unicode(k + items_len - 1) |
|||
if array.has_property(fr): |
|||
array.put(to, array.get(fr)) |
|||
else: |
|||
array.delete(to) |
|||
k -= 1 |
|||
# 14-17 |
|||
k = actual_start |
|||
while items: |
|||
E = items.pop(0) |
|||
array.put(unicode(k), E) |
|||
k += 1 |
|||
array.put('length', float(arr_len - actual_delete_count + items_len)) |
|||
return A |
|||
|
|||
def unshift(this, args): |
|||
array = to_object(this, args.space) |
|||
arr_len = js_arr_length(array) |
|||
argCount = len(args) |
|||
k = arr_len |
|||
while k > 0: |
|||
fr = unicode(k - 1) |
|||
to = unicode(k + argCount - 1) |
|||
if array.has_property(fr): |
|||
array.put(to, array.get(fr)) |
|||
else: |
|||
array.delete(to) |
|||
k -= 1 |
|||
items = tuple(args) |
|||
for j, e in enumerate(items): |
|||
array.put(unicode(j), e) |
|||
array.put('length', float(arr_len + argCount)) |
|||
return float(arr_len + argCount) |
|||
|
|||
def indexOf(this, args): |
|||
array = to_object(this, args.space) |
|||
searchElement = get_arg(args, 0) |
|||
arr_len = js_arr_length(array) |
|||
if arr_len == 0: |
|||
return -1. |
|||
if len(args) > 1: |
|||
n = to_int(args[1]) |
|||
else: |
|||
n = 0 |
|||
if n >= arr_len: |
|||
return -1. |
|||
if n >= 0: |
|||
k = n |
|||
else: |
|||
k = arr_len - abs(n) |
|||
if k < 0: |
|||
k = 0 |
|||
while k < arr_len: |
|||
if array.has_property(unicode(k)): |
|||
elementK = array.get(unicode(k)) |
|||
if strict_equality_op(searchElement, elementK): |
|||
return float(k) |
|||
k += 1 |
|||
return -1. |
|||
|
|||
def lastIndexOf(this, args): |
|||
array = to_object(this, args.space) |
|||
searchElement = get_arg(args, 0) |
|||
arr_len = js_arr_length(array) |
|||
if arr_len == 0: |
|||
return -1. |
|||
if len(args) > 1: |
|||
n = to_int(args[1]) |
|||
else: |
|||
n = arr_len - 1 |
|||
if n >= 0: |
|||
k = min(n, arr_len - 1) |
|||
else: |
|||
k = arr_len - abs(n) |
|||
while k >= 0: |
|||
if array.has_property(unicode(k)): |
|||
elementK = array.get(unicode(k)) |
|||
if strict_equality_op(searchElement, elementK): |
|||
return float(k) |
|||
k -= 1 |
|||
return -1. |
|||
|
|||
def every(this, args): |
|||
array = to_object(this, args.space) |
|||
callbackfn = get_arg(args, 0) |
|||
arr_len = js_arr_length(array) |
|||
if not is_callable(callbackfn): |
|||
raise MakeError('TypeError', 'callbackfn must be a function') |
|||
T = get_arg(args, 1) |
|||
k = 0 |
|||
while k < arr_len: |
|||
if array.has_property(unicode(k)): |
|||
kValue = array.get(unicode(k)) |
|||
if not to_boolean( |
|||
callbackfn.call(T, (kValue, float(k), array))): |
|||
return False |
|||
k += 1 |
|||
return True |
|||
|
|||
def some(this, args): |
|||
array = to_object(this, args.space) |
|||
callbackfn = get_arg(args, 0) |
|||
arr_len = js_arr_length(array) |
|||
if not is_callable(callbackfn): |
|||
raise MakeError('TypeError', 'callbackfn must be a function') |
|||
T = get_arg(args, 1) |
|||
k = 0 |
|||
while k < arr_len: |
|||
if array.has_property(unicode(k)): |
|||
kValue = array.get(unicode(k)) |
|||
if to_boolean(callbackfn.call(T, (kValue, float(k), array))): |
|||
return True |
|||
k += 1 |
|||
return False |
|||
|
|||
def forEach(this, args): |
|||
array = to_object(this, args.space) |
|||
callbackfn = get_arg(args, 0) |
|||
arr_len = js_arr_length(array) |
|||
if not is_callable(callbackfn): |
|||
raise MakeError('TypeError', 'callbackfn must be a function') |
|||
_this = get_arg(args, 1) |
|||
k = 0 |
|||
while k < arr_len: |
|||
sk = unicode(k) |
|||
if array.has_property(sk): |
|||
kValue = array.get(sk) |
|||
callbackfn.call(_this, (kValue, float(k), array)) |
|||
k += 1 |
|||
return undefined |
|||
|
|||
def map(this, args): |
|||
array = to_object(this, args.space) |
|||
callbackfn = get_arg(args, 0) |
|||
arr_len = js_arr_length(array) |
|||
if not is_callable(callbackfn): |
|||
raise MakeError('TypeError', 'callbackfn must be a function') |
|||
_this = get_arg(args, 1) |
|||
k = 0 |
|||
A = args.space.NewArray(0) |
|||
while k < arr_len: |
|||
Pk = unicode(k) |
|||
if array.has_property(Pk): |
|||
kValue = array.get(Pk) |
|||
mappedValue = callbackfn.call(_this, (kValue, float(k), array)) |
|||
A.define_own_property( |
|||
Pk, { |
|||
'value': mappedValue, |
|||
'writable': True, |
|||
'enumerable': True, |
|||
'configurable': True |
|||
}, False) |
|||
k += 1 |
|||
return A |
|||
|
|||
def filter(this, args): |
|||
array = to_object(this, args.space) |
|||
callbackfn = get_arg(args, 0) |
|||
arr_len = js_arr_length(array) |
|||
if not is_callable(callbackfn): |
|||
raise MakeError('TypeError', 'callbackfn must be a function') |
|||
_this = get_arg(args, 1) |
|||
k = 0 |
|||
res = [] |
|||
while k < arr_len: |
|||
if array.has_property(unicode(k)): |
|||
kValue = array.get(unicode(k)) |
|||
if to_boolean( |
|||
callbackfn.call(_this, (kValue, float(k), array))): |
|||
res.append(kValue) |
|||
k += 1 |
|||
return args.space.ConstructArray(res) |
|||
|
|||
def reduce(this, args): |
|||
array = to_object(this, args.space) |
|||
callbackfn = get_arg(args, 0) |
|||
arr_len = js_arr_length(array) |
|||
if not is_callable(callbackfn): |
|||
raise MakeError('TypeError', 'callbackfn must be a function') |
|||
if not arr_len and len(args) < 2: |
|||
raise MakeError('TypeError', |
|||
'Reduce of empty array with no initial value') |
|||
k = 0 |
|||
accumulator = undefined |
|||
if len(args) > 1: # initial value present |
|||
accumulator = args[1] |
|||
else: |
|||
kPresent = False |
|||
while not kPresent and k < arr_len: |
|||
kPresent = array.has_property(unicode(k)) |
|||
if kPresent: |
|||
accumulator = array.get(unicode(k)) |
|||
k += 1 |
|||
if not kPresent: |
|||
raise MakeError('TypeError', |
|||
'Reduce of empty array with no initial value') |
|||
while k < arr_len: |
|||
if array.has_property(unicode(k)): |
|||
kValue = array.get(unicode(k)) |
|||
accumulator = callbackfn.call( |
|||
undefined, (accumulator, kValue, float(k), array)) |
|||
k += 1 |
|||
return accumulator |
|||
|
|||
def reduceRight(this, args): |
|||
array = to_object(this, args.space) |
|||
callbackfn = get_arg(args, 0) |
|||
arr_len = js_arr_length(array) |
|||
if not is_callable(callbackfn): |
|||
raise MakeError('TypeError', 'callbackfn must be a function') |
|||
if not arr_len and len(args) < 2: |
|||
raise MakeError('TypeError', |
|||
'Reduce of empty array with no initial value') |
|||
k = arr_len - 1 |
|||
accumulator = undefined |
|||
|
|||
if len(args) > 1: # initial value present |
|||
accumulator = args[1] |
|||
else: |
|||
kPresent = False |
|||
while not kPresent and k >= 0: |
|||
kPresent = array.has_property(unicode(k)) |
|||
if kPresent: |
|||
accumulator = array.get(unicode(k)) |
|||
k -= 1 |
|||
if not kPresent: |
|||
raise MakeError('TypeError', |
|||
'Reduce of empty array with no initial value') |
|||
while k >= 0: |
|||
if array.has_property(unicode(k)): |
|||
kValue = array.get(unicode(k)) |
|||
accumulator = callbackfn.call( |
|||
undefined, (accumulator, kValue, float(k), array)) |
|||
k -= 1 |
|||
return accumulator |
|||
|
|||
|
|||
def sort_compare(a, b, comp): |
|||
if a is None: |
|||
if b is None: |
|||
return 0 |
|||
return 1 |
|||
if b is None: |
|||
if a is None: |
|||
return 0 |
|||
return -1 |
|||
if is_undefined(a): |
|||
if is_undefined(b): |
|||
return 0 |
|||
return 1 |
|||
if is_undefined(b): |
|||
if is_undefined(a): |
|||
return 0 |
|||
return -1 |
|||
if comp is not None: |
|||
res = comp.call(undefined, (a, b)) |
|||
return to_int(res) |
|||
x, y = to_string(a), to_string(b) |
|||
if x < y: |
|||
return -1 |
|||
elif x > y: |
|||
return 1 |
|||
return 0 |
@ -0,0 +1,22 @@ |
|||
from __future__ import unicode_literals |
|||
|
|||
from ..conversions import * |
|||
from ..func_utils import * |
|||
|
|||
|
|||
class BooleanPrototype: |
|||
def toString(this, args): |
|||
if GetClass(this) != 'Boolean': |
|||
raise MakeError('TypeError', |
|||
'Boolean.prototype.toString is not generic') |
|||
if is_object(this): |
|||
this = this.value |
|||
return u'true' if this else u'false' |
|||
|
|||
def valueOf(this, args): |
|||
if GetClass(this) != 'Boolean': |
|||
raise MakeError('TypeError', |
|||
'Boolean.prototype.valueOf is not generic') |
|||
if is_object(this): |
|||
this = this.value |
|||
return this |
@ -0,0 +1,15 @@ |
|||
from __future__ import unicode_literals |
|||
from ..conversions import * |
|||
from ..func_utils import * |
|||
|
|||
|
|||
class ErrorPrototype: |
|||
def toString(this, args): |
|||
if Type(this) != 'Object': |
|||
raise MakeError('TypeError', |
|||
'Error.prototype.toString called on non-object') |
|||
name = this.get('name') |
|||
name = u'Error' if is_undefined(name) else to_string(name) |
|||
msg = this.get('message') |
|||
msg = '' if is_undefined(msg) else to_string(msg) |
|||
return name + (name and msg and ': ') + msg |
@ -0,0 +1,61 @@ |
|||
from __future__ import unicode_literals |
|||
|
|||
from ..conversions import * |
|||
from ..func_utils import * |
|||
|
|||
# python 3 support |
|||
import six |
|||
if six.PY3: |
|||
basestring = str |
|||
long = int |
|||
xrange = range |
|||
unicode = str |
|||
|
|||
# todo fix apply and bind |
|||
|
|||
|
|||
class FunctionPrototype: |
|||
def toString(this, args): |
|||
if not is_callable(this): |
|||
raise MakeError('TypeError', |
|||
'Function.prototype.toString is not generic') |
|||
|
|||
args = u', '.join(map(unicode, this.params)) |
|||
return u'function %s(%s) { [native code] }' % (this.name if this.name |
|||
else u'', args) |
|||
|
|||
def call(this, args): |
|||
if not is_callable(this): |
|||
raise MakeError('TypeError', |
|||
'Function.prototype.call is not generic') |
|||
_this = get_arg(args, 0) |
|||
_args = tuple(args)[1:] |
|||
return this.call(_this, _args) |
|||
|
|||
def apply(this, args): |
|||
if not is_callable(this): |
|||
raise MakeError('TypeError', |
|||
'Function.prototype.apply is not generic') |
|||
_args = get_arg(args, 1) |
|||
if not is_object(_args): |
|||
raise MakeError( |
|||
'TypeError', |
|||
'argList argument to Function.prototype.apply must an Object') |
|||
_this = get_arg(args, 0) |
|||
return this.call(_this, js_array_to_tuple(_args)) |
|||
|
|||
def bind(this, args): |
|||
if not is_callable(this): |
|||
raise MakeError('TypeError', |
|||
'Function.prototype.bind is not generic') |
|||
bound_this = get_arg(args, 0) |
|||
bound_args = tuple(args)[1:] |
|||
|
|||
def bound(dummy_this, extra_args): |
|||
return this.call(bound_this, bound_args + tuple(extra_args)) |
|||
|
|||
js_bound = args.space.NewFunction(bound, this.ctx, (), u'', False, ()) |
|||
js_bound.put(u'length', |
|||
float(max(len(this.params) - len(bound_args), 0.))) |
|||
js_bound.put(u'name', u'boundFunc') |
|||
return js_bound |
@ -0,0 +1,205 @@ |
|||
from __future__ import unicode_literals |
|||
from ..conversions import * |
|||
from ..func_utils import * |
|||
from ..operations import strict_equality_op |
|||
import json |
|||
|
|||
indent = '' |
|||
# python 3 support |
|||
import six |
|||
if six.PY3: |
|||
basestring = str |
|||
long = int |
|||
xrange = range |
|||
unicode = str |
|||
|
|||
|
|||
def parse(this, args): |
|||
text, reviver = get_arg(args, 0), get_arg(args, 1) |
|||
s = to_string(text) |
|||
try: |
|||
unfiltered = json.loads(s) |
|||
except: |
|||
raise MakeError( |
|||
'SyntaxError', |
|||
'JSON.parse could not parse JSON string - Invalid syntax') |
|||
unfiltered = to_js(unfiltered, args.space) |
|||
if is_callable(reviver): |
|||
root = args.space.ConstructObject({'': unfiltered}) |
|||
return walk(root, '', reviver) |
|||
else: |
|||
return unfiltered |
|||
|
|||
|
|||
def stringify(this, args): |
|||
global indent |
|||
value, replacer, space = get_arg(args, 0), get_arg(args, 1), get_arg( |
|||
args, 2) |
|||
stack = set([]) |
|||
indent = '' |
|||
property_list = replacer_function = undefined |
|||
if is_object(replacer): |
|||
if is_callable(replacer): |
|||
replacer_function = replacer |
|||
elif replacer.Class == 'Array': |
|||
property_list = [] |
|||
for e in replacer: |
|||
v = replacer[e] |
|||
item = undefined |
|||
typ = Type(v) |
|||
if typ == 'Number': |
|||
item = to_string(v) |
|||
elif typ == 'String': |
|||
item = v |
|||
elif typ == 'Object': |
|||
if GetClass(v) in ('String', 'Number'): |
|||
item = to_string(v) |
|||
if not is_undefined(item) and item not in property_list: |
|||
property_list.append(item) |
|||
if is_object(space): |
|||
if GetClass(space) == 'Number': |
|||
space = to_number(space) |
|||
elif GetClass(space) == 'String': |
|||
space = to_string(space) |
|||
if Type(space) == 'Number': |
|||
space = min(10, to_int(space)) |
|||
gap = max(int(space), 0) * ' ' |
|||
elif Type(space) == 'String': |
|||
gap = space[:10] |
|||
else: |
|||
gap = '' |
|||
return Str('', args.space.ConstructObject({ |
|||
'': value |
|||
}), replacer_function, property_list, gap, stack, space) |
|||
|
|||
|
|||
def Str(key, holder, replacer_function, property_list, gap, stack, space): |
|||
value = holder.get(key) |
|||
if is_object(value): |
|||
to_json = value.get('toJSON') |
|||
if is_callable(to_json): |
|||
value = to_json.call(value, (key, )) |
|||
if not is_undefined(replacer_function): |
|||
value = replacer_function.call(holder, (key, value)) |
|||
|
|||
if is_object(value): |
|||
if value.Class == 'String': |
|||
value = to_string(value) |
|||
elif value.Class == 'Number': |
|||
value = to_number(value) |
|||
elif value.Class == 'Boolean': |
|||
value = to_boolean(value) |
|||
typ = Type(value) |
|||
if is_null(value): |
|||
return 'null' |
|||
elif typ == 'Boolean': |
|||
return 'true' if value else 'false' |
|||
elif typ == 'String': |
|||
return Quote(value) |
|||
elif typ == 'Number': |
|||
if not is_infinity(value): |
|||
return to_string(value) |
|||
return 'null' |
|||
if is_object(value) and not is_callable(value): |
|||
if value.Class == 'Array': |
|||
return ja(value, stack, gap, property_list, replacer_function, |
|||
space) |
|||
else: |
|||
return jo(value, stack, gap, property_list, replacer_function, |
|||
space) |
|||
return undefined |
|||
|
|||
|
|||
def jo(value, stack, gap, property_list, replacer_function, space): |
|||
global indent |
|||
if value in stack: |
|||
raise MakeError('TypeError', 'Converting circular structure to JSON') |
|||
stack.add(value) |
|||
stepback = indent |
|||
indent += gap |
|||
if not is_undefined(property_list): |
|||
k = property_list |
|||
else: |
|||
k = [unicode(e) for e, d in value.own.items() if d.get('enumerable')] |
|||
partial = [] |
|||
for p in k: |
|||
str_p = Str(p, value, replacer_function, property_list, gap, stack, |
|||
space) |
|||
if not is_undefined(str_p): |
|||
member = json.dumps(p) + ':' + ( |
|||
' ' if gap else |
|||
'') + str_p # todo not sure here - what space character? |
|||
partial.append(member) |
|||
if not partial: |
|||
final = '{}' |
|||
else: |
|||
if not gap: |
|||
final = '{%s}' % ','.join(partial) |
|||
else: |
|||
sep = ',\n' + indent |
|||
properties = sep.join(partial) |
|||
final = '{\n' + indent + properties + '\n' + stepback + '}' |
|||
stack.remove(value) |
|||
indent = stepback |
|||
return final |
|||
|
|||
|
|||
def ja(value, stack, gap, property_list, replacer_function, space): |
|||
global indent |
|||
if value in stack: |
|||
raise MakeError('TypeError', 'Converting circular structure to JSON') |
|||
stack.add(value) |
|||
stepback = indent |
|||
indent += gap |
|||
partial = [] |
|||
length = js_arr_length(value) |
|||
for index in xrange(length): |
|||
index = unicode(index) |
|||
str_index = Str(index, value, replacer_function, property_list, gap, |
|||
stack, space) |
|||
if is_undefined(str_index): |
|||
partial.append('null') |
|||
else: |
|||
partial.append(str_index) |
|||
if not partial: |
|||
final = '[]' |
|||
else: |
|||
if not gap: |
|||
final = '[%s]' % ','.join(partial) |
|||
else: |
|||
sep = ',\n' + indent |
|||
properties = sep.join(partial) |
|||
final = '[\n' + indent + properties + '\n' + stepback + ']' |
|||
stack.remove(value) |
|||
indent = stepback |
|||
return final |
|||
|
|||
|
|||
def Quote(string): |
|||
return json.dumps(string) |
|||
|
|||
|
|||
def to_js(d, _args_space): |
|||
return convert_to_js_type(d, _args_space) |
|||
|
|||
|
|||
def walk(holder, name, reviver): |
|||
val = holder.get(name) |
|||
if GetClass(val) == 'Array': |
|||
for i in xrange(js_arr_length(val)): |
|||
i = unicode(i) |
|||
new_element = walk(val, i, reviver) |
|||
if is_undefined(new_element): |
|||
val.delete(i) |
|||
else: |
|||
new_element.put(i, new_element) |
|||
elif is_object(val): |
|||
for key in [ |
|||
unicode(e) for e, d in val.own.items() if d.get('enumerable') |
|||
]: |
|||
new_element = walk(val, key, reviver) |
|||
if is_undefined(new_element): |
|||
val.delete(key) |
|||
else: |
|||
val.put(key, new_element) |
|||
return reviver.call(holder, (name, val)) |
@ -0,0 +1,163 @@ |
|||
from __future__ import unicode_literals |
|||
from ..conversions import * |
|||
from ..func_utils import * |
|||
|
|||
import six |
|||
if six.PY3: |
|||
basestring = str |
|||
long = int |
|||
xrange = range |
|||
unicode = str |
|||
|
|||
RADIX_SYMBOLS = { |
|||
0: '0', |
|||
1: '1', |
|||
2: '2', |
|||
3: '3', |
|||
4: '4', |
|||
5: '5', |
|||
6: '6', |
|||
7: '7', |
|||
8: '8', |
|||
9: '9', |
|||
10: 'a', |
|||
11: 'b', |
|||
12: 'c', |
|||
13: 'd', |
|||
14: 'e', |
|||
15: 'f', |
|||
16: 'g', |
|||
17: 'h', |
|||
18: 'i', |
|||
19: 'j', |
|||
20: 'k', |
|||
21: 'l', |
|||
22: 'm', |
|||
23: 'n', |
|||
24: 'o', |
|||
25: 'p', |
|||
26: 'q', |
|||
27: 'r', |
|||
28: 's', |
|||
29: 't', |
|||
30: 'u', |
|||
31: 'v', |
|||
32: 'w', |
|||
33: 'x', |
|||
34: 'y', |
|||
35: 'z' |
|||
} |
|||
|
|||
|
|||
def to_str_rep(num): |
|||
if is_nan(num): |
|||
return 'NaN' |
|||
elif is_infinity(num): |
|||
sign = '-' if num < 0 else '' |
|||
return sign + 'Infinity' |
|||
elif int(num) == num: # dont print .0 |
|||
return unicode(int(num)) |
|||
return unicode(num) # todo: Make it 100% consistent with Node |
|||
|
|||
|
|||
class NumberPrototype: |
|||
def toString(this, args): |
|||
if GetClass(this) != 'Number': |
|||
raise MakeError('TypeError', |
|||
'Number.prototype.valueOf is not generic') |
|||
if type(this) != float: |
|||
this = this.value |
|||
radix = get_arg(args, 0) |
|||
if is_undefined(radix): |
|||
return to_str_rep(this) |
|||
r = to_int(radix) |
|||
if r == 10: |
|||
return to_str_rep(this) |
|||
if r not in xrange(2, 37) or radix != r: |
|||
raise MakeError( |
|||
'RangeError', |
|||
'Number.prototype.toString() radix argument must be an integer between 2 and 36' |
|||
) |
|||
num = to_int(this) |
|||
if num < 0: |
|||
num = -num |
|||
sign = '-' |
|||
else: |
|||
sign = '' |
|||
res = '' |
|||
while num: |
|||
s = RADIX_SYMBOLS[num % r] |
|||
num = num // r |
|||
res = s + res |
|||
return sign + (res if res else '0') |
|||
|
|||
def valueOf(this, args): |
|||
if GetClass(this) != 'Number': |
|||
raise MakeError('TypeError', |
|||
'Number.prototype.valueOf is not generic') |
|||
if type(this) != float: |
|||
this = this.value |
|||
return this |
|||
|
|||
def toFixed(this, args): |
|||
if GetClass(this) != 'Number': |
|||
raise MakeError( |
|||
'TypeError', |
|||
'Number.prototype.toFixed called on incompatible receiver') |
|||
if type(this) != float: |
|||
this = this.value |
|||
fractionDigits = get_arg(args, 0) |
|||
digs = to_int(fractionDigits) |
|||
if digs < 0 or digs > 20: |
|||
raise MakeError( |
|||
'RangeError', |
|||
'toFixed() digits argument must be between 0 and 20') |
|||
elif is_infinity(this): |
|||
return 'Infinity' if this > 0 else '-Infinity' |
|||
elif is_nan(this): |
|||
return 'NaN' |
|||
return format(this, '-.%df' % digs) |
|||
|
|||
def toExponential(this, args): |
|||
if GetClass(this) != 'Number': |
|||
raise MakeError( |
|||
'TypeError', |
|||
'Number.prototype.toExponential called on incompatible receiver' |
|||
) |
|||
if type(this) != float: |
|||
this = this.value |
|||
fractionDigits = get_arg(args, 0) |
|||
digs = to_int(fractionDigits) |
|||
if digs < 0 or digs > 20: |
|||
raise MakeError( |
|||
'RangeError', |
|||
'toFixed() digits argument must be between 0 and 20') |
|||
elif is_infinity(this): |
|||
return 'Infinity' if this > 0 else '-Infinity' |
|||
elif is_nan(this): |
|||
return 'NaN' |
|||
return format(this, '-.%de' % digs) |
|||
|
|||
def toPrecision(this, args): |
|||
if GetClass(this) != 'Number': |
|||
raise MakeError( |
|||
'TypeError', |
|||
'Number.prototype.toPrecision called on incompatible receiver') |
|||
if type(this) != float: |
|||
this = this.value |
|||
precision = get_arg(args, 0) |
|||
if is_undefined(precision): |
|||
return to_string(this) |
|||
prec = to_int(precision) |
|||
if is_nan(this): |
|||
return 'NaN' |
|||
elif is_infinity(this): |
|||
return 'Infinity' if this > 0 else '-Infinity' |
|||
digs = prec - len(str(int(this))) |
|||
if digs >= 0: |
|||
return format(this, '-.%df' % digs) |
|||
else: |
|||
return format(this, '-.%df' % (prec - 1)) |
|||
|
|||
|
|||
NumberPrototype.toLocaleString = NumberPrototype.toString |
@ -0,0 +1,48 @@ |
|||
from __future__ import unicode_literals |
|||
from ..conversions import * |
|||
from ..func_utils import * |
|||
|
|||
|
|||
class ObjectPrototype: |
|||
def toString(this, args): |
|||
if type(this) == UNDEFINED_TYPE: |
|||
return u'[object Undefined]' |
|||
elif type(this) == NULL_TYPE: |
|||
return u'[object Null]' |
|||
return u'[object %s]' % GetClass(to_object(this, args.space)) |
|||
|
|||
def valueOf(this, args): |
|||
return to_object(this, args.space) |
|||
|
|||
def toLocaleString(this, args): |
|||
o = to_object(this, args.space) |
|||
toString = o.get(u'toString') |
|||
if not is_callable(toString): |
|||
raise MakeError('TypeError', 'toString of this is not callcable') |
|||
else: |
|||
return toString.call(this, args) |
|||
|
|||
def hasOwnProperty(this, args): |
|||
prop = get_arg(args, 0) |
|||
o = to_object(this, args.space) |
|||
return o.get_own_property(to_string(prop)) is not None |
|||
|
|||
def isPrototypeOf(this, args): |
|||
# a bit stupid specification because of object conversion that will cause invalid values for primitives |
|||
# for example Object.prototype.isPrototypeOf.call((5).__proto__, 5) gives false |
|||
obj = get_arg(args, 0) |
|||
if not is_object(obj): |
|||
return False |
|||
o = to_object(this, args.space) |
|||
while 1: |
|||
obj = obj.prototype |
|||
if obj is None or is_null(obj): |
|||
return False |
|||
if obj is o: |
|||
return True |
|||
|
|||
def propertyIsEnumerable(this, args): |
|||
prop = get_arg(args, 0) |
|||
o = to_object(this, args.space) |
|||
cand = o.own.get(to_string(prop)) |
|||
return cand is not None and cand.get('enumerable') |
@ -0,0 +1,56 @@ |
|||
from __future__ import unicode_literals |
|||
|
|||
from ..conversions import * |
|||
from ..func_utils import * |
|||
|
|||
|
|||
class RegExpPrototype: |
|||
def toString(this, args): |
|||
flags = u'' |
|||
try: |
|||
if this.glob: |
|||
flags += u'g' |
|||
if this.ignore_case: |
|||
flags += u'i' |
|||
if this.multiline: |
|||
flags += u'm' |
|||
except: |
|||
pass |
|||
try: |
|||
v = this.value if this.value else u'(?:)' |
|||
except: |
|||
v = u'(?:)' |
|||
return u'/%s/' % v + flags |
|||
|
|||
def test(this, args): |
|||
string = get_arg(args, 0) |
|||
return RegExpExec(this, string, args.space) is not null |
|||
|
|||
def _exec( |
|||
this, args |
|||
): # will be changed to exec in base.py. cant name it exec here... |
|||
string = get_arg(args, 0) |
|||
return RegExpExec(this, string, args.space) |
|||
|
|||
|
|||
def RegExpExec(this, string, space): |
|||
if GetClass(this) != 'RegExp': |
|||
raise MakeError('TypeError', 'RegExp.prototype.exec is not generic!') |
|||
string = to_string(string) |
|||
length = len(string) |
|||
i = to_int(this.get('lastIndex')) if this.glob else 0 |
|||
matched = False |
|||
while not matched: |
|||
if i < 0 or i > length: |
|||
this.put('lastIndex', 0.) |
|||
return null |
|||
matched = this.match(string, i) |
|||
i += 1 |
|||
start, end = matched.span() #[0]+i-1, matched.span()[1]+i-1 |
|||
if this.glob: |
|||
this.put('lastIndex', float(end)) |
|||
arr = convert_to_js_type( |
|||
[matched.group()] + list(matched.groups()), space=space) |
|||
arr.put('index', float(start)) |
|||
arr.put('input', unicode(string)) |
|||
return arr |
@ -0,0 +1,323 @@ |
|||
from __future__ import unicode_literals |
|||
|
|||
# -*- coding: utf-8 -*- |
|||
import re |
|||
from ..conversions import * |
|||
from ..func_utils import * |
|||
from jsregexp import RegExpExec |
|||
|
|||
DIGS = set(u'0123456789') |
|||
WHITE = u"\u0009\u000A\u000B\u000C\u000D\u0020\u00A0\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF" |
|||
|
|||
|
|||
def replacement_template(rep, source, span, npar): |
|||
"""Takes the replacement template and some info about the match and returns filled template |
|||
""" |
|||
n = 0 |
|||
res = '' |
|||
while n < len(rep) - 1: |
|||
char = rep[n] |
|||
if char == '$': |
|||
if rep[n + 1] == '$': |
|||
res += '$' |
|||
n += 2 |
|||
continue |
|||
elif rep[n + 1] == '`': |
|||
# replace with string that is BEFORE match |
|||
res += source[:span[0]] |
|||
n += 2 |
|||
continue |
|||
elif rep[n + 1] == '\'': |
|||
# replace with string that is AFTER match |
|||
res += source[span[1]:] |
|||
n += 2 |
|||
continue |
|||
elif rep[n + 1] in DIGS: |
|||
dig = rep[n + 1] |
|||
if n + 2 < len(rep) and rep[n + 2] in DIGS: |
|||
dig += rep[n + 2] |
|||
num = int(dig) |
|||
# we will not do any replacements if we dont have this npar or dig is 0 |
|||
if not num or num > len(npar): |
|||
res += '$' + dig |
|||
else: |
|||
# None - undefined has to be replaced with '' |
|||
res += npar[num - 1] if npar[num - 1] else '' |
|||
n += 1 + len(dig) |
|||
continue |
|||
res += char |
|||
n += 1 |
|||
if n < len(rep): |
|||
res += rep[-1] |
|||
return res |
|||
|
|||
|
|||
################################################### |
|||
|
|||
|
|||
class StringPrototype: |
|||
def toString(this, args): |
|||
if GetClass(this) != 'String': |
|||
raise MakeError('TypeError', |
|||
'String.prototype.toString is not generic') |
|||
if type(this) == unicode: |
|||
return this |
|||
assert type(this.value) == unicode |
|||
return this.value |
|||
|
|||
def valueOf(this, args): |
|||
if GetClass(this) != 'String': |
|||
raise MakeError('TypeError', |
|||
'String.prototype.valueOf is not generic') |
|||
if type(this) == unicode: |
|||
return this |
|||
assert type(this.value) == unicode |
|||
return this.value |
|||
|
|||
def charAt(this, args): |
|||
cok(this) |
|||
pos = to_int(get_arg(args, 0)) |
|||
s = to_string(this) |
|||
if 0 <= pos < len(s): |
|||
return s[pos] |
|||
return u'' |
|||
|
|||
def charCodeAt(this, args): |
|||
cok(this) |
|||
pos = to_int(get_arg(args, 0)) |
|||
s = to_string(this) |
|||
if 0 <= pos < len(s): |
|||
return float(ord(s[pos])) |
|||
return NaN |
|||
|
|||
def concat(this, args): |
|||
cok(this) |
|||
return to_string(this) + u''.join(map(to_string, args)) |
|||
|
|||
def indexOf(this, args): |
|||
cok(this) |
|||
search = to_string(get_arg(args, 0)) |
|||
pos = to_int(get_arg(args, 1)) |
|||
s = to_string(this) |
|||
return float(s.find(search, min(max(pos, 0), len(s)))) |
|||
|
|||
def lastIndexOf(this, args): |
|||
cok(this) |
|||
search = to_string(get_arg(args, 0)) |
|||
pos = get_arg(args, 1) |
|||
s = to_string(this) |
|||
pos = 10**12 if is_nan(pos) else to_int(pos) |
|||
return float(s.rfind(search, 0, min(max(pos, 0) + 1, len(s)))) |
|||
|
|||
def localeCompare(this, args): |
|||
cok(this) |
|||
s = to_string(this) |
|||
that = to_string(get_arg(args, 0)) |
|||
if s < that: |
|||
return -1. |
|||
elif s > that: |
|||
return 1. |
|||
return 0. |
|||
|
|||
def match(this, args): |
|||
cok(this) |
|||
s = to_string(this) |
|||
regexp = get_arg(args, 0) |
|||
r = args.space.NewRegExp( |
|||
regexp, '') if GetClass(regexp) != 'RegExp' else regexp |
|||
if not r.glob: |
|||
return RegExpExec(r, s, space=args.space) |
|||
r.put('lastIndex', float(0)) |
|||
found = [] |
|||
previous_last_index = 0 |
|||
last_match = True |
|||
while last_match: |
|||
result = RegExpExec(r, s, space=args.space) |
|||
if is_null(result): |
|||
last_match = False |
|||
else: |
|||
this_index = r.get('lastIndex') |
|||
if this_index == previous_last_index: |
|||
r.put('lastIndex', float(this_index + 1)) |
|||
previous_last_index += 1 |
|||
else: |
|||
previous_last_index = this_index |
|||
matchStr = result.get('0') |
|||
found.append(matchStr) |
|||
if not found: |
|||
return null |
|||
return args.space.ConstructArray(found) |
|||
|
|||
def replace(this, args): |
|||
# VERY COMPLICATED. to check again. |
|||
cok(this) |
|||
s = to_string(this) |
|||
searchValue = get_arg(args, 0) |
|||
replaceValue = get_arg(args, 1) |
|||
res = '' |
|||
if not is_callable(replaceValue): |
|||
replaceValue = to_string(replaceValue) |
|||
func = False |
|||
else: |
|||
func = True |
|||
# Replace all ( global ) |
|||
if GetClass(searchValue) == 'RegExp' and searchValue.glob: |
|||
last = 0 |
|||
for e in re.finditer(searchValue.pat, s): |
|||
res += s[last:e.span()[0]] |
|||
if func: |
|||
# prepare arguments for custom func (replaceValue) |
|||
call_args = (e.group(), ) + e.groups() + (e.span()[1], s) |
|||
# convert all types to JS before Js bytecode call... |
|||
res += to_string( |
|||
replaceValue.call( |
|||
this, ensure_js_types(call_args, |
|||
space=args.space))) |
|||
else: |
|||
res += replacement_template(replaceValue, s, e.span(), |
|||
e.groups()) |
|||
last = e.span()[1] |
|||
res += s[last:] |
|||
return res |
|||
elif GetClass(searchValue) == 'RegExp': |
|||
e = re.search(searchValue.pat, s) |
|||
if e is None: |
|||
return s |
|||
span = e.span() |
|||
pars = e.groups() |
|||
match = e.group() |
|||
else: |
|||
match = to_string(searchValue) |
|||
ind = s.find(match) |
|||
if ind == -1: |
|||
return s |
|||
span = ind, ind + len(match) |
|||
pars = () |
|||
res = s[:span[0]] |
|||
if func: |
|||
call_args = (match, ) + pars + (span[1], s) |
|||
# convert all types to JS before Js bytecode call... |
|||
res += to_string( |
|||
replaceValue.call(this, |
|||
ensure_js_types(call_args, |
|||
space=args.space))) |
|||
else: |
|||
res += replacement_template(replaceValue, s, span, pars) |
|||
res += s[span[1]:] |
|||
return res |
|||
|
|||
def search(this, args): |
|||
cok(this) |
|||
string = to_string(this) |
|||
regexp = get_arg(args, 0) |
|||
if GetClass(regexp) == 'RegExp': |
|||
rx = regexp |
|||
else: |
|||
rx = args.space.NewRegExp(regexp, '') |
|||
res = re.search(rx.pat, string) |
|||
if res is not None: |
|||
return float(res.span()[0]) |
|||
return -1. |
|||
|
|||
def slice(this, args): |
|||
cok(this) |
|||
s = to_string(this) |
|||
start = to_int(get_arg(args, 0)) |
|||
length = len(s) |
|||
end = get_arg(args, 1) |
|||
end = length if is_undefined(end) else to_int(end) |
|||
#From = max(length+start, 0) if start<0 else min(length, start) |
|||
#To = max(length+end, 0) if end<0 else min(length, end) |
|||
return s[start:end] |
|||
|
|||
def split(this, args): |
|||
# its a bit different from re.split! |
|||
cok(this) |
|||
s = to_string(this) |
|||
separator = get_arg(args, 0) |
|||
limit = get_arg(args, 1) |
|||
lim = 2**32 - 1 if is_undefined(limit) else to_uint32(limit) |
|||
if not lim: |
|||
return args.space.ConstructArray([]) |
|||
if is_undefined(separator): |
|||
return args.space.ConstructArray([s]) |
|||
len_s = len(s) |
|||
res = [] |
|||
R = separator if GetClass(separator) == 'RegExp' else to_string( |
|||
separator) |
|||
if not len_s: |
|||
if SplitMatch(s, 0, R) is None: |
|||
return args.space.ConstructArray([s]) |
|||
return args.space.ConstructArray([]) |
|||
p = q = 0 |
|||
while q != len_s: |
|||
e, cap = SplitMatch(s, q, R) |
|||
if e is None or e == p: |
|||
q += 1 |
|||
continue |
|||
res.append(s[p:q]) |
|||
p = q = e |
|||
if len(res) == lim: |
|||
return args.space.ConstructArray(res) |
|||
for element in cap: |
|||
res.append(element) |
|||
if len(res) == lim: |
|||
return args.space.ConstructArray(res) |
|||
res.append(s[p:]) |
|||
return args.space.ConstructArray(res) |
|||
|
|||
def substring(this, args): |
|||
cok(this) |
|||
s = to_string(this) |
|||
start = to_int(get_arg(args, 0)) |
|||
length = len(s) |
|||
end = get_arg(args, 1) |
|||
end = length if is_undefined(end) else to_int(end) |
|||
fstart = min(max(start, 0), length) |
|||
fend = min(max(end, 0), length) |
|||
return s[min(fstart, fend):max(fstart, fend)] |
|||
|
|||
def substr(this, args): |
|||
cok(this) |
|||
#I hate this function and its description in specification |
|||
r1 = to_string(this) |
|||
r2 = to_int(get_arg(args, 0)) |
|||
length = get_arg(args, 1) |
|||
r3 = 10**20 if is_undefined(length) else to_int(length) |
|||
r4 = len(r1) |
|||
r5 = r2 if r2 >= 0 else max(0, r2 + r4) |
|||
r6 = min(max(r3, 0), r4 - r5) |
|||
if r6 <= 0: |
|||
return '' |
|||
return r1[r5:r5 + r6] |
|||
|
|||
def toLowerCase(this, args): |
|||
cok(this) |
|||
return to_string(this).lower() |
|||
|
|||
def toLocaleLowerCase(this, args): |
|||
cok(this) |
|||
return to_string(this).lower() |
|||
|
|||
def toUpperCase(this, args): |
|||
cok(this) |
|||
return to_string(this).upper() |
|||
|
|||
def toLocaleUpperCase(this, args): |
|||
cok(this) |
|||
return to_string(this).upper() |
|||
|
|||
def trim(this, args): |
|||
cok(this) |
|||
return to_string(this).strip(WHITE) |
|||
|
|||
|
|||
def SplitMatch(s, q, R): |
|||
# s is Py String to match, q is the py int match start and R is Js RegExp or String. |
|||
if GetClass(R) == 'RegExp': |
|||
res = R.match(s, q) |
|||
return (None, ()) if res is None else (res.span()[1], res.groups()) |
|||
# R is just a string |
|||
if s[q:].startswith(R): |
|||
return q + len(R), () |
|||
return None, () |
@ -0,0 +1,149 @@ |
|||
from __future__ import unicode_literals |
|||
from ..conversions import * |
|||
from ..func_utils import * |
|||
|
|||
RADIX_CHARS = { |
|||
'1': 1, |
|||
'0': 0, |
|||
'3': 3, |
|||
'2': 2, |
|||
'5': 5, |
|||
'4': 4, |
|||
'7': 7, |
|||
'6': 6, |
|||
'9': 9, |
|||
'8': 8, |
|||
'a': 10, |
|||
'c': 12, |
|||
'b': 11, |
|||
'e': 14, |
|||
'd': 13, |
|||
'g': 16, |
|||
'f': 15, |
|||
'i': 18, |
|||
'h': 17, |
|||
'k': 20, |
|||
'j': 19, |
|||
'm': 22, |
|||
'l': 21, |
|||
'o': 24, |
|||
'n': 23, |
|||
'q': 26, |
|||
'p': 25, |
|||
's': 28, |
|||
'r': 27, |
|||
'u': 30, |
|||
't': 29, |
|||
'w': 32, |
|||
'v': 31, |
|||
'y': 34, |
|||
'x': 33, |
|||
'z': 35, |
|||
'A': 10, |
|||
'C': 12, |
|||
'B': 11, |
|||
'E': 14, |
|||
'D': 13, |
|||
'G': 16, |
|||
'F': 15, |
|||
'I': 18, |
|||
'H': 17, |
|||
'K': 20, |
|||
'J': 19, |
|||
'M': 22, |
|||
'L': 21, |
|||
'O': 24, |
|||
'N': 23, |
|||
'Q': 26, |
|||
'P': 25, |
|||
'S': 28, |
|||
'R': 27, |
|||
'U': 30, |
|||
'T': 29, |
|||
'W': 32, |
|||
'V': 31, |
|||
'Y': 34, |
|||
'X': 33, |
|||
'Z': 35 |
|||
} |
|||
|
|||
# parseFloat |
|||
# parseInt |
|||
# isFinite |
|||
# isNaN |
|||
|
|||
|
|||
def parseInt(this, args): |
|||
string, radix = get_arg(args, 0), get_arg(args, 1) |
|||
string = to_string(string).lstrip() |
|||
sign = 1 |
|||
if string and string[0] in ('+', '-'): |
|||
if string[0] == '-': |
|||
sign = -1 |
|||
string = string[1:] |
|||
r = to_int32(radix) |
|||
strip_prefix = True |
|||
if r: |
|||
if r < 2 or r > 36: |
|||
return NaN |
|||
if r != 16: |
|||
strip_prefix = False |
|||
else: |
|||
r = 10 |
|||
if strip_prefix: |
|||
if len(string) >= 2 and string[:2] in ('0x', '0X'): |
|||
string = string[2:] |
|||
r = 16 |
|||
n = 0 |
|||
num = 0 |
|||
while n < len(string): |
|||
cand = RADIX_CHARS.get(string[n]) |
|||
if cand is None or not cand < r: |
|||
break |
|||
num = cand + num * r |
|||
n += 1 |
|||
if not n: |
|||
return NaN |
|||
return float(sign * num) |
|||
|
|||
|
|||
def parseFloat(this, args): |
|||
string = get_arg(args, 0) |
|||
string = to_string(string).strip() |
|||
sign = 1 |
|||
if string and string[0] in ('+', '-'): |
|||
if string[0] == '-': |
|||
sign = -1 |
|||
string = string[1:] |
|||
num = None |
|||
length = 1 |
|||
max_len = None |
|||
failed = 0 |
|||
while length <= len(string): |
|||
try: |
|||
num = float(string[:length]) |
|||
max_len = length |
|||
failed = 0 |
|||
except: |
|||
failed += 1 |
|||
if failed > 4: # cant be a number anymore |
|||
break |
|||
length += 1 |
|||
if num is None: |
|||
return NaN |
|||
return sign * float(string[:max_len]) |
|||
|
|||
|
|||
def isNaN(this, args): |
|||
number = get_arg(args, 0) |
|||
if is_nan(to_number(number)): |
|||
return True |
|||
return False |
|||
|
|||
|
|||
def isFinite(this, args): |
|||
number = get_arg(args, 0) |
|||
num = to_number(number) |
|||
if is_nan(num) or is_infinity(num): |
|||
return False |
|||
return True |
@ -0,0 +1,27 @@ |
|||
import pyjsparser |
|||
from space import Space |
|||
import fill_space |
|||
from byte_trans import ByteCodeGenerator |
|||
from code import Code |
|||
from simplex import MakeError |
|||
import sys |
|||
sys.setrecursionlimit(100000) |
|||
|
|||
|
|||
pyjsparser.parser.ENABLE_JS2PY_ERRORS = lambda msg: MakeError(u'SyntaxError', unicode(msg)) |
|||
|
|||
|
|||
def eval_js_vm(js): |
|||
a = ByteCodeGenerator(Code()) |
|||
s = Space() |
|||
a.exe.space = s |
|||
s.exe = a.exe |
|||
|
|||
d = pyjsparser.parse(js) |
|||
|
|||
a.emit(d) |
|||
fill_space.fill_space(s, a) |
|||
# print a.exe.tape |
|||
a.exe.compile() |
|||
|
|||
return a.exe.run(a.exe.space.GlobalObj) |
@ -0,0 +1,133 @@ |
|||
from __future__ import unicode_literals |
|||
import six |
|||
|
|||
|
|||
#Undefined |
|||
class PyJsUndefined(object): |
|||
TYPE = 'Undefined' |
|||
Class = 'Undefined' |
|||
|
|||
|
|||
undefined = PyJsUndefined() |
|||
|
|||
|
|||
#Null |
|||
class PyJsNull(object): |
|||
TYPE = 'Null' |
|||
Class = 'Null' |
|||
|
|||
|
|||
null = PyJsNull() |
|||
|
|||
Infinity = float('inf') |
|||
NaN = float('nan') |
|||
|
|||
UNDEFINED_TYPE = PyJsUndefined |
|||
NULL_TYPE = PyJsNull |
|||
STRING_TYPE = unicode if six.PY2 else str |
|||
NUMBER_TYPE = float |
|||
BOOLEAN_TYPE = bool |
|||
|
|||
# exactly 5 simplexes! |
|||
PRIMITIVES = frozenset( |
|||
[UNDEFINED_TYPE, NULL_TYPE, STRING_TYPE, NUMBER_TYPE, BOOLEAN_TYPE]) |
|||
|
|||
TYPE_NAMES = { |
|||
UNDEFINED_TYPE: 'Undefined', |
|||
NULL_TYPE: 'Null', |
|||
STRING_TYPE: 'String', |
|||
NUMBER_TYPE: 'Number', |
|||
BOOLEAN_TYPE: 'Boolean', |
|||
} |
|||
|
|||
|
|||
def Type(x): |
|||
# Any -> Str |
|||
return TYPE_NAMES.get(type(x), 'Object') |
|||
|
|||
|
|||
def GetClass(x): |
|||
# Any -> Str |
|||
cand = TYPE_NAMES.get(type(x)) |
|||
if cand is None: |
|||
return x.Class |
|||
return cand |
|||
|
|||
|
|||
def is_undefined(self): |
|||
return self is undefined |
|||
|
|||
|
|||
def is_null(self): |
|||
return self is null |
|||
|
|||
|
|||
def is_primitive(self): |
|||
return type(self) in PRIMITIVES |
|||
|
|||
|
|||
def is_object(self): |
|||
return not is_primitive(self) |
|||
|
|||
|
|||
def is_callable(self): |
|||
return hasattr(self, 'call') |
|||
|
|||
|
|||
def is_infinity(self): |
|||
return self == float('inf') or self == -float('inf') |
|||
|
|||
|
|||
def is_nan(self): |
|||
return self != self # nan!=nan evaluates to True |
|||
|
|||
|
|||
def is_finite(self): |
|||
return not (is_nan(self) or is_infinity(self)) |
|||
|
|||
|
|||
class JsException(Exception): |
|||
def __init__(self, typ=None, message=None, throw=None): |
|||
if typ is None and message is None and throw is None: |
|||
# it means its the trasnlator based error (old format), do nothing |
|||
self._translator_based = True |
|||
else: |
|||
assert throw is None or (typ is None |
|||
and message is None), (throw, typ, |
|||
message) |
|||
self._translator_based = False |
|||
self.typ = typ |
|||
self.message = message |
|||
self.throw = throw |
|||
|
|||
def get_thrown_value(self, space): |
|||
if self.throw is not None: |
|||
return self.throw |
|||
else: |
|||
return space.NewError(self.typ, self.message) |
|||
|
|||
def __str__(self): |
|||
if self._translator_based: |
|||
if self.mes.Class == 'Error': |
|||
return self.mes.callprop('toString').value |
|||
else: |
|||
return self.mes.to_string().value |
|||
else: |
|||
if self.throw is not None: |
|||
from conversions import to_string |
|||
return to_string(self.throw) |
|||
else: |
|||
return self.typ + ': ' + self.message |
|||
|
|||
|
|||
def MakeError(typ, message=u'no info', throw=None): |
|||
return JsException(typ, |
|||
unicode(message) if message is not None else message, |
|||
throw) |
|||
|
|||
|
|||
def value_from_js_exception(js_exception, space): |
|||
if js_exception.throw is not None: |
|||
return js_exception.throw |
|||
else: |
|||
return space.NewError(js_exception.typ, js_exception.message) |
@ -0,0 +1,92 @@ |
|||
from base import * |
|||
from simplex import * |
|||
|
|||
|
|||
class Space(object): |
|||
def __init__(self): |
|||
self.GlobalObj = None |
|||
self.ctx = None |
|||
self.byte_generator = None |
|||
|
|||
self.Number = None |
|||
self.String = None |
|||
self.Boolean = None |
|||
self.RegExp = None |
|||
self.Object = None |
|||
self.Array = None |
|||
self.Function = None |
|||
|
|||
self.BooleanPrototype = None |
|||
self.NumberPrototype = None |
|||
self.StringPrototype = None |
|||
|
|||
self.FunctionPrototype = None |
|||
self.ArrayPrototype = None |
|||
self.RegExpPrototype = None |
|||
self.DatePrototype = None |
|||
self.ObjectPrototype = None |
|||
|
|||
self.ErrorPrototype = None |
|||
self.EvalErrorPrototype = None |
|||
self.RangeErrorPrototype = None |
|||
self.ReferenceErrorPrototype = None |
|||
self.SyntaxErrorPrototype = None |
|||
self.TypeErrorPrototype = None |
|||
self.URIErrorPrototype = None |
|||
|
|||
self.interpreter = None |
|||
|
|||
@property |
|||
def ERROR_TYPES(self): |
|||
return { |
|||
'Error': self.ErrorPrototype, |
|||
'EvalError': self.EvalErrorPrototype, |
|||
'RangeError': self.RangeErrorPrototype, |
|||
'ReferenceError': self.ReferenceErrorPrototype, |
|||
'SyntaxError': self.SyntaxErrorPrototype, |
|||
'TypeError': self.TypeErrorPrototype, |
|||
'URIError': self.URIErrorPrototype, |
|||
} |
|||
|
|||
def get_global_environment(self): |
|||
return self.GlobalCtx.variable_environment() |
|||
|
|||
def NewObject(self): |
|||
return PyJsObject(self.ObjectPrototype) |
|||
|
|||
def NewFunction(self, code, ctx, params, name, is_declaration, |
|||
definitions): |
|||
return PyJsFunction( |
|||
code, |
|||
ctx, |
|||
params, |
|||
name, |
|||
self, |
|||
is_declaration, |
|||
definitions, |
|||
prototype=self.FunctionPrototype) |
|||
|
|||
def NewDate(self, value): |
|||
return PyJsDate(value, self.DatePrototype) |
|||
|
|||
def NewArray(self, length=0): |
|||
return PyJsArray(length, self.ArrayPrototype) |
|||
|
|||
def NewError(self, typ, message): |
|||
return PyJsError(message, self.ERROR_TYPES[typ]) |
|||
|
|||
def NewRegExp(self, body, flags): |
|||
return PyJsRegExp(body, flags, self.RegExpPrototype) |
|||
|
|||
def ConstructArray(self, py_arr): |
|||
''' note py_arr elems are NOT converted to PyJs types!''' |
|||
arr = self.NewArray(len(py_arr)) |
|||
arr._init(py_arr) |
|||
return arr |
|||
|
|||
def ConstructObject(self, py_obj): |
|||
''' note py_obj items are NOT converted to PyJs types! ''' |
|||
obj = self.NewObject() |
|||
for k, v in py_obj.items(): |
|||
obj.put(unicode(k), v) |
|||
return obj |
@ -0,0 +1,62 @@ |
|||
from timeit import timeit |
|||
from collections import namedtuple |
|||
from array import array |
|||
from itertools import izip |
|||
from collections import deque |
|||
|
|||
|
|||
class Y(object): |
|||
UUU = 88 |
|||
|
|||
def __init__(self, x): |
|||
self.x = x |
|||
|
|||
def s(self, x): |
|||
return self.x + 1 |
|||
|
|||
|
|||
class X(Y): |
|||
A = 10 |
|||
B = 2 |
|||
C = 4 |
|||
D = 9 |
|||
|
|||
def __init__(self, x): |
|||
self.x = x |
|||
self.stack = [] |
|||
self.par = super(X, self) |
|||
|
|||
def s(self, x): |
|||
pass |
|||
|
|||
def __add__(self, other): |
|||
return self.x + other.x |
|||
|
|||
def another(self): |
|||
return Y.s(self, 1) |
|||
|
|||
def yet_another(self): |
|||
return self.par.s(1) |
|||
|
|||
|
|||
def add(a, b): |
|||
return a.x + b.x |
|||
|
|||
|
|||
t = [] |
|||
|
|||
Type = None |
|||
try: |
|||
print timeit( |
|||
""" |
|||
|
|||
t.append(4) |
|||
t.pop() |
|||
|
|||
|
|||
|
|||
""", |
|||
"from __main__ import X,Y,namedtuple,array,t,add,Type, izip", |
|||
number=1000000) |
|||
except: |
|||
raise |
@ -0,0 +1,28 @@ |
|||
def to_key(literal_or_identifier): |
|||
''' returns string representation of this object''' |
|||
if literal_or_identifier['type'] == 'Identifier': |
|||
return literal_or_identifier['name'] |
|||
elif literal_or_identifier['type'] == 'Literal': |
|||
k = literal_or_identifier['value'] |
|||
if isinstance(k, float): |
|||
return unicode(float_repr(k)) |
|||
elif 'regex' in literal_or_identifier: |
|||
return compose_regex(k) |
|||
elif isinstance(k, bool): |
|||
return u'true' if k else u'false' |
|||
elif k is None: |
|||
return u'null' |
|||
else: |
|||
return unicode(k) |
|||
|
|||
|
|||
def compose_regex(val): |
|||
reg, flags = val |
|||
# reg = REGEXP_CONVERTER._unescape_string(reg) |
|||
return u'/%s/%s' % (reg, flags) |
|||
|
|||
|
|||
def float_repr(f): |
|||
if int(f) == f: |
|||
return unicode(repr(int(f))) |
|||
return unicode(repr(f)) |
@ -0,0 +1 @@ |
|||
__author__ = 'Piotrek' |
@ -0,0 +1,308 @@ |
|||
from string import ascii_lowercase, digits |
|||
################################## |
|||
StringName = u'PyJsConstantString%d_' |
|||
NumberName = u'PyJsConstantNumber%d_' |
|||
RegExpName = u'PyJsConstantRegExp%d_' |
|||
################################## |
|||
ALPHAS = set(ascii_lowercase + ascii_lowercase.upper()) |
|||
NUMS = set(digits) |
|||
IDENTIFIER_START = ALPHAS.union(NUMS) |
|||
ESCAPE_CHARS = {'n', '0', 'b', 'f', 'r', 't', 'v', '"', "'", '\\'} |
|||
OCTAL = {'0', '1', '2', '3', '4', '5', '6', '7'} |
|||
HEX = set('0123456789abcdefABCDEF') |
|||
from utils import * |
|||
IDENTIFIER_PART = IDENTIFIER_PART.union({'.'}) |
|||
|
|||
|
|||
def _is_cancelled(source, n): |
|||
cancelled = False |
|||
k = 0 |
|||
while True: |
|||
k += 1 |
|||
if source[n - k] != '\\': |
|||
break |
|||
cancelled = not cancelled |
|||
return cancelled |
|||
|
|||
|
|||
def _ensure_regexp(source, n): #<- this function has to be improved |
|||
'''returns True if regexp starts at n else returns False |
|||
checks whether it is not a division ''' |
|||
markers = '(+~"\'=[%:?!*^|&-,;/\\' |
|||
k = 0 |
|||
while True: |
|||
k += 1 |
|||
if n - k < 0: |
|||
return True |
|||
char = source[n - k] |
|||
if char in markers: |
|||
return True |
|||
if char != ' ' and char != '\n': |
|||
break |
|||
return False |
|||
|
|||
|
|||
def parse_num(source, start, charset): |
|||
"""Returns a first index>=start of chat not in charset""" |
|||
while start < len(source) and source[start] in charset: |
|||
start += 1 |
|||
return start |
|||
|
|||
|
|||
def parse_exponent(source, start): |
|||
"""returns end of exponential, raises SyntaxError if failed""" |
|||
if not source[start] in {'e', 'E'}: |
|||
if source[start] in IDENTIFIER_PART: |
|||
raise SyntaxError('Invalid number literal!') |
|||
return start |
|||
start += 1 |
|||
if source[start] in {'-', '+'}: |
|||
start += 1 |
|||
FOUND = False |
|||
# we need at least one dig after exponent |
|||
while source[start] in NUMS: |
|||
FOUND = True |
|||
start += 1 |
|||
if not FOUND or source[start] in IDENTIFIER_PART: |
|||
raise SyntaxError('Invalid number literal!') |
|||
return start |
|||
|
|||
|
|||
def remove_constants(source): |
|||
'''Replaces Strings and Regexp literals in the source code with |
|||
identifiers and *removes comments*. Identifier is of the format: |
|||
|
|||
PyJsStringConst(String const number)_ - for Strings |
|||
PyJsRegExpConst(RegExp const number)_ - for RegExps |
|||
|
|||
Returns dict which relates identifier and replaced constant. |
|||
|
|||
Removes single line and multiline comments from JavaScript source code |
|||
Pseudo comments (inside strings) will not be removed. |
|||
|
|||
For example this line: |
|||
var x = "/*PSEUDO COMMENT*/ TEXT //ANOTHER PSEUDO COMMENT" |
|||
will be unaltered''' |
|||
source = ' ' + source + '\n' |
|||
comments = [] |
|||
inside_comment, single_comment = False, False |
|||
inside_single, inside_double = False, False |
|||
inside_regexp = False |
|||
regexp_class_count = 0 |
|||
n = 0 |
|||
while n < len(source): |
|||
char = source[n] |
|||
if char == '"' and not (inside_comment or inside_single |
|||
or inside_regexp): |
|||
if not _is_cancelled(source, n): |
|||
if inside_double: |
|||
inside_double[1] = n + 1 |
|||
comments.append(inside_double) |
|||
inside_double = False |
|||
else: |
|||
inside_double = [n, None, 0] |
|||
elif char == "'" and not (inside_comment or inside_double |
|||
or inside_regexp): |
|||
if not _is_cancelled(source, n): |
|||
if inside_single: |
|||
inside_single[1] = n + 1 |
|||
comments.append(inside_single) |
|||
inside_single = False |
|||
else: |
|||
inside_single = [n, None, 0] |
|||
elif (inside_single or inside_double): |
|||
if char in LINE_TERMINATOR: |
|||
if _is_cancelled(source, n): |
|||
if char == CR and source[n + 1] == LF: |
|||
n += 1 |
|||
n += 1 |
|||
continue |
|||
else: |
|||
raise SyntaxError( |
|||
'Invalid string literal. Line terminators must be escaped!' |
|||
) |
|||
else: |
|||
if inside_comment: |
|||
if single_comment: |
|||
if char in LINE_TERMINATOR: |
|||
inside_comment[1] = n |
|||
comments.append(inside_comment) |
|||
inside_comment = False |
|||
single_comment = False |
|||
else: # Multiline |
|||
if char == '/' and source[n - 1] == '*': |
|||
inside_comment[1] = n + 1 |
|||
comments.append(inside_comment) |
|||
inside_comment = False |
|||
elif inside_regexp: |
|||
if not quiting_regexp: |
|||
if char in LINE_TERMINATOR: |
|||
raise SyntaxError( |
|||
'Invalid regexp literal. Line terminators cant appear!' |
|||
) |
|||
if _is_cancelled(source, n): |
|||
n += 1 |
|||
continue |
|||
if char == '[': |
|||
regexp_class_count += 1 |
|||
elif char == ']': |
|||
regexp_class_count = max(regexp_class_count - 1, 0) |
|||
elif char == '/' and not regexp_class_count: |
|||
quiting_regexp = True |
|||
else: |
|||
if char not in IDENTIFIER_START: |
|||
inside_regexp[1] = n |
|||
comments.append(inside_regexp) |
|||
inside_regexp = False |
|||
elif char == '/' and source[n - 1] == '/': |
|||
single_comment = True |
|||
inside_comment = [n - 1, None, 1] |
|||
elif char == '*' and source[n - 1] == '/': |
|||
inside_comment = [n - 1, None, 1] |
|||
elif char == '/' and source[n + 1] not in ('/', '*'): |
|||
if not _ensure_regexp(source, n): #<- improve this one |
|||
n += 1 |
|||
continue #Probably just a division |
|||
quiting_regexp = False |
|||
inside_regexp = [n, None, 2] |
|||
elif not (inside_comment or inside_regexp): |
|||
if (char in NUMS and |
|||
source[n - 1] not in IDENTIFIER_PART) or char == '.': |
|||
if char == '.': |
|||
k = parse_num(source, n + 1, NUMS) |
|||
if k == n + 1: # just a stupid dot... |
|||
n += 1 |
|||
continue |
|||
k = parse_exponent(source, k) |
|||
elif char == '0' and source[n + 1] in { |
|||
'x', 'X' |
|||
}: #Hex number probably |
|||
k = parse_num(source, n + 2, HEX) |
|||
if k == n + 2 or source[k] in IDENTIFIER_PART: |
|||
raise SyntaxError('Invalid hex literal!') |
|||
else: #int or exp or flot or exp flot |
|||
k = parse_num(source, n + 1, NUMS) |
|||
if source[k] == '.': |
|||
k = parse_num(source, k + 1, NUMS) |
|||
k = parse_exponent(source, k) |
|||
comments.append((n, k, 3)) |
|||
n = k |
|||
continue |
|||
n += 1 |
|||
res = '' |
|||
start = 0 |
|||
count = 0 |
|||
constants = {} |
|||
for end, next_start, typ in comments: |
|||
res += source[start:end] |
|||
start = next_start |
|||
if typ == 0: # String |
|||
name = StringName |
|||
elif typ == 1: # comment |
|||
continue |
|||
elif typ == 2: # regexp |
|||
name = RegExpName |
|||
elif typ == 3: # number |
|||
name = NumberName |
|||
else: |
|||
raise RuntimeError() |
|||
res += ' ' + name % count + ' ' |
|||
constants[name % count] = source[end:next_start] |
|||
count += 1 |
|||
res += source[start:] |
|||
# remove this stupid white space |
|||
for e in WHITE: |
|||
res = res.replace(e, ' ') |
|||
res = res.replace(CR + LF, '\n') |
|||
for e in LINE_TERMINATOR: |
|||
res = res.replace(e, '\n') |
|||
return res.strip(), constants |
|||
|
|||
|
|||
def recover_constants(py_source, |
|||
replacements): #now has n^2 complexity. improve to n |
|||
'''Converts identifiers representing Js constants to the PyJs constants |
|||
PyJsNumberConst_1_ which has the true value of 5 will be converted to PyJsNumber(5)''' |
|||
for identifier, value in replacements.iteritems(): |
|||
if identifier.startswith('PyJsConstantRegExp'): |
|||
py_source = py_source.replace(identifier, |
|||
'JsRegExp(%s)' % repr(value)) |
|||
elif identifier.startswith('PyJsConstantString'): |
|||
py_source = py_source.replace( |
|||
identifier, 'Js(u%s)' % unify_string_literals(value)) |
|||
else: |
|||
py_source = py_source.replace(identifier, 'Js(%s)' % value) |
|||
return py_source |
|||
|
|||
|
|||
def unify_string_literals(js_string): |
|||
"""this function parses the string just like javascript |
|||
for example literal '\d' in JavaScript would be interpreted |
|||
as 'd' - backslash would be ignored and in Pyhon this |
|||
would be interpreted as '\\d' This function fixes this problem.""" |
|||
n = 0 |
|||
res = '' |
|||
limit = len(js_string) |
|||
while n < limit: |
|||
char = js_string[n] |
|||
if char == '\\': |
|||
new, n = do_escape(js_string, n) |
|||
res += new |
|||
else: |
|||
res += char |
|||
n += 1 |
|||
return res |
|||
|
|||
|
|||
def unify_regexp_literals(js): |
|||
pass |
|||
|
|||
|
|||
def do_escape(source, n): |
|||
"""Its actually quite complicated to cover every case :) |
|||
http://www.javascriptkit.com/jsref/escapesequence.shtml""" |
|||
if not n + 1 < len(source): |
|||
return '' # not possible here but can be possible in general case. |
|||
if source[n + 1] in LINE_TERMINATOR: |
|||
if source[n + 1] == CR and n + 2 < len(source) and source[n + 2] == LF: |
|||
return source[n:n + 3], n + 3 |
|||
return source[n:n + 2], n + 2 |
|||
if source[n + 1] in ESCAPE_CHARS: |
|||
return source[n:n + 2], n + 2 |
|||
if source[n + 1] in {'x', 'u'}: |
|||
char, length = ('u', 4) if source[n + 1] == 'u' else ('x', 2) |
|||
n += 2 |
|||
end = parse_num(source, n, HEX) |
|||
if end - n < length: |
|||
raise SyntaxError('Invalid escape sequence!') |
|||
#if length==4: |
|||
# return unichr(int(source[n:n+4], 16)), n+4 # <- this was a very bad way of solving this problem :) |
|||
return source[n - 2:n + length], n + length |
|||
if source[n + 1] in OCTAL: |
|||
n += 1 |
|||
end = parse_num(source, n, OCTAL) |
|||
end = min(end, n + 3) # cant be longer than 3 |
|||
# now the max allowed is 377 ( in octal) and 255 in decimal |
|||
max_num = 255 |
|||
num = 0 |
|||
len_parsed = 0 |
|||
for e in source[n:end]: |
|||
cand = 8 * num + int(e) |
|||
if cand > max_num: |
|||
break |
|||
num = cand |
|||
len_parsed += 1 |
|||
# we have to return in a different form because python may want to parse more... |
|||
# for example '\777' will be parsed by python as a whole while js will use only \77 |
|||
return '\\' + hex(num)[1:], n + len_parsed |
|||
return source[n + 1], n + 2 |
|||
|
|||
|
|||
#####TEST###### |
|||
|
|||
if __name__ == '__main__': |
|||
test = (''' |
|||
''') |
|||
|
|||
t, d = remove_constants(test) |
|||
print t, d |
@ -0,0 +1,83 @@ |
|||
""" |
|||
exp_translate routine: |
|||
It takes a single line of JS code and returns a SINGLE line of Python code. |
|||
Note var is not present here because it was removed in previous stages. Also remove this useless void keyword |
|||
If case of parsing errors it must return a pos of error. |
|||
1. Convert all assignment operations to put operations, this may be hard :( DONE, wasn't that bad |
|||
2. Convert all gets and calls to get and callprop. |
|||
3. Convert unary operators like typeof, new, !, delete, ++, -- |
|||
Delete can be handled by replacing last get method with delete. |
|||
4. Convert remaining operators that are not handled by python: |
|||
&&, || <= these should be easy simply replace && by and and || by or |
|||
=== and !== |
|||
comma operator , in, instanceof and finally :? |
|||
|
|||
|
|||
NOTES: |
|||
Strings and other literals are not present so each = means assignment |
|||
""" |
|||
from utils import * |
|||
from jsparser import * |
|||
|
|||
|
|||
def exps_translator(js): |
|||
#Check () {} and [] nums |
|||
ass = assignment_translator(js) |
|||
|
|||
|
|||
# Step 1 |
|||
def assignment_translator(js): |
|||
sep = js.split(',') |
|||
res = sep[:] |
|||
for i, e in enumerate(sep): |
|||
if '=' not in e: # no need to convert |
|||
continue |
|||
res[i] = bass_translator(e) |
|||
return ','.join(res) |
|||
|
|||
|
|||
def bass_translator(s): |
|||
# I hope that I will not have to fix any bugs here because it will be terrible |
|||
if '(' in s or '[' in s: |
|||
converted = '' |
|||
for e in bracket_split(s, ['()', '[]'], strip=False): |
|||
if e[0] == '(': |
|||
converted += '(' + bass_translator(e[1:-1]) + ')' |
|||
elif e[0] == '[': |
|||
converted += '[' + bass_translator(e[1:-1]) + ']' |
|||
else: |
|||
converted += e |
|||
s = converted |
|||
if '=' not in s: |
|||
return s |
|||
ass = reversed(s.split('=')) |
|||
last = ass.next() |
|||
res = last |
|||
for e in ass: |
|||
op = '' |
|||
if e[-1] in OP_METHODS: #increment assign like += |
|||
op = ', "' + e[-1] + '"' |
|||
e = e[:-1] |
|||
cand = e.strip( |
|||
'() ') # (a) = 40 is valid so we need to transform '(a) ' to 'a' |
|||
if not is_property_accessor(cand): # it is not a property assignment |
|||
if not is_lval(cand) or is_internal(cand): |
|||
raise SyntaxError('Invalid left-hand side in assignment') |
|||
res = 'var.put(%s, %s%s)' % (cand.__repr__(), res, op) |
|||
elif cand[-1] == ']': # property assignment via [] |
|||
c = list(bracket_split(cand, ['[]'], strip=False)) |
|||
meth, prop = ''.join(c[:-1]).strip(), c[-1][1:-1].strip( |
|||
) #this does not have to be a string so dont remove |
|||
#() because it can be a call |
|||
res = '%s.put(%s, %s%s)' % (meth, prop, res, op) |
|||
else: # Prop set via '.' |
|||
c = cand.rfind('.') |
|||
meth, prop = cand[:c].strip(), cand[c + 1:].strip('() ') |
|||
if not is_lval(prop): |
|||
raise SyntaxError('Invalid left-hand side in assignment') |
|||
res = '%s.put(%s, %s%s)' % (meth, prop.__repr__(), res, op) |
|||
return res |
|||
|
|||
|
|||
if __name__ == '__main__': |
|||
print bass_translator('3.ddsd = 40') |
@ -0,0 +1,480 @@ |
|||
"""This module translates JS flow into PY flow. |
|||
|
|||
Translates: |
|||
IF ELSE |
|||
|
|||
DO WHILE |
|||
WHILE |
|||
FOR 123 |
|||
FOR iter |
|||
CONTINUE, BREAK, RETURN, LABEL, THROW, TRY, SWITCH |
|||
""" |
|||
from utils import * |
|||
from jsparser import * |
|||
from nodevisitor import exp_translator |
|||
import random |
|||
|
|||
TO_REGISTER = [] |
|||
CONTINUE_LABEL = 'JS_CONTINUE_LABEL_%s' |
|||
BREAK_LABEL = 'JS_BREAK_LABEL_%s' |
|||
|
|||
PREPARE = '''HOLDER = var.own.get(NAME)\nvar.force_own_put(NAME, PyExceptionToJs(PyJsTempException))\n''' |
|||
RESTORE = '''if HOLDER is not None:\n var.own[NAME] = HOLDER\nelse:\n del var.own[NAME]\ndel HOLDER\n''' |
|||
TRY_CATCH = '''%stry:\nBLOCKfinally:\n%s''' % (PREPARE, indent(RESTORE)) |
|||
|
|||
|
|||
def get_continue_label(label): |
|||
return CONTINUE_LABEL % label.encode('hex') |
|||
|
|||
|
|||
def get_break_label(label): |
|||
return BREAK_LABEL % label.encode('hex') |
|||
|
|||
|
|||
def pass_until(source, start, tokens=(';', )): |
|||
while start < len(source) and source[start] not in tokens: |
|||
start += 1 |
|||
return start + 1 |
|||
|
|||
|
|||
def do_bracket_exp(source, start, throw=True): |
|||
bra, cand = pass_bracket(source, start, '()') |
|||
if throw and not bra: |
|||
raise SyntaxError('Missing bracket expression') |
|||
bra = exp_translator(bra[1:-1]) |
|||
if throw and not bra: |
|||
raise SyntaxError('Empty bracket condition') |
|||
return bra, cand if bra else start |
|||
|
|||
|
|||
def do_if(source, start): |
|||
start += 2 # pass this if |
|||
bra, start = do_bracket_exp(source, start, throw=True) |
|||
statement, start = do_statement(source, start) |
|||
if statement is None: |
|||
raise SyntaxError('Invalid if statement') |
|||
translated = 'if %s:\n' % bra + indent(statement) |
|||
|
|||
elseif = except_keyword(source, start, 'else') |
|||
is_elseif = False |
|||
if elseif: |
|||
start = elseif |
|||
if except_keyword(source, start, 'if'): |
|||
is_elseif = True |
|||
elseif, start = do_statement(source, start) |
|||
if elseif is None: |
|||
raise SyntaxError('Invalid if statement)') |
|||
if is_elseif: |
|||
translated += 'el' + elseif |
|||
else: |
|||
translated += 'else:\n' + indent(elseif) |
|||
return translated, start |
|||
|
|||
|
|||
def do_statement(source, start): |
|||
"""returns none if not found other functions that begin with 'do_' raise |
|||
also this do_ type function passes white space""" |
|||
start = pass_white(source, start) |
|||
# start is the fist position after initial start that is not a white space or \n |
|||
if not start < len(source): #if finished parsing return None |
|||
return None, start |
|||
if any(startswith_keyword(source[start:], e) for e in {'case', 'default'}): |
|||
return None, start |
|||
rest = source[start:] |
|||
for key, meth in KEYWORD_METHODS.iteritems( |
|||
): # check for statements that are uniquely defined by their keywords |
|||
if rest.startswith(key): |
|||
# has to startwith this keyword and the next letter after keyword must be either EOF or not in IDENTIFIER_PART |
|||
if len(key) == len(rest) or rest[len(key)] not in IDENTIFIER_PART: |
|||
return meth(source, start) |
|||
if rest[0] == '{': #Block |
|||
return do_block(source, start) |
|||
# Now only label and expression left |
|||
cand = parse_identifier(source, start, False) |
|||
if cand is not None: # it can mean that its a label |
|||
label, cand_start = cand |
|||
cand_start = pass_white(source, cand_start) |
|||
if source[cand_start] == ':': |
|||
return do_label(source, start) |
|||
return do_expression(source, start) |
|||
|
|||
|
|||
def do_while(source, start): |
|||
start += 5 # pass while |
|||
bra, start = do_bracket_exp(source, start, throw=True) |
|||
statement, start = do_statement(source, start) |
|||
if statement is None: |
|||
raise SyntaxError('Missing statement to execute in while loop!') |
|||
return 'while %s:\n' % bra + indent(statement), start |
|||
|
|||
|
|||
def do_dowhile(source, start): |
|||
start += 2 # pass do |
|||
statement, start = do_statement(source, start) |
|||
if statement is None: |
|||
raise SyntaxError('Missing statement to execute in do while loop!') |
|||
start = except_keyword(source, start, 'while') |
|||
if not start: |
|||
raise SyntaxError('Missing while keyword in do-while loop') |
|||
bra, start = do_bracket_exp(source, start, throw=True) |
|||
statement += 'if not %s:\n' % bra + indent('break\n') |
|||
return 'while 1:\n' + indent(statement), start |
|||
|
|||
|
|||
def do_block(source, start): |
|||
bra, start = pass_bracket(source, start, '{}') |
|||
#print source[start:], bra |
|||
#return bra +'\n', start |
|||
if bra is None: |
|||
raise SyntaxError('Missing block ( {code} )') |
|||
code = '' |
|||
bra = bra[1:-1] + ';' |
|||
bra_pos = 0 |
|||
while bra_pos < len(bra): |
|||
st, bra_pos = do_statement(bra, bra_pos) |
|||
if st is None: |
|||
break |
|||
code += st |
|||
bra_pos = pass_white(bra, bra_pos) |
|||
if bra_pos < len(bra): |
|||
raise SyntaxError('Block has more code that could not be parsed:\n' + |
|||
bra[bra_pos:]) |
|||
return code, start |
|||
|
|||
|
|||
def do_empty(source, start): |
|||
return 'pass\n', start + 1 |
|||
|
|||
|
|||
def do_expression(source, start): |
|||
start = pass_white(source, start) |
|||
end = pass_until(source, start, tokens=(';', )) |
|||
if end == start + 1: #empty statement |
|||
return 'pass\n', end |
|||
# AUTOMATIC SEMICOLON INSERTION FOLLOWS |
|||
# Without ASI this function would end with: return exp_translator(source[start:end].rstrip(';'))+'\n', end |
|||
# ASI makes things a bit more complicated: |
|||
# we will try to parse as much as possible, inserting ; in place of last new line in case of error |
|||
rev = False |
|||
rpos = 0 |
|||
while True: |
|||
try: |
|||
code = source[start:end].rstrip(';') |
|||
cand = exp_translator(code) + '\n', end |
|||
just_to_test = compile(cand[0], '', 'exec') |
|||
return cand |
|||
except Exception as e: |
|||
if not rev: |
|||
rev = source[start:end][::-1] |
|||
lpos = rpos |
|||
while True: |
|||
rpos = pass_until(rev, rpos, LINE_TERMINATOR) |
|||
if rpos >= len(rev): |
|||
raise |
|||
if filter(lambda x: x not in SPACE, rev[lpos:rpos]): |
|||
break |
|||
end = start + len(rev) - rpos + 1 |
|||
|
|||
|
|||
def do_var(source, start): |
|||
#todo auto ; insertion |
|||
start += 3 #pass var |
|||
end = pass_until(source, start, tokens=(';', )) |
|||
defs = argsplit( |
|||
source[start:end - 1] |
|||
) # defs is the list of defined vars with optional initializer |
|||
code = '' |
|||
for de in defs: |
|||
var, var_end = parse_identifier(de, 0, True) |
|||
TO_REGISTER.append(var) |
|||
var_end = pass_white(de, var_end) |
|||
if var_end < len( |
|||
de |
|||
): # we have something more to parse... It has to start with = |
|||
if de[var_end] != '=': |
|||
raise SyntaxError( |
|||
'Unexpected initializer in var statement. Expected "=", got "%s"' |
|||
% de[var_end]) |
|||
code += exp_translator(de) + '\n' |
|||
if not code.strip(): |
|||
code = 'pass\n' |
|||
return code, end |
|||
|
|||
|
|||
def do_label(source, start): |
|||
label, end = parse_identifier(source, start) |
|||
end = pass_white(source, end) |
|||
#now source[end] must be : |
|||
assert source[end] == ':' |
|||
end += 1 |
|||
inside, end = do_statement(source, end) |
|||
if inside is None: |
|||
raise SyntaxError('Missing statement after label') |
|||
defs = '' |
|||
if 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) |
|||
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) |
|||
inside = 'try:\n%sexcept %s:\n pass\n' % (indent(inside), break_label) |
|||
defs += 'class %s(Exception): pass\n' % break_label |
|||
return defs + inside, end |
|||
|
|||
|
|||
def do_for(source, start): |
|||
start += 3 # pass for |
|||
entered = start |
|||
bra, start = pass_bracket(source, start, '()') |
|||
inside, start = do_statement(source, start) |
|||
if inside is None: |
|||
raise SyntaxError('Missing statement after for') |
|||
bra = bra[1:-1] |
|||
if ';' in bra: |
|||
init = argsplit(bra, ';') |
|||
if len(init) != 3: |
|||
raise SyntaxError('Invalid for statement') |
|||
args = [] |
|||
for i, item in enumerate(init): |
|||
end = pass_white(item, 0) |
|||
if end == len(item): |
|||
args.append('' if i != 1 else '1') |
|||
continue |
|||
if not i and except_keyword(item, end, 'var') is not None: |
|||
# var statement |
|||
args.append(do_var(item, end)[0]) |
|||
continue |
|||
args.append(do_expression(item, end)[0]) |
|||
return '#for JS loop\n%swhile %s:\n%s%s\n' % ( |
|||
args[0], args[1].strip(), indent(inside), indent(args[2])), start |
|||
# iteration |
|||
end = pass_white(bra, 0) |
|||
register = False |
|||
if bra[end:].startswith('var '): |
|||
end += 3 |
|||
end = pass_white(bra, end) |
|||
register = True |
|||
name, end = parse_identifier(bra, end) |
|||
if register: |
|||
TO_REGISTER.append(name) |
|||
end = pass_white(bra, end) |
|||
if bra[end:end + 2] != 'in' or bra[end + 2] in IDENTIFIER_PART: |
|||
#print source[entered-10:entered+50] |
|||
raise SyntaxError('Invalid "for x in y" statement') |
|||
end += 2 # pass in |
|||
exp = exp_translator(bra[end:]) |
|||
res = 'for temp in %s:\n' % exp |
|||
res += indent('var.put(%s, temp)\n' % name.__repr__()) + indent(inside) |
|||
return res, start |
|||
|
|||
|
|||
# todo - IMPORTANT |
|||
def do_continue(source, start, name='continue'): |
|||
start += len(name) #pass continue |
|||
start = pass_white(source, start) |
|||
if start < len(source) and source[start] == ';': |
|||
return '%s\n' % name, start + 1 |
|||
# labeled statement or error |
|||
label, start = parse_identifier(source, start) |
|||
start = pass_white(source, start) |
|||
if start < len(source) and source[start] != ';': |
|||
raise SyntaxError('Missing ; after label name in %s statement' % name) |
|||
return 'raise %s("%s")\n' % (get_continue_label(label) |
|||
if name == 'continue' else |
|||
get_break_label(label), name), start + 1 |
|||
|
|||
|
|||
def do_break(source, start): |
|||
return do_continue(source, start, 'break') |
|||
|
|||
|
|||
def do_return(source, start): |
|||
start += 6 # pass return |
|||
end = source.find(';', start) + 1 |
|||
if end == -1: |
|||
end = len(source) |
|||
trans = exp_translator(source[start:end].rstrip(';')) |
|||
return 'return %s\n' % (trans if trans else "var.get('undefined')"), end |
|||
|
|||
|
|||
# todo later?- Also important |
|||
def do_throw(source, start): |
|||
start += 5 # pass throw |
|||
end = source.find(';', start) + 1 |
|||
if not end: |
|||
end = len(source) |
|||
trans = exp_translator(source[start:end].rstrip(';')) |
|||
if not trans: |
|||
raise SyntaxError('Invalid throw statement: nothing to throw') |
|||
res = 'PyJsTempException = JsToPyException(%s)\nraise PyJsTempException\n' % trans |
|||
return res, end |
|||
|
|||
|
|||
def do_try(source, start): |
|||
start += 3 # pass try |
|||
block, start = do_block(source, start) |
|||
result = 'try:\n%s' % indent(block) |
|||
catch = except_keyword(source, start, 'catch') |
|||
if catch: |
|||
bra, catch = pass_bracket(source, catch, '()') |
|||
bra = bra[1:-1] |
|||
identifier, bra_end = parse_identifier(bra, 0) |
|||
holder = 'PyJsHolder_%s_%d' % (identifier.encode('hex'), |
|||
random.randrange(1e8)) |
|||
identifier = identifier.__repr__() |
|||
bra_end = pass_white(bra, bra_end) |
|||
if bra_end < len(bra): |
|||
raise SyntaxError('Invalid content of catch statement') |
|||
result += 'except PyJsException as PyJsTempException:\n' |
|||
block, catch = do_block(source, catch) |
|||
# fill in except ( catch ) block and remember to recover holder variable to its previous state |
|||
result += indent( |
|||
TRY_CATCH.replace('HOLDER', holder).replace('NAME', |
|||
identifier).replace( |
|||
'BLOCK', |
|||
indent(block))) |
|||
start = max(catch, start) |
|||
final = except_keyword(source, start, 'finally') |
|||
if not (final or catch): |
|||
raise SyntaxError( |
|||
'Try statement has to be followed by catch or finally') |
|||
if not final: |
|||
return result, start |
|||
# translate finally statement |
|||
block, start = do_block(source, final) |
|||
return result + 'finally:\n%s' % indent(block), start |
|||
|
|||
|
|||
def do_debugger(source, start): |
|||
start += 8 # pass debugger |
|||
end = pass_white(source, start) |
|||
if end < len(source) and source[end] == ';': |
|||
end += 1 |
|||
return 'pass\n', end #ignore errors... |
|||
|
|||
|
|||
# todo automatic ; insertion. fuck this crappy feature |
|||
|
|||
# Least important |
|||
|
|||
|
|||
def do_switch(source, start): |
|||
start += 6 # pass switch |
|||
code = 'while 1:\n' + indent('SWITCHED = False\nCONDITION = (%s)\n') |
|||
# parse value of check |
|||
val, start = pass_bracket(source, start, '()') |
|||
if val is None: |
|||
raise SyntaxError('Missing () after switch statement') |
|||
if not val.strip(): |
|||
raise SyntaxError('Missing content inside () after switch statement') |
|||
code = code % exp_translator(val) |
|||
bra, start = pass_bracket(source, start, '{}') |
|||
if bra is None: |
|||
raise SyntaxError('Missing block {} after switch statement') |
|||
bra_pos = 0 |
|||
bra = bra[1:-1] + ';' |
|||
while True: |
|||
case = except_keyword(bra, bra_pos, 'case') |
|||
default = except_keyword(bra, bra_pos, 'default') |
|||
assert not (case and default) |
|||
if case or default: # this ?: expression makes things much harder.... |
|||
case_code = None |
|||
if case: |
|||
case_code = 'if SWITCHED or PyJsStrictEq(CONDITION, %s):\n' |
|||
# we are looking for a first : with count 1. ? gives -1 and : gives +1. |
|||
count = 0 |
|||
for pos, e in enumerate(bra[case:], case): |
|||
if e == '?': |
|||
count -= 1 |
|||
elif e == ':': |
|||
count += 1 |
|||
if count == 1: |
|||
break |
|||
else: |
|||
raise SyntaxError( |
|||
'Missing : token after case in switch statement') |
|||
case_condition = exp_translator( |
|||
bra[case:pos]) # switch {case CONDITION: statements} |
|||
case_code = case_code % case_condition |
|||
case = pos + 1 |
|||
if default: |
|||
case = except_token(bra, default, ':') |
|||
case_code = 'if True:\n' |
|||
# now parse case statements (things after ':' ) |
|||
cand, case = do_statement(bra, case) |
|||
while cand: |
|||
case_code += indent(cand) |
|||
cand, case = do_statement(bra, case) |
|||
case_code += indent('SWITCHED = True\n') |
|||
code += indent(case_code) |
|||
bra_pos = case |
|||
else: |
|||
break |
|||
# prevent infinite loop :) |
|||
code += indent('break\n') |
|||
return code, start |
|||
|
|||
|
|||
def do_pyimport(source, start): |
|||
start += 8 |
|||
lib, start = parse_identifier(source, start) |
|||
jlib = 'PyImport_%s' % lib |
|||
code = 'import %s as %s\n' % (lib, jlib) |
|||
#check whether valid lib name... |
|||
try: |
|||
compile(code, '', 'exec') |
|||
except: |
|||
raise SyntaxError( |
|||
'Invalid Python module name (%s) in pyimport statement' % lib) |
|||
# var.pyimport will handle module conversion to PyJs object |
|||
code += 'var.pyimport(%s, %s)\n' % (repr(lib), jlib) |
|||
return code, start |
|||
|
|||
|
|||
def do_with(source, start): |
|||
raise NotImplementedError('With statement is not implemented yet :(') |
|||
|
|||
|
|||
KEYWORD_METHODS = { |
|||
'do': do_dowhile, |
|||
'while': do_while, |
|||
'if': do_if, |
|||
'throw': do_throw, |
|||
'return': do_return, |
|||
'continue': do_continue, |
|||
'break': do_break, |
|||
'try': do_try, |
|||
'for': do_for, |
|||
'switch': do_switch, |
|||
'var': do_var, |
|||
'debugger': do_debugger, # this one does not do anything |
|||
'with': do_with, |
|||
'pyimport': do_pyimport |
|||
} |
|||
|
|||
#Also not specific statements (harder to detect) |
|||
# Block {} |
|||
# Expression or Empty Statement |
|||
# Label |
|||
# |
|||
# Its easy to recognize block but harder to distinguish between label and expression statement |
|||
|
|||
|
|||
def translate_flow(source): |
|||
"""Source cant have arrays, object, constant or function literals. |
|||
Returns PySource and variables to register""" |
|||
global TO_REGISTER |
|||
TO_REGISTER = [] |
|||
return do_block('{%s}' % source, 0)[0], TO_REGISTER |
|||
|
|||
|
|||
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] |
@ -0,0 +1,98 @@ |
|||
"""This module removes JS functions from source code""" |
|||
from jsparser import * |
|||
from utils import * |
|||
|
|||
INLINE_NAME = 'PyJsLvalInline%d_' |
|||
INLINE_COUNT = 0 |
|||
PRE_EXP_STARTS = { |
|||
'return', 'new', 'void', 'throw', 'typeof', 'in', 'instanceof' |
|||
} |
|||
PRE_ALLOWED = IDENTIFIER_PART.union({';', '{', '}', ']', ')', ':'}) |
|||
INCREMENTS = {'++', '--'} |
|||
|
|||
|
|||
def reset_inline_count(): |
|||
global INLINE_COUNT |
|||
INLINE_COUNT = 0 |
|||
|
|||
|
|||
def remove_functions(source, all_inline=False): |
|||
"""removes functions and returns new source, and 2 dicts. |
|||
first dict with removed hoisted(global) functions and second with replaced inline functions""" |
|||
global INLINE_COUNT |
|||
inline = {} |
|||
hoisted = {} |
|||
n = 0 |
|||
limit = len(source) - 9 # 8 is length of 'function' |
|||
res = '' |
|||
last = 0 |
|||
while n < limit: |
|||
if n and source[n - 1] in IDENTIFIER_PART: |
|||
n += 1 |
|||
continue |
|||
if source[n:n + 8] == 'function' and source[n + |
|||
8] not in IDENTIFIER_PART: |
|||
if source[:n].rstrip().endswith( |
|||
'.'): # allow function as a property name :) |
|||
n += 1 |
|||
continue |
|||
if source[n + 8:].lstrip().startswith( |
|||
':'): # allow functions inside objects... |
|||
n += 1 |
|||
continue |
|||
entered = n |
|||
res += source[last:n] |
|||
name = '' |
|||
n = pass_white(source, n + 8) |
|||
if source[n] in IDENTIFIER_START: # hoisted function |
|||
name, n = parse_identifier(source, n) |
|||
args, n = pass_bracket(source, n, '()') |
|||
if not args: |
|||
raise SyntaxError('Function misses bracket with argnames ()') |
|||
args = args.strip('() \n') |
|||
args = tuple(parse_identifier(e, 0)[0] |
|||
for e in argsplit(args)) if args else () |
|||
if len(args) - len(set(args)): |
|||
# I know its legal in JS but python does not allow duplicate argnames |
|||
# I will not work around it |
|||
raise SyntaxError( |
|||
'Function has duplicate argument names. Its not legal in this implementation. Sorry.' |
|||
) |
|||
block, n = pass_bracket(source, n, '{}') |
|||
if not block: |
|||
raise SyntaxError( |
|||
'Function does not have any code block to execute') |
|||
mixed = False # named function expression flag |
|||
if name and not all_inline: |
|||
# Here I will distinguish between named function expression (mixed) and a function statement |
|||
before = source[:entered].rstrip() |
|||
if any(endswith_keyword(before, e) for e in PRE_EXP_STARTS): |
|||
#print 'Ended ith keyword' |
|||
mixed = True |
|||
elif before and before[-1] not in PRE_ALLOWED and not before[ |
|||
-2:] in INCREMENTS: |
|||
#print 'Ended with'+repr(before[-1]), before[-1]=='}' |
|||
mixed = True |
|||
else: |
|||
#print 'FUNCTION STATEMENT' |
|||
#its a function statement. |
|||
# todo remove fucking label if present! |
|||
hoisted[name] = block, args |
|||
if not name or mixed or all_inline: # its a function expression (can be both named and not named) |
|||
#print 'FUNCTION EXPRESSION' |
|||
INLINE_COUNT += 1 |
|||
iname = INLINE_NAME % INLINE_COUNT # inline name |
|||
res += ' ' + iname |
|||
inline['%s@%s' % ( |
|||
iname, name |
|||
)] = block, args #here added real name at the end because it has to be added to the func scope |
|||
last = n |
|||
else: |
|||
n += 1 |
|||
res += source[last:] |
|||
return res, hoisted, inline |
|||
|
|||
|
|||
if __name__ == '__main__': |
|||
print remove_functions( |
|||
'5+5 function n (functiona ,functionaj) {dsd s, dsdd}') |
@ -0,0 +1,326 @@ |
|||
""" |
|||
The process of translating JS will go like that: # TOP = 'imports and scope set' |
|||
|
|||
1. Remove all the comments |
|||
2. Replace number, string and regexp literals with markers |
|||
4. Remove global Functions and move their translation to the TOP. Also add register code there. |
|||
5. Replace inline functions with lvals |
|||
6. Remove List and Object literals and replace them with lvals |
|||
7. Find and remove var declarations, generate python register code that would go on TOP. |
|||
|
|||
Here we should be left with global code only where 1 line of js code = 1 line of python code. |
|||
Routine translating this code should be called glob_translate: |
|||
1. Search for outer structures and translate them using glob and inside using exps_translate |
|||
|
|||
|
|||
exps_translate routine: |
|||
1. Remove outer {} |
|||
2. Split lines at ; |
|||
3. Convert line by line using exp_translate |
|||
4. In case of error in 3 try to insert ; according to ECMA rules and repeat 3. |
|||
|
|||
exp_translate routine: |
|||
It takes a single line of JS code and returns a SINGLE line of Python code. |
|||
Note var is not present here because it was removed in previous stages. |
|||
If case of parsing errors it must return a pos of error. |
|||
1. Convert all assignment operations to put operations, this may be hard :( |
|||
2. Convert all gets and calls to get and callprop. |
|||
3. Convert unary operators like typeof, new, !, delete. |
|||
Delete can be handled by replacing last get method with delete. |
|||
4. Convert remaining operators that are not handled by python eg: === and , |
|||
|
|||
|
|||
|
|||
|
|||
|
|||
lval format PyJsLvalNR |
|||
marker PyJs(TYPE_NAME)(NR) |
|||
|
|||
TODO |
|||
1. Number literal replacement |
|||
2. Array literal replacement |
|||
3. Object literal replacement |
|||
5. Function replacement |
|||
4. Literal replacement translators |
|||
|
|||
|
|||
""" |
|||
|
|||
from utils import * |
|||
|
|||
OP_METHODS = { |
|||
'*': '__mul__', |
|||
'/': '__div__', |
|||
'%': '__mod__', |
|||
'+': '__add__', |
|||
'-': '__sub__', |
|||
'<<': '__lshift__', |
|||
'>>': '__rshift__', |
|||
'&': '__and__', |
|||
'^': '__xor__', |
|||
'|': '__or__' |
|||
} |
|||
|
|||
|
|||
def dbg(source): |
|||
try: |
|||
with open('C:\Users\Piotrek\Desktop\dbg.py', 'w') as f: |
|||
f.write(source) |
|||
except: |
|||
pass |
|||
|
|||
|
|||
def indent(lines, ind=4): |
|||
return ind * ' ' + lines.replace('\n', '\n' + ind * ' ').rstrip(' ') |
|||
|
|||
|
|||
def inject_before_lval(source, lval, code): |
|||
if source.count(lval) > 1: |
|||
dbg(source) |
|||
print |
|||
print lval |
|||
raise RuntimeError('To many lvals (%s)' % lval) |
|||
elif not source.count(lval): |
|||
dbg(source) |
|||
print |
|||
print lval |
|||
assert lval not in source |
|||
raise RuntimeError('No lval found "%s"' % lval) |
|||
end = source.index(lval) |
|||
inj = source.rfind('\n', 0, end) |
|||
ind = inj |
|||
while source[ind + 1] == ' ': |
|||
ind += 1 |
|||
ind -= inj |
|||
return source[:inj + 1] + indent(code, ind) + source[inj + 1:] |
|||
|
|||
|
|||
def bracket_split(source, brackets=('()', '{}', '[]'), strip=False): |
|||
"""DOES NOT RETURN EMPTY STRINGS (can only return empty bracket content if strip=True)""" |
|||
starts = [e[0] for e in brackets] |
|||
in_bracket = 0 |
|||
n = 0 |
|||
last = 0 |
|||
while n < len(source): |
|||
e = source[n] |
|||
if not in_bracket and e in starts: |
|||
in_bracket = 1 |
|||
start = n |
|||
b_start, b_end = brackets[starts.index(e)] |
|||
elif in_bracket: |
|||
if e == b_start: |
|||
in_bracket += 1 |
|||
elif e == b_end: |
|||
in_bracket -= 1 |
|||
if not in_bracket: |
|||
if source[last:start]: |
|||
yield source[last:start] |
|||
last = n + 1 |
|||
yield source[start + strip:n + 1 - strip] |
|||
n += 1 |
|||
if source[last:]: |
|||
yield source[last:] |
|||
|
|||
|
|||
def pass_bracket(source, start, bracket='()'): |
|||
"""Returns content of brackets with brackets and first pos after brackets |
|||
if source[start] is followed by some optional white space and brackets. |
|||
Otherwise None""" |
|||
e = bracket_split(source[start:], [bracket], False) |
|||
try: |
|||
cand = e.next() |
|||
except StopIteration: |
|||
return None, None |
|||
if not cand.strip(): #white space... |
|||
try: |
|||
res = e.next() |
|||
return res, start + len(cand) + len(res) |
|||
except StopIteration: |
|||
return None, None |
|||
elif cand[-1] == bracket[1]: |
|||
return cand, start + len(cand) |
|||
else: |
|||
return None, None |
|||
|
|||
|
|||
def startswith_keyword(start, keyword): |
|||
start = start.lstrip() |
|||
if start.startswith(keyword): |
|||
if len(keyword) < len(start): |
|||
if start[len(keyword)] in IDENTIFIER_PART: |
|||
return False |
|||
return True |
|||
return False |
|||
|
|||
|
|||
def endswith_keyword(ending, keyword): |
|||
ending = ending.rstrip() |
|||
if ending.endswith(keyword): |
|||
if len(keyword) < len(ending): |
|||
if ending[len(ending) - len(keyword) - 1] in IDENTIFIER_PART: |
|||
return False |
|||
return True |
|||
return False |
|||
|
|||
|
|||
def pass_white(source, start): |
|||
n = start |
|||
while n < len(source): |
|||
if source[n] in SPACE: |
|||
n += 1 |
|||
else: |
|||
break |
|||
return n |
|||
|
|||
|
|||
def except_token(source, start, token, throw=True): |
|||
"""Token can be only a single char. Returns position after token if found. Otherwise raises syntax error if throw |
|||
otherwise returns None""" |
|||
start = pass_white(source, start) |
|||
if start < len(source) and source[start] == token: |
|||
return start + 1 |
|||
if throw: |
|||
raise SyntaxError('Missing token. Expected %s' % token) |
|||
return None |
|||
|
|||
|
|||
def except_keyword(source, start, keyword): |
|||
""" Returns position after keyword if found else None |
|||
Note: skips white space""" |
|||
start = pass_white(source, start) |
|||
kl = len(keyword) #keyword len |
|||
if kl + start > len(source): |
|||
return None |
|||
if source[start:start + kl] != keyword: |
|||
return None |
|||
if kl + start < len(source) and source[start + kl] in IDENTIFIER_PART: |
|||
return None |
|||
return start + kl |
|||
|
|||
|
|||
def parse_identifier(source, start, throw=True): |
|||
"""passes white space from start and returns first identifier, |
|||
if identifier invalid and throw raises SyntaxError otherwise returns None""" |
|||
start = pass_white(source, start) |
|||
end = start |
|||
if not end < len(source): |
|||
if throw: |
|||
raise SyntaxError('Missing identifier!') |
|||
return None |
|||
if source[end] not in IDENTIFIER_START: |
|||
if throw: |
|||
raise SyntaxError('Invalid identifier start: "%s"' % source[end]) |
|||
return None |
|||
end += 1 |
|||
while end < len(source) and source[end] in IDENTIFIER_PART: |
|||
end += 1 |
|||
if not is_valid_lval(source[start:end]): |
|||
if throw: |
|||
raise SyntaxError( |
|||
'Invalid identifier name: "%s"' % source[start:end]) |
|||
return None |
|||
return source[start:end], end |
|||
|
|||
|
|||
def argsplit(args, sep=','): |
|||
"""used to split JS args (it is not that simple as it seems because |
|||
sep can be inside brackets). |
|||
|
|||
pass args *without* brackets! |
|||
|
|||
Used also to parse array and object elements, and more""" |
|||
parsed_len = 0 |
|||
last = 0 |
|||
splits = [] |
|||
for e in bracket_split(args, brackets=['()', '[]', '{}']): |
|||
if e[0] not in {'(', '[', '{'}: |
|||
for i, char in enumerate(e): |
|||
if char == sep: |
|||
splits.append(args[last:parsed_len + i]) |
|||
last = parsed_len + i + 1 |
|||
parsed_len += len(e) |
|||
splits.append(args[last:]) |
|||
return splits |
|||
|
|||
|
|||
def split_add_ops(text): |
|||
"""Specialized function splitting text at add/sub operators. |
|||
Operands are *not* translated. Example result ['op1', '+', 'op2', '-', 'op3']""" |
|||
n = 0 |
|||
text = text.replace('++', '##').replace( |
|||
'--', '@@') #text does not normally contain any of these |
|||
spotted = False # set to true if noticed anything other than +- or white space |
|||
last = 0 |
|||
while n < len(text): |
|||
e = text[n] |
|||
if e == '+' or e == '-': |
|||
if spotted: |
|||
yield text[last:n].replace('##', '++').replace('@@', '--') |
|||
yield e |
|||
last = n + 1 |
|||
spotted = False |
|||
elif e == '/' or e == '*' or e == '%': |
|||
spotted = False |
|||
elif e != ' ': |
|||
spotted = True |
|||
n += 1 |
|||
yield text[last:n].replace('##', '++').replace('@@', '--') |
|||
|
|||
|
|||
def split_at_any(text, |
|||
lis, |
|||
translate=False, |
|||
not_before=[], |
|||
not_after=[], |
|||
validitate=None): |
|||
""" doc """ |
|||
lis.sort(key=lambda x: len(x), reverse=True) |
|||
last = 0 |
|||
n = 0 |
|||
text_len = len(text) |
|||
while n < text_len: |
|||
if any(text[:n].endswith(e) |
|||
for e in not_before): #Cant end with end before |
|||
n += 1 |
|||
continue |
|||
for e in lis: |
|||
s = len(e) |
|||
if s + n > text_len: |
|||
continue |
|||
if validitate and not validitate(e, text[:n], text[n + s:]): |
|||
continue |
|||
if any(text[n + s:].startswith(e) |
|||
for e in not_after): #Cant end with end before |
|||
n += 1 |
|||
break |
|||
if e == text[n:n + s]: |
|||
yield text[last:n] if not translate else translate( |
|||
text[last:n]) |
|||
yield e |
|||
n += s |
|||
last = n |
|||
break |
|||
else: |
|||
n += 1 |
|||
yield text[last:n] if not translate else translate(text[last:n]) |
|||
|
|||
|
|||
def split_at_single(text, sep, not_before=[], not_after=[]): |
|||
"""Works like text.split(sep) but separated fragments |
|||
cant end with not_before or start with not_after""" |
|||
n = 0 |
|||
lt, s = len(text), len(sep) |
|||
last = 0 |
|||
while n < lt: |
|||
if not s + n > lt: |
|||
if sep == text[n:n + s]: |
|||
if any(text[last:n].endswith(e) for e in not_before): |
|||
pass |
|||
elif any(text[n + s:].startswith(e) for e in not_after): |
|||
pass |
|||
else: |
|||
yield text[last:n] |
|||
last = n + s |
|||
n += s - 1 |
|||
n += 1 |
|||
yield text[last:] |
@ -0,0 +1,562 @@ |
|||
from jsparser import * |
|||
from utils import * |
|||
import re |
|||
from utils import * |
|||
|
|||
#Note all white space sent to this module must be ' ' so no '\n' |
|||
REPL = {} |
|||
|
|||
#PROBLEMS |
|||
# <<=, >>=, >>>= |
|||
# they are unusual so I will not fix that now. a++ +b works fine and a+++++b (a++ + ++b) does not work even in V8 |
|||
ASSIGNMENT_MATCH = '(?<!=|!|<|>)=(?!=)' |
|||
|
|||
|
|||
def unary_validitator(keyword, before, after): |
|||
if keyword[-1] in IDENTIFIER_PART: |
|||
if not after or after[0] in IDENTIFIER_PART: |
|||
return False |
|||
if before and before[-1] in IDENTIFIER_PART: # I am not sure here... |
|||
return False |
|||
return True |
|||
|
|||
|
|||
def comb_validitator(keyword, before, after): |
|||
if keyword == 'instanceof' or keyword == 'in': |
|||
if before and before[-1] in IDENTIFIER_PART: |
|||
return False |
|||
elif after and after[0] in IDENTIFIER_PART: |
|||
return False |
|||
return True |
|||
|
|||
|
|||
def bracket_replace(code): |
|||
new = '' |
|||
for e in bracket_split(code, ['()', '[]'], False): |
|||
if e[0] == '[': |
|||
name = '#PYJSREPL' + str(len(REPL)) + '{' |
|||
new += name |
|||
REPL[name] = e |
|||
elif e[0] == '(': # can be a function call |
|||
name = '@PYJSREPL' + str(len(REPL)) + '}' |
|||
new += name |
|||
REPL[name] = e |
|||
else: |
|||
new += e |
|||
return new |
|||
|
|||
|
|||
class NodeVisitor: |
|||
def __init__(self, code): |
|||
self.code = code |
|||
|
|||
def rl(self, lis, op): |
|||
"""performs this operation on a list from *right to left* |
|||
op must take 2 args |
|||
a,b,c => op(a, op(b, c))""" |
|||
it = reversed(lis) |
|||
res = trans(it.next()) |
|||
for e in it: |
|||
e = trans(e) |
|||
res = op(e, res) |
|||
return res |
|||
|
|||
def lr(self, lis, op): |
|||
"""performs this operation on a list from *left to right* |
|||
op must take 2 args |
|||
a,b,c => op(op(a, b), c)""" |
|||
it = iter(lis) |
|||
res = trans(it.next()) |
|||
for e in it: |
|||
e = trans(e) |
|||
res = op(res, e) |
|||
return res |
|||
|
|||
def translate(self): |
|||
"""Translates outer operation and calls translate on inner operation. |
|||
Returns fully translated code.""" |
|||
if not self.code: |
|||
return '' |
|||
new = bracket_replace(self.code) |
|||
#Check comma operator: |
|||
cand = new.split(',') #every comma in new must be an operator |
|||
if len(cand) > 1: #LR |
|||
return self.lr(cand, js_comma) |
|||
#Check = operator: |
|||
# dont split at != or !== or == or === or <= or >= |
|||
#note <<=, >>= or this >>> will NOT be supported |
|||
# maybe I will change my mind later |
|||
# Find this crappy ?: |
|||
if '?' in new: |
|||
cond_ind = new.find('?') |
|||
tenary_start = 0 |
|||
for ass in re.finditer(ASSIGNMENT_MATCH, new): |
|||
cand = ass.span()[1] |
|||
if cand < cond_ind: |
|||
tenary_start = cand |
|||
else: |
|||
break |
|||
actual_tenary = new[tenary_start:] |
|||
spl = ''.join(split_at_any(new, [':', '?'], translate=trans)) |
|||
tenary_translation = transform_crap(spl) |
|||
assignment = new[:tenary_start] + ' PyJsConstantTENARY' |
|||
return trans(assignment).replace('PyJsConstantTENARY', |
|||
tenary_translation) |
|||
cand = list(split_at_single(new, '=', ['!', '=', '<', '>'], ['='])) |
|||
if len(cand) > 1: # RL |
|||
it = reversed(cand) |
|||
res = trans(it.next()) |
|||
for e in it: |
|||
e = e.strip() |
|||
if not e: |
|||
raise SyntaxError('Missing left-hand in assignment!') |
|||
op = '' |
|||
if e[-2:] in OP_METHODS: |
|||
op = ',' + e[-2:].__repr__() |
|||
e = e[:-2] |
|||
elif e[-1:] in OP_METHODS: |
|||
op = ',' + e[-1].__repr__() |
|||
e = e[:-1] |
|||
e = trans(e) |
|||
#Now replace last get method with put and change args |
|||
c = list(bracket_split(e, ['()'])) |
|||
beg, arglist = ''.join(c[:-1]).strip(), c[-1].strip( |
|||
) #strips just to make sure... I will remove it later |
|||
if beg[-4:] != '.get': |
|||
raise SyntaxError('Invalid left-hand side in assignment') |
|||
beg = beg[0:-3] + 'put' |
|||
arglist = arglist[0:-1] + ', ' + res + op + ')' |
|||
res = beg + arglist |
|||
return res |
|||
#Now check remaining 2 arg operators that are not handled by python |
|||
#They all have Left to Right (LR) associativity |
|||
order = [OR, AND, BOR, BXOR, BAND, EQS, COMPS, BSHIFTS, ADDS, MULTS] |
|||
# actually we dont need OR and AND because they can be handled easier. But just for fun |
|||
dangerous = ['<', '>'] |
|||
for typ in order: |
|||
#we have to use special method for ADDS since they can be also unary operation +/++ or -/-- FUCK |
|||
if '+' in typ: |
|||
cand = list(split_add_ops(new)) |
|||
else: |
|||
#dont translate. cant start or end on dangerous op. |
|||
cand = list( |
|||
split_at_any( |
|||
new, |
|||
typ.keys(), |
|||
False, |
|||
dangerous, |
|||
dangerous, |
|||
validitate=comb_validitator)) |
|||
if not len(cand) > 1: |
|||
continue |
|||
n = 1 |
|||
res = trans(cand[0]) |
|||
if not res: |
|||
raise SyntaxError("Missing operand!") |
|||
while n < len(cand): |
|||
e = cand[n] |
|||
if not e: |
|||
raise SyntaxError("Missing operand!") |
|||
if n % 2: |
|||
op = typ[e] |
|||
else: |
|||
res = op(res, trans(e)) |
|||
n += 1 |
|||
return res |
|||
#Now replace unary operators - only they are left |
|||
cand = list( |
|||
split_at_any( |
|||
new, UNARY.keys(), False, validitate=unary_validitator)) |
|||
if len(cand) > 1: #contains unary operators |
|||
if '++' in cand or '--' in cand: #it cant contain both ++ and -- |
|||
if '--' in cand: |
|||
op = '--' |
|||
meths = js_post_dec, js_pre_dec |
|||
else: |
|||
op = '++' |
|||
meths = js_post_inc, js_pre_inc |
|||
pos = cand.index(op) |
|||
if cand[pos - 1].strip(): # post increment |
|||
a = cand[pos - 1] |
|||
meth = meths[0] |
|||
elif cand[pos + 1].strip(): #pre increment |
|||
a = cand[pos + 1] |
|||
meth = meths[1] |
|||
else: |
|||
raise SyntaxError('Invalid use of ++ operator') |
|||
if cand[pos + 2:]: |
|||
raise SyntaxError('Too many operands') |
|||
operand = meth(trans(a)) |
|||
cand = cand[:pos - 1] |
|||
# now last cand should be operand and every other odd element should be empty |
|||
else: |
|||
operand = trans(cand[-1]) |
|||
del cand[-1] |
|||
for i, e in enumerate(reversed(cand)): |
|||
if i % 2: |
|||
if e.strip(): |
|||
raise SyntaxError('Too many operands') |
|||
else: |
|||
operand = UNARY[e](operand) |
|||
return operand |
|||
#Replace brackets |
|||
if new[0] == '@' or new[0] == '#': |
|||
if len( |
|||
list(bracket_split(new, ('#{', '@}'))) |
|||
) == 1: # we have only one bracket, otherwise pseudobracket like @@.... |
|||
assert new in REPL |
|||
if new[0] == '#': |
|||
raise SyntaxError( |
|||
'[] cant be used as brackets! Use () instead.') |
|||
return '(' + trans(REPL[new][1:-1]) + ')' |
|||
#Replace function calls and prop getters |
|||
# 'now' must be a reference like: a or b.c.d but it can have also calls or getters ( for example a["b"](3)) |
|||
#From here @@ means a function call and ## means get operation (note they dont have to present) |
|||
it = bracket_split(new, ('#{', '@}')) |
|||
res = [] |
|||
for e in it: |
|||
if e[0] != '#' and e[0] != '@': |
|||
res += [x.strip() for x in e.split('.')] |
|||
else: |
|||
res += [e.strip()] |
|||
# res[0] can be inside @@ (name)... |
|||
res = filter(lambda x: x, res) |
|||
if is_internal(res[0]): |
|||
out = res[0] |
|||
elif res[0][0] in {'#', '@'}: |
|||
out = '(' + trans(REPL[res[0]][1:-1]) + ')' |
|||
elif is_valid_lval( |
|||
res[0]) or res[0] in {'this', 'false', 'true', 'null'}: |
|||
out = 'var.get(' + res[0].__repr__() + ')' |
|||
else: |
|||
if is_reserved(res[0]): |
|||
raise SyntaxError('Unexpected reserved word: "%s"' % res[0]) |
|||
raise SyntaxError('Invalid identifier: "%s"' % res[0]) |
|||
if len(res) == 1: |
|||
return out |
|||
n = 1 |
|||
while n < len(res): #now every func call is a prop call |
|||
e = res[n] |
|||
if e[0] == '@': # direct call |
|||
out += trans_args(REPL[e]) |
|||
n += 1 |
|||
continue |
|||
args = False #assume not prop call |
|||
if n + 1 < len(res) and res[n + 1][0] == '@': #prop call |
|||
args = trans_args(REPL[res[n + 1]])[1:] |
|||
if args != ')': |
|||
args = ',' + args |
|||
if e[0] == '#': |
|||
prop = trans(REPL[e][1:-1]) |
|||
else: |
|||
if not is_lval(e): |
|||
raise SyntaxError('Invalid identifier: "%s"' % e) |
|||
prop = e.__repr__() |
|||
if args: # prop call |
|||
n += 1 |
|||
out += '.callprop(' + prop + args |
|||
else: #prop get |
|||
out += '.get(' + prop + ')' |
|||
n += 1 |
|||
return out |
|||
|
|||
|
|||
def js_comma(a, b): |
|||
return 'PyJsComma(' + a + ',' + b + ')' |
|||
|
|||
|
|||
def js_or(a, b): |
|||
return '(' + a + ' or ' + b + ')' |
|||
|
|||
|
|||
def js_bor(a, b): |
|||
return '(' + a + '|' + b + ')' |
|||
|
|||
|
|||
def js_bxor(a, b): |
|||
return '(' + a + '^' + b + ')' |
|||
|
|||
|
|||
def js_band(a, b): |
|||
return '(' + a + '&' + b + ')' |
|||
|
|||
|
|||
def js_and(a, b): |
|||
return '(' + a + ' and ' + b + ')' |
|||
|
|||
|
|||
def js_strict_eq(a, b): |
|||
|
|||
return 'PyJsStrictEq(' + a + ',' + b + ')' |
|||
|
|||
|
|||
def js_strict_neq(a, b): |
|||
return 'PyJsStrictNeq(' + a + ',' + b + ')' |
|||
|
|||
|
|||
#Not handled by python in the same way like JS. For example 2==2==True returns false. |
|||
# In JS above would return true so we need brackets. |
|||
def js_abstract_eq(a, b): |
|||
return '(' + a + '==' + b + ')' |
|||
|
|||
|
|||
#just like == |
|||
def js_abstract_neq(a, b): |
|||
return '(' + a + '!=' + b + ')' |
|||
|
|||
|
|||
def js_lt(a, b): |
|||
return '(' + a + '<' + b + ')' |
|||
|
|||
|
|||
def js_le(a, b): |
|||
return '(' + a + '<=' + b + ')' |
|||
|
|||
|
|||
def js_ge(a, b): |
|||
return '(' + a + '>=' + b + ')' |
|||
|
|||
|
|||
def js_gt(a, b): |
|||
return '(' + a + '>' + b + ')' |
|||
|
|||
|
|||
def js_in(a, b): |
|||
return b + '.contains(' + a + ')' |
|||
|
|||
|
|||
def js_instanceof(a, b): |
|||
return a + '.instanceof(' + b + ')' |
|||
|
|||
|
|||
def js_lshift(a, b): |
|||
return '(' + a + '<<' + b + ')' |
|||
|
|||
|
|||
def js_rshift(a, b): |
|||
return '(' + a + '>>' + b + ')' |
|||
|
|||
|
|||
def js_shit(a, b): |
|||
return 'PyJsBshift(' + a + ',' + b + ')' |
|||
|
|||
|
|||
def js_add( |
|||
a, |
|||
b): # To simplify later process of converting unary operators + and ++ |
|||
return '(%s+%s)' % (a, b) |
|||
|
|||
|
|||
def js_sub(a, b): # To simplify |
|||
return '(%s-%s)' % (a, b) |
|||
|
|||
|
|||
def js_mul(a, b): |
|||
return '(' + a + '*' + b + ')' |
|||
|
|||
|
|||
def js_div(a, b): |
|||
return '(' + a + '/' + b + ')' |
|||
|
|||
|
|||
def js_mod(a, b): |
|||
return '(' + a + '%' + b + ')' |
|||
|
|||
|
|||
def js_typeof(a): |
|||
cand = list(bracket_split(a, ('()', ))) |
|||
if len(cand) == 2 and cand[0] == 'var.get': |
|||
return cand[0] + cand[1][:-1] + ',throw=False).typeof()' |
|||
return a + '.typeof()' |
|||
|
|||
|
|||
def js_void(a): |
|||
return '(' + a + ')' |
|||
|
|||
|
|||
def js_new(a): |
|||
cands = list(bracket_split(a, ('()', ))) |
|||
lim = len(cands) |
|||
if lim < 2: |
|||
return a + '.create()' |
|||
n = 0 |
|||
while n < lim: |
|||
c = cands[n] |
|||
if c[0] == '(': |
|||
if cands[n - 1].endswith( |
|||
'.get') and n + 1 >= lim: # last get operation. |
|||
return a + '.create()' |
|||
elif cands[n - 1][0] == '(': |
|||
return ''.join(cands[:n]) + '.create' + c + ''.join( |
|||
cands[n + 1:]) |
|||
elif cands[n - 1] == '.callprop': |
|||
beg = ''.join(cands[:n - 1]) |
|||
args = argsplit(c[1:-1], ',') |
|||
prop = args[0] |
|||
new_args = ','.join(args[1:]) |
|||
create = '.get(%s).create(%s)' % (prop, new_args) |
|||
return beg + create + ''.join(cands[n + 1:]) |
|||
n += 1 |
|||
return a + '.create()' |
|||
|
|||
|
|||
def js_delete(a): |
|||
#replace last get with delete. |
|||
c = list(bracket_split(a, ['()'])) |
|||
beg, arglist = ''.join(c[:-1]).strip(), c[-1].strip( |
|||
) #strips just to make sure... I will remove it later |
|||
if beg[-4:] != '.get': |
|||
raise SyntaxError('Invalid delete operation') |
|||
return beg[:-3] + 'delete' + arglist |
|||
|
|||
|
|||
def js_neg(a): |
|||
return '(-' + a + ')' |
|||
|
|||
|
|||
def js_pos(a): |
|||
return '(+' + a + ')' |
|||
|
|||
|
|||
def js_inv(a): |
|||
return '(~' + a + ')' |
|||
|
|||
|
|||
def js_not(a): |
|||
return a + '.neg()' |
|||
|
|||
|
|||
def postfix(a, inc, post): |
|||
bra = list(bracket_split(a, ('()', ))) |
|||
meth = bra[-2] |
|||
if not meth.endswith('get'): |
|||
raise SyntaxError('Invalid ++ or -- operation.') |
|||
bra[-2] = bra[-2][:-3] + 'put' |
|||
bra[-1] = '(%s,%s%sJs(1))' % (bra[-1][1:-1], a, '+' if inc else '-') |
|||
res = ''.join(bra) |
|||
return res if not post else '(%s%sJs(1))' % (res, '-' if inc else '+') |
|||
|
|||
|
|||
def js_pre_inc(a): |
|||
return postfix(a, True, False) |
|||
|
|||
|
|||
def js_post_inc(a): |
|||
return postfix(a, True, True) |
|||
|
|||
|
|||
def js_pre_dec(a): |
|||
return postfix(a, False, False) |
|||
|
|||
|
|||
def js_post_dec(a): |
|||
return postfix(a, False, True) |
|||
|
|||
|
|||
OR = {'||': js_or} |
|||
AND = {'&&': js_and} |
|||
BOR = {'|': js_bor} |
|||
BXOR = {'^': js_bxor} |
|||
BAND = {'&': js_band} |
|||
|
|||
EQS = { |
|||
'===': js_strict_eq, |
|||
'!==': js_strict_neq, |
|||
'==': js_abstract_eq, # we need == and != too. Read a note above method |
|||
'!=': js_abstract_neq |
|||
} |
|||
|
|||
#Since JS does not have chained comparisons we need to implement all cmp methods. |
|||
COMPS = { |
|||
'<': js_lt, |
|||
'<=': js_le, |
|||
'>=': js_ge, |
|||
'>': js_gt, |
|||
'instanceof': js_instanceof, #todo change to validitate |
|||
'in': js_in |
|||
} |
|||
|
|||
BSHIFTS = {'<<': js_lshift, '>>': js_rshift, '>>>': js_shit} |
|||
|
|||
ADDS = {'+': js_add, '-': js_sub} |
|||
|
|||
MULTS = {'*': js_mul, '/': js_div, '%': js_mod} |
|||
|
|||
#Note they dont contain ++ and -- methods because they both have 2 different methods |
|||
# correct method will be found automatically in translate function |
|||
UNARY = { |
|||
'typeof': js_typeof, |
|||
'void': js_void, |
|||
'new': js_new, |
|||
'delete': js_delete, |
|||
'!': js_not, |
|||
'-': js_neg, |
|||
'+': js_pos, |
|||
'~': js_inv, |
|||
'++': None, |
|||
'--': None |
|||
} |
|||
|
|||
|
|||
def transform_crap(code): #needs some more tests |
|||
"""Transforms this ?: crap into if else python syntax""" |
|||
ind = code.rfind('?') |
|||
if ind == -1: |
|||
return code |
|||
sep = code.find(':', ind) |
|||
if sep == -1: |
|||
raise SyntaxError('Invalid ?: syntax (probably missing ":" )') |
|||
beg = max(code.rfind(':', 0, ind), code.find('?', 0, ind)) + 1 |
|||
end = code.find(':', sep + 1) |
|||
end = len(code) if end == -1 else end |
|||
formula = '(' + code[ind + 1:sep] + ' if ' + code[ |
|||
beg:ind] + ' else ' + code[sep + 1:end] + ')' |
|||
return transform_crap(code[:beg] + formula + code[end:]) |
|||
|
|||
|
|||
from code import InteractiveConsole |
|||
|
|||
#e = InteractiveConsole(globals()).interact() |
|||
import traceback |
|||
|
|||
|
|||
def trans(code): |
|||
return NodeVisitor(code.strip()).translate().strip() |
|||
|
|||
|
|||
#todo finish this trans args |
|||
def trans_args(code): |
|||
new = bracket_replace(code.strip()[1:-1]) |
|||
args = ','.join(trans(e) for e in new.split(',')) |
|||
return '(%s)' % args |
|||
|
|||
|
|||
EXP = 0 |
|||
|
|||
|
|||
def exp_translator(code): |
|||
global REPL, EXP |
|||
EXP += 1 |
|||
REPL = {} |
|||
#print EXP, code |
|||
code = code.replace('\n', ' ') |
|||
assert '@' not in code |
|||
assert ';' not in code |
|||
assert '#' not in code |
|||
#if not code.strip(): #? |
|||
# return 'var.get("undefined")' |
|||
try: |
|||
return trans(code) |
|||
except: |
|||
#print '\n\ntrans failed on \n\n' + code |
|||
#raw_input('\n\npress enter') |
|||
raise |
|||
|
|||
|
|||
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++') |
|||
#First line translated with PyJs: PyJsStrictEq(PyJsAdd((Js(100)*Js(50)),Js(30)), Js("5030")), yay! |
|||
print exp_translator('delete a.f') |
File diff suppressed because one or more lines are too long
@ -0,0 +1,300 @@ |
|||
""" 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""" |
|||
OBJECT_LVAL = 'PyJsLvalObject%d_' |
|||
ARRAY_LVAL = 'PyJsLvalArray%d_' |
|||
from utils import * |
|||
from jsparser import * |
|||
from nodevisitor import exp_translator |
|||
import functions |
|||
from flow import KEYWORD_METHODS |
|||
|
|||
|
|||
def FUNC_TRANSLATOR(*a): # stupid import system in python |
|||
raise RuntimeError('Remember to set func translator. Thank you.') |
|||
|
|||
|
|||
def set_func_translator(ftrans): |
|||
# stupid stupid Python or Peter |
|||
global FUNC_TRANSLATOR |
|||
FUNC_TRANSLATOR = ftrans |
|||
|
|||
|
|||
def is_empty_object(n, last): |
|||
"""n may be the inside of block or object""" |
|||
if n.strip(): |
|||
return False |
|||
# seems to be but can be empty code |
|||
last = last.strip() |
|||
markers = { |
|||
')', |
|||
';', |
|||
} |
|||
if not last or last[-1] in markers: |
|||
return False |
|||
return True |
|||
|
|||
|
|||
# todo refine this function |
|||
def is_object(n, last): |
|||
"""n may be the inside of block or object. |
|||
last is the code before object""" |
|||
if is_empty_object(n, last): |
|||
return True |
|||
if not n.strip(): |
|||
return False |
|||
#Object contains lines of code so it cant be an object |
|||
if len(argsplit(n, ';')) > 1: |
|||
return False |
|||
cands = argsplit(n, ',') |
|||
if not cands[-1].strip(): |
|||
return True # {xxxx,} empty after last , it must be an object |
|||
for cand in cands: |
|||
cand = cand.strip() |
|||
# separate each candidate element at : in dict and check whether they are correct... |
|||
kv = argsplit(cand, ':') |
|||
if len( |
|||
kv |
|||
) > 2: # set the len of kv to 2 because of this stupid : expression |
|||
kv = kv[0], ':'.join(kv[1:]) |
|||
|
|||
if len(kv) == 2: |
|||
# key value pair, check whether not label or ?: |
|||
k, v = kv |
|||
if not is_lval(k.strip()): |
|||
return False |
|||
v = v.strip() |
|||
if v.startswith('function'): |
|||
continue |
|||
#will fail on label... {xxx: while {}} |
|||
if v[0] == '{': # value cant be a code block |
|||
return False |
|||
for e in KEYWORD_METHODS: |
|||
# if v starts with any statement then return false |
|||
if v.startswith(e) and len(e) < len(v) and v[len( |
|||
e)] not in IDENTIFIER_PART: |
|||
return False |
|||
elif not (cand.startswith('set ') or cand.startswith('get ')): |
|||
return False |
|||
return True |
|||
|
|||
|
|||
def is_array(last): |
|||
#it can be prop getter |
|||
last = last.strip() |
|||
if any( |
|||
endswith_keyword(last, e) for e in |
|||
{'return', 'new', 'void', 'throw', 'typeof', 'in', 'instanceof'}): |
|||
return True |
|||
markers = {')', ']'} |
|||
return not last or not (last[-1] in markers or last[-1] in IDENTIFIER_PART) |
|||
|
|||
|
|||
def remove_objects(code, count=1): |
|||
""" This function replaces objects with OBJECTS_LVALS, returns new code, replacement dict and count. |
|||
count arg is the number that should be added to the LVAL of the first replaced object |
|||
""" |
|||
replacements = {} #replacement dict |
|||
br = bracket_split(code, ['{}', '[]']) |
|||
res = '' |
|||
last = '' |
|||
for e in br: |
|||
#test whether e is an object |
|||
if e[0] == '{': |
|||
n, temp_rep, cand_count = remove_objects(e[1:-1], count) |
|||
# if e was not an object then n should not contain any : |
|||
if is_object(n, last): |
|||
#e was an object |
|||
res += ' ' + OBJECT_LVAL % count |
|||
replacements[OBJECT_LVAL % count] = e |
|||
count += 1 |
|||
else: |
|||
# e was just a code block but could contain objects inside |
|||
res += '{%s}' % n |
|||
count = cand_count |
|||
replacements.update(temp_rep) |
|||
elif e[0] == '[': |
|||
if is_array(last): |
|||
res += e # will be translated later |
|||
else: # prop get |
|||
n, rep, count = remove_objects(e[1:-1], count) |
|||
res += '[%s]' % n |
|||
replacements.update(rep) |
|||
else: # e does not contain any objects |
|||
res += e |
|||
last = e #needed to test for this stipid empty object |
|||
return res, replacements, count |
|||
|
|||
|
|||
def remove_arrays(code, count=1): |
|||
"""removes arrays and replaces them with ARRAY_LVALS |
|||
returns new code and replacement dict |
|||
*NOTE* has to be called AFTER remove objects""" |
|||
res = '' |
|||
last = '' |
|||
replacements = {} |
|||
for e in bracket_split(code, ['[]']): |
|||
if e[0] == '[': |
|||
if is_array(last): |
|||
name = ARRAY_LVAL % count |
|||
res += ' ' + name |
|||
replacements[name] = e |
|||
count += 1 |
|||
else: # pseudo array. But pseudo array can contain true array. for example a[['d'][3]] has 2 pseudo and 1 true array |
|||
cand, new_replacements, count = remove_arrays(e[1:-1], count) |
|||
res += '[%s]' % cand |
|||
replacements.update(new_replacements) |
|||
else: |
|||
res += e |
|||
last = e |
|||
return res, replacements, count |
|||
|
|||
|
|||
def translate_object(obj, lval, obj_count=1, arr_count=1): |
|||
obj = obj[1:-1] # remove {} from both ends |
|||
obj, obj_rep, obj_count = remove_objects(obj, obj_count) |
|||
obj, arr_rep, arr_count = remove_arrays(obj, arr_count) |
|||
# functions can be defined inside objects. exp translator cant translate them. |
|||
# we have to remove them and translate with func translator |
|||
# its better explained in translate_array function |
|||
obj, hoisted, inline = functions.remove_functions(obj, all_inline=True) |
|||
assert not hoisted |
|||
gsetters_after = '' |
|||
keys = argsplit(obj) |
|||
res = [] |
|||
for i, e in enumerate(keys, 1): |
|||
e = e.strip() |
|||
if e.startswith('set '): |
|||
gsetters_after += translate_setter(lval, e) |
|||
elif e.startswith('get '): |
|||
gsetters_after += translate_getter(lval, e) |
|||
elif ':' not in e: |
|||
if i < len(keys |
|||
): # can happen legally only in the last element {3:2,} |
|||
raise SyntaxError('Unexpected "," in Object literal') |
|||
break |
|||
else: #Not getter, setter or elision |
|||
spl = argsplit(e, ':') |
|||
if len(spl) < 2: |
|||
raise SyntaxError('Invalid Object literal: ' + e) |
|||
try: |
|||
key, value = spl |
|||
except: #len(spl)> 2 |
|||
print 'Unusual case ' + repr(e) |
|||
key = spl[0] |
|||
value = ':'.join(spl[1:]) |
|||
key = key.strip() |
|||
if is_internal(key): |
|||
key = '%s.to_string().value' % key |
|||
else: |
|||
key = repr(key) |
|||
|
|||
value = exp_translator(value) |
|||
if not value: |
|||
raise SyntaxError('Missing value in Object literal') |
|||
res.append('%s:%s' % (key, value)) |
|||
res = '%s = Js({%s})\n' % (lval, ','.join(res)) + gsetters_after |
|||
# translate all the nested objects (including removed earlier functions) |
|||
for nested_name, nested_info in inline.iteritems(): # functions |
|||
nested_block, nested_args = nested_info |
|||
new_def = FUNC_TRANSLATOR(nested_name, nested_block, nested_args) |
|||
res = new_def + res |
|||
for lval, obj in obj_rep.iteritems(): #objects |
|||
new_def, obj_count, arr_count = translate_object( |
|||
obj, lval, obj_count, arr_count) |
|||
# add object definition BEFORE array definition |
|||
res = new_def + res |
|||
for lval, obj in arr_rep.iteritems(): # arrays |
|||
new_def, obj_count, arr_count = translate_array( |
|||
obj, lval, obj_count, arr_count) |
|||
# add object definition BEFORE array definition |
|||
res = new_def + res |
|||
return res, obj_count, arr_count |
|||
|
|||
|
|||
def translate_setter(lval, setter): |
|||
func = 'function' + setter[3:] |
|||
try: |
|||
_, data, _ = functions.remove_functions(func) |
|||
if not data or len(data) > 1: |
|||
raise Exception() |
|||
except: |
|||
raise SyntaxError('Could not parse setter: ' + setter) |
|||
prop = data.keys()[0] |
|||
body, args = data[prop] |
|||
if len(args) != 1: #setter must have exactly 1 argument |
|||
raise SyntaxError('Invalid setter. It must take exactly 1 argument.') |
|||
# now messy part |
|||
res = FUNC_TRANSLATOR('setter', body, args) |
|||
res += "%s.define_own_property(%s, {'set': setter})\n" % (lval, repr(prop)) |
|||
return res |
|||
|
|||
|
|||
def translate_getter(lval, getter): |
|||
func = 'function' + getter[3:] |
|||
try: |
|||
_, data, _ = functions.remove_functions(func) |
|||
if not data or len(data) > 1: |
|||
raise Exception() |
|||
except: |
|||
raise SyntaxError('Could not parse getter: ' + getter) |
|||
prop = data.keys()[0] |
|||
body, args = data[prop] |
|||
if len(args) != 0: #setter must have exactly 0 argument |
|||
raise SyntaxError('Invalid getter. It must take exactly 0 argument.') |
|||
# now messy part |
|||
res = FUNC_TRANSLATOR('getter', body, args) |
|||
res += "%s.define_own_property(%s, {'get': setter})\n" % (lval, repr(prop)) |
|||
return res |
|||
|
|||
|
|||
def translate_array(array, lval, obj_count=1, arr_count=1): |
|||
"""array has to be any js array for example [1,2,3] |
|||
lval has to be name of this array. |
|||
Returns python code that adds lval to the PY scope it should be put before lval""" |
|||
array = array[1:-1] |
|||
array, obj_rep, obj_count = remove_objects(array, obj_count) |
|||
array, arr_rep, arr_count = remove_arrays(array, arr_count) |
|||
#functions can be also defined in arrays, this caused many problems since in Python |
|||
# functions cant be defined inside literal |
|||
# remove functions (they dont contain arrays or objects so can be translated easily) |
|||
# hoisted functions are treated like inline |
|||
array, hoisted, inline = functions.remove_functions(array, all_inline=True) |
|||
assert not hoisted |
|||
arr = [] |
|||
# separate elements in array |
|||
for e in argsplit(array, ','): |
|||
# translate expressions in array PyJsLvalInline will not be translated! |
|||
e = exp_translator(e.replace('\n', '')) |
|||
arr.append(e if e else 'None') |
|||
arr = '%s = Js([%s])\n' % (lval, ','.join(arr)) |
|||
#But we can have more code to add to define arrays/objects/functions defined inside this array |
|||
# translate nested objects: |
|||
# functions: |
|||
for nested_name, nested_info in inline.iteritems(): |
|||
nested_block, nested_args = nested_info |
|||
new_def = FUNC_TRANSLATOR(nested_name, nested_block, nested_args) |
|||
arr = new_def + arr |
|||
for lval, obj in obj_rep.iteritems(): |
|||
new_def, obj_count, arr_count = translate_object( |
|||
obj, lval, obj_count, arr_count) |
|||
# add object definition BEFORE array definition |
|||
arr = new_def + arr |
|||
for lval, obj in arr_rep.iteritems(): |
|||
new_def, obj_count, arr_count = translate_array( |
|||
obj, lval, obj_count, arr_count) |
|||
# add object definition BEFORE array definition |
|||
arr = new_def + arr |
|||
return arr, obj_count, arr_count |
|||
|
|||
|
|||
if __name__ == '__main__': |
|||
test = 'a = {404:{494:19}}; b = 303; if () {f={:}; { }}' |
|||
|
|||
#print remove_objects(test) |
|||
#print list(bracket_split(' {}')) |
|||
print |
|||
print remove_arrays( |
|||
'typeof a&&!db.test(a)&&!ib[(bb.exec(a)||["",""], [][[5][5]])[1].toLowerCase()])' |
|||
) |
|||
print is_object('', ')') |
@ -0,0 +1,4 @@ |
|||
from jsparser import * |
|||
from utils import * |
|||
# maybe I will try rewriting my parser in the future... Tokenizer makes things much easier and faster, unfortunately I |
|||
# did not know anything about parsers when I was starting this project so I invented my own. |
@ -0,0 +1,151 @@ |
|||
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 |
|||
from functions import remove_functions, reset_inline_count |
|||
from jsparser import inject_before_lval, indent, dbg |
|||
|
|||
TOP_GLOBAL = '''from js2py.pyjs import *\nvar = Scope( JS_BUILTINS )\nset_global_object(var)\n''' |
|||
|
|||
|
|||
def translate_js(js, top=TOP_GLOBAL): |
|||
"""js has to be a javascript source code. |
|||
returns equivalent python code.""" |
|||
# Remove constant literals |
|||
no_const, constants = remove_constants(js) |
|||
#print 'const count', len(constants) |
|||
# Remove object literals |
|||
no_obj, objects, obj_count = remove_objects(no_const) |
|||
#print 'obj count', len(objects) |
|||
# Remove arrays |
|||
no_arr, arrays, arr_count = remove_arrays(no_obj) |
|||
#print 'arr count', len(arrays) |
|||
# Here remove and replace functions |
|||
reset_inline_count() |
|||
no_func, hoisted, inline = remove_functions(no_arr) |
|||
|
|||
#translate flow and expressions |
|||
py_seed, to_register = translate_flow(no_func) |
|||
|
|||
# register variables and hoisted functions |
|||
#top += '# register variables\n' |
|||
top += 'var.registers(%s)\n' % str(to_register + hoisted.keys()) |
|||
|
|||
#Recover functions |
|||
# hoisted functions recovery |
|||
defs = '' |
|||
#defs += '# define hoisted functions\n' |
|||
#print len(hoisted) , 'HH'*40 |
|||
for nested_name, nested_info in hoisted.iteritems(): |
|||
nested_block, nested_args = nested_info |
|||
new_code = translate_func('PyJsLvalTempHoisted', nested_block, |
|||
nested_args) |
|||
new_code += 'PyJsLvalTempHoisted.func_name = %s\n' % repr(nested_name) |
|||
defs += new_code + '\nvar.put(%s, PyJsLvalTempHoisted)\n' % repr( |
|||
nested_name) |
|||
#defs += '# Everting ready!\n' |
|||
# inline functions recovery |
|||
for nested_name, nested_info in inline.iteritems(): |
|||
nested_block, nested_args = nested_info |
|||
new_code = translate_func(nested_name, nested_block, nested_args) |
|||
py_seed = inject_before_lval(py_seed, |
|||
nested_name.split('@')[0], new_code) |
|||
# add hoisted definitiond - they have literals that have to be recovered |
|||
py_seed = defs + py_seed |
|||
|
|||
#Recover arrays |
|||
for arr_lval, arr_code in arrays.iteritems(): |
|||
translation, obj_count, arr_count = translate_array( |
|||
arr_code, arr_lval, obj_count, arr_count) |
|||
py_seed = inject_before_lval(py_seed, arr_lval, translation) |
|||
|
|||
#Recover objects |
|||
for obj_lval, obj_code in objects.iteritems(): |
|||
translation, obj_count, arr_count = translate_object( |
|||
obj_code, obj_lval, obj_count, arr_count) |
|||
py_seed = inject_before_lval(py_seed, obj_lval, translation) |
|||
|
|||
#Recover constants |
|||
py_code = recover_constants(py_seed, constants) |
|||
|
|||
return top + py_code |
|||
|
|||
|
|||
def translate_func(name, block, args): |
|||
"""Translates functions and all nested functions to Python code. |
|||
name - name of that function (global functions will be available under var while |
|||
inline will be available directly under this name ) |
|||
block - code of the function (*with* brackets {} ) |
|||
args - arguments that this function takes""" |
|||
inline = name.startswith('PyJsLvalInline') |
|||
real_name = '' |
|||
if inline: |
|||
name, real_name = name.split('@') |
|||
arglist = ', '.join(args) + ', ' if args else '' |
|||
code = '@Js\ndef %s(%sthis, arguments, var=var):\n' % (name, arglist) |
|||
# register local variables |
|||
scope = "'this':this, 'arguments':arguments" #it will be a simple dictionary |
|||
for arg in args: |
|||
scope += ', %s:%s' % (repr(arg), arg) |
|||
if real_name: |
|||
scope += ', %s:%s' % (repr(real_name), name) |
|||
code += indent('var = Scope({%s}, var)\n' % scope) |
|||
block, nested_hoisted, nested_inline = remove_functions(block) |
|||
py_code, to_register = translate_flow(block) |
|||
#register variables declared with var and names of hoisted functions. |
|||
to_register += nested_hoisted.keys() |
|||
if to_register: |
|||
code += indent('var.registers(%s)\n' % str(to_register)) |
|||
for nested_name, info in nested_hoisted.iteritems(): |
|||
nested_block, nested_args = info |
|||
new_code = translate_func('PyJsLvalTempHoisted', nested_block, |
|||
nested_args) |
|||
# Now put definition of hoisted function on the top |
|||
code += indent(new_code) |
|||
code += indent( |
|||
'PyJsLvalTempHoisted.func_name = %s\n' % repr(nested_name)) |
|||
code += indent( |
|||
'var.put(%s, PyJsLvalTempHoisted)\n' % repr(nested_name)) |
|||
for nested_name, info in nested_inline.iteritems(): |
|||
nested_block, nested_args = info |
|||
new_code = translate_func(nested_name, nested_block, nested_args) |
|||
# Inject definitions of inline functions just before usage |
|||
# nested inline names have this format : LVAL_NAME@REAL_NAME |
|||
py_code = inject_before_lval(py_code, |
|||
nested_name.split('@')[0], new_code) |
|||
if py_code.strip(): |
|||
code += indent(py_code) |
|||
return code |
|||
|
|||
|
|||
set_func_translator(translate_func) |
|||
|
|||
#print inject_before_lval(' chuj\n moj\n lval\nelse\n', 'lval', 'siema\njestem piter\n') |
|||
import time |
|||
#print time.time() |
|||
#print translate_js('if (1) console.log("Hello, World!"); else if (5) console.log("Hello world?");') |
|||
#print time.time() |
|||
t = """ |
|||
var x = [1,2,3,4,5,6]; |
|||
for (var e in x) {console.log(e); delete x[3];} |
|||
console.log(5 in [1,2,3,4,5]); |
|||
|
|||
""" |
|||
|
|||
SANDBOX = ''' |
|||
import traceback |
|||
try: |
|||
%s |
|||
except: |
|||
print traceback.format_exc() |
|||
print |
|||
raw_input('Press Enter to quit') |
|||
''' |
|||
if __name__ == '__main__': |
|||
# test with jq if works then it really works :) |
|||
#with open('jq.js', 'r') as f: |
|||
#jq = f.read() |
|||
|
|||
#res = translate_js(jq) |
|||
res = translate_js(t) |
|||
dbg(SANDBOX % indent(res)) |
|||
print 'Done' |
@ -0,0 +1,91 @@ |
|||
import sys |
|||
import unicodedata |
|||
from collections import defaultdict |
|||
|
|||
|
|||
def is_lval(t): |
|||
"""Does not chceck whether t is not resticted or internal""" |
|||
if not t: |
|||
return False |
|||
i = iter(t) |
|||
if i.next() not in IDENTIFIER_START: |
|||
return False |
|||
return all(e in IDENTIFIER_PART for e in i) |
|||
|
|||
|
|||
def is_valid_lval(t): |
|||
"""Checks whether t is valid JS identifier name (no keyword like var, function, if etc) |
|||
Also returns false on internal""" |
|||
if not is_internal(t) and is_lval(t) and t not in RESERVED_NAMES: |
|||
return True |
|||
return False |
|||
|
|||
|
|||
def is_plval(t): |
|||
return t.startswith('PyJsLval') |
|||
|
|||
|
|||
def is_marker(t): |
|||
return t.startswith('PyJsMarker') or t.startswith('PyJsConstant') |
|||
|
|||
|
|||
def is_internal(t): |
|||
return is_plval(t) or is_marker(t) or t == 'var' # var is a scope var |
|||
|
|||
|
|||
def is_property_accessor(t): |
|||
return '[' in t or '.' in t |
|||
|
|||
|
|||
def is_reserved(t): |
|||
return t in RESERVED_NAMES |
|||
|
|||
|
|||
#http://stackoverflow.com/questions/14245893/efficiently-list-all-characters-in-a-given-unicode-category |
|||
BOM = u'\uFEFF' |
|||
ZWJ = u'\u200D' |
|||
ZWNJ = u'\u200C' |
|||
TAB = u'\u0009' |
|||
VT = u'\u000B' |
|||
FF = u'\u000C' |
|||
SP = u'\u0020' |
|||
NBSP = u'\u00A0' |
|||
LF = u'\u000A' |
|||
CR = u'\u000D' |
|||
LS = u'\u2028' |
|||
PS = u'\u2029' |
|||
|
|||
U_CATEGORIES = defaultdict(list) # Thank you Martijn Pieters! |
|||
for c in map(unichr, range(sys.maxunicode + 1)): |
|||
U_CATEGORIES[unicodedata.category(c)].append(c) |
|||
|
|||
UNICODE_LETTER = set(U_CATEGORIES['Lu'] + U_CATEGORIES['Ll'] + |
|||
U_CATEGORIES['Lt'] + U_CATEGORIES['Lm'] + |
|||
U_CATEGORIES['Lo'] + U_CATEGORIES['Nl']) |
|||
UNICODE_COMBINING_MARK = set(U_CATEGORIES['Mn'] + U_CATEGORIES['Mc']) |
|||
UNICODE_DIGIT = set(U_CATEGORIES['Nd']) |
|||
UNICODE_CONNECTOR_PUNCTUATION = set(U_CATEGORIES['Pc']) |
|||
IDENTIFIER_START = UNICODE_LETTER.union( |
|||
{'$', '_'}) # and some fucking unicode escape sequence |
|||
IDENTIFIER_PART = IDENTIFIER_START.union(UNICODE_COMBINING_MARK).union( |
|||
UNICODE_DIGIT).union(UNICODE_CONNECTOR_PUNCTUATION).union({ZWJ, ZWNJ}) |
|||
USP = U_CATEGORIES['Zs'] |
|||
KEYWORD = { |
|||
'break', 'do', 'instanceof', 'typeof', 'case', 'else', 'new', 'var', |
|||
'catch', 'finally', 'return', 'void', 'continue', 'for', 'switch', 'while', |
|||
'debugger', 'function', 'this', 'with', 'default', 'if', 'throw', 'delete', |
|||
'in', 'try' |
|||
} |
|||
|
|||
FUTURE_RESERVED_WORD = { |
|||
'class', 'enum', 'extends', 'super', 'const', 'export', 'import' |
|||
} |
|||
RESERVED_NAMES = KEYWORD.union(FUTURE_RESERVED_WORD).union( |
|||
{'null', 'false', 'true'}) |
|||
|
|||
WHITE = {TAB, VT, FF, SP, NBSP, BOM}.union(USP) |
|||
LINE_TERMINATOR = {LF, CR, LS, PS} |
|||
LLINE_TERMINATOR = list(LINE_TERMINATOR) |
|||
x = ''.join(WHITE) + ''.join(LINE_TERMINATOR) |
|||
SPACE = WHITE.union(LINE_TERMINATOR) |
|||
LINE_TERMINATOR_SEQUENCE = LINE_TERMINATOR.union({CR + LF}) |
@ -0,0 +1,113 @@ |
|||
__all__ = ['require'] |
|||
import subprocess, os, codecs, glob |
|||
from .evaljs import translate_js |
|||
import six |
|||
DID_INIT = False |
|||
DIRNAME = os.path.dirname(os.path.abspath(__file__)) |
|||
PY_NODE_MODULES_PATH = os.path.join(DIRNAME, 'py_node_modules') |
|||
|
|||
|
|||
def _init(): |
|||
global DID_INIT |
|||
if DID_INIT: |
|||
return |
|||
assert subprocess.call( |
|||
'node -v', shell=True, cwd=DIRNAME |
|||
) == 0, 'You must have node installed! run: brew install node' |
|||
assert subprocess.call( |
|||
'cd %s;npm install babel-core babel-cli babel-preset-es2015 babel-polyfill babelify browserify' |
|||
% repr(DIRNAME), |
|||
shell=True, |
|||
cwd=DIRNAME) == 0, 'Could not link required node_modules' |
|||
DID_INIT = True |
|||
|
|||
|
|||
ADD_TO_GLOBALS_FUNC = ''' |
|||
;function addToGlobals(name, obj) { |
|||
if (!Object.prototype.hasOwnProperty('_fake_exports')) { |
|||
Object.prototype._fake_exports = {}; |
|||
} |
|||
Object.prototype._fake_exports[name] = obj; |
|||
}; |
|||
|
|||
''' |
|||
# subprocess.call("""node -e 'require("browserify")'""", shell=True) |
|||
GET_FROM_GLOBALS_FUNC = ''' |
|||
;function getFromGlobals(name) { |
|||
if (!Object.prototype.hasOwnProperty('_fake_exports')) { |
|||
throw Error("Could not find any value named "+name); |
|||
} |
|||
if (Object.prototype._fake_exports.hasOwnProperty(name)) { |
|||
return Object.prototype._fake_exports[name]; |
|||
} else { |
|||
throw Error("Could not find any value named "+name); |
|||
} |
|||
}; |
|||
|
|||
''' |
|||
|
|||
|
|||
def require(module_name, include_polyfill=False, update=False): |
|||
assert isinstance(module_name, str), 'module_name must be a string!' |
|||
py_name = module_name.replace('-', '_') |
|||
module_filename = '%s.py' % py_name |
|||
var_name = py_name.rpartition('/')[-1] |
|||
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' |
|||
code = ADD_TO_GLOBALS_FUNC |
|||
if include_polyfill: |
|||
code += "\n;require('babel-polyfill');\n" |
|||
code += """ |
|||
var module_temp_love_python = require(%s); |
|||
addToGlobals(%s, module_temp_love_python); |
|||
""" % (repr(module_name), repr(module_name)) |
|||
with open(os.path.join(DIRNAME, in_file_name), 'wb') as f: |
|||
f.write(code.encode('utf-8') if six.PY3 else code) |
|||
|
|||
pkg_name = module_name.partition('/')[0] |
|||
# make sure the module is installed |
|||
assert subprocess.call( |
|||
'cd %s;npm install %s' % (repr(DIRNAME), pkg_name), |
|||
shell=True, |
|||
cwd=DIRNAME |
|||
) == 0, 'Could not install the required module: ' + pkg_name |
|||
|
|||
# convert the module |
|||
assert subprocess.call( |
|||
'''node -e "(require('browserify')('./%s').bundle(function (err,data) {fs.writeFile('%s', require('babel-core').transform(data, {'presets': require('babel-preset-es2015')}).code, ()=>{});}))"''' |
|||
% (in_file_name, out_file_name), |
|||
shell=True, |
|||
cwd=DIRNAME, |
|||
) == 0, 'Error when converting module to the js bundle' |
|||
|
|||
os.remove(os.path.join(DIRNAME, in_file_name)) |
|||
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)) |
|||
|
|||
js_code += GET_FROM_GLOBALS_FUNC |
|||
js_code += ';var %s = getFromGlobals(%s);%s' % ( |
|||
var_name, repr(module_name), var_name) |
|||
print('Please wait, translating...') |
|||
py_code = translate_js(js_code) |
|||
|
|||
dirname = os.path.dirname( |
|||
os.path.join(PY_NODE_MODULES_PATH, module_filename)) |
|||
if not os.path.isdir(dirname): |
|||
os.makedirs(dirname) |
|||
with open(os.path.join(PY_NODE_MODULES_PATH, module_filename), |
|||
'wb') as f: |
|||
f.write(py_code.encode('utf-8') if six.PY3 else py_code) |
|||
else: |
|||
with codecs.open( |
|||
os.path.join(PY_NODE_MODULES_PATH, module_filename), "r", |
|||
"utf-8") as f: |
|||
py_code = f.read() |
|||
|
|||
context = {} |
|||
exec (py_code, context) |
|||
return context['var'][var_name].to_py() |
@ -1,16 +1,10 @@ |
|||
|
|||
|
|||
class BooleanPrototype: |
|||
def toString(): |
|||
if this.Class!='Boolean': |
|||
raise this.Js(TypeError)('this must be a boolean') |
|||
return 'true' if this.value else 'false' |
|||
|
|||
def valueOf(): |
|||
if this.Class!='Boolean': |
|||
raise this.Js(TypeError)('this must be a boolean') |
|||
return this.value |
|||
|
|||
|
|||
|
|||
|
|||
class BooleanPrototype: |
|||
def toString(): |
|||
if this.Class != 'Boolean': |
|||
raise this.Js(TypeError)('this must be a boolean') |
|||
return 'true' if this.value else 'false' |
|||
|
|||
def valueOf(): |
|||
if this.Class != 'Boolean': |
|||
raise this.Js(TypeError)('this must be a boolean') |
|||
return this.value |
|||
|
@ -1,10 +1,10 @@ |
|||
|
|||
class ErrorPrototype: |
|||
def toString(): |
|||
if this.TYPE!='Object': |
|||
raise this.MakeError('TypeError', 'Error.prototype.toString called on non-object') |
|||
name = this.get('name') |
|||
name = 'Error' if name.is_undefined() else name.to_string().value |
|||
msg = this.get('message') |
|||
msg = '' if msg.is_undefined() else msg.to_string().value |
|||
return name + (name and msg and ': ') + msg |
|||
class ErrorPrototype: |
|||
def toString(): |
|||
if this.TYPE != 'Object': |
|||
raise this.MakeError( |
|||
'TypeError', 'Error.prototype.toString called on non-object') |
|||
name = this.get('name') |
|||
name = 'Error' if name.is_undefined() else name.to_string().value |
|||
msg = this.get('message') |
|||
msg = '' if msg.is_undefined() else msg.to_string().value |
|||
return name + (name and msg and ': ') + msg |
|||
|
@ -1,53 +1,52 @@ |
|||
# python 3 support |
|||
import six |
|||
if six.PY3: |
|||
basestring = str |
|||
long = int |
|||
xrange = range |
|||
unicode = str |
|||
|
|||
|
|||
# todo fix apply and bind |
|||
|
|||
class FunctionPrototype: |
|||
def toString(): |
|||
if not this.is_callable(): |
|||
raise TypeError('toString is not generic!') |
|||
args = ', '.join(this.code.__code__.co_varnames[:this.argcount]) |
|||
return 'function %s(%s) '%(this.func_name, args)+this.source |
|||
|
|||
def call(): |
|||
arguments_ = arguments |
|||
if not len(arguments): |
|||
obj = this.Js(None) |
|||
else: |
|||
obj = arguments[0] |
|||
if len(arguments)<=1: |
|||
args = () |
|||
else: |
|||
args = tuple([arguments_[e] for e in xrange(1, len(arguments_))]) |
|||
return this.call(obj, args) |
|||
|
|||
def apply(): |
|||
if not len(arguments): |
|||
obj = this.Js(None) |
|||
else: |
|||
obj = arguments[0] |
|||
if len(arguments)<=1: |
|||
args = () |
|||
else: |
|||
appl = arguments[1] |
|||
args = tuple([appl[e] for e in xrange(len(appl))]) |
|||
return this.call(obj, args) |
|||
|
|||
def bind(thisArg): |
|||
target = this |
|||
if not target.is_callable(): |
|||
raise this.MakeError('Object must be callable in order to be used with bind method') |
|||
if len(arguments) <= 1: |
|||
args = () |
|||
else: |
|||
args = tuple([arguments[e] for e in xrange(1, len(arguments))]) |
|||
return this.PyJsBoundFunction(target, thisArg, args) |
|||
|
|||
|
|||
# python 3 support |
|||
import six |
|||
if six.PY3: |
|||
basestring = str |
|||
long = int |
|||
xrange = range |
|||
unicode = str |
|||
|
|||
# todo fix apply and bind |
|||
|
|||
|
|||
class FunctionPrototype: |
|||
def toString(): |
|||
if not this.is_callable(): |
|||
raise TypeError('toString is not generic!') |
|||
args = ', '.join(this.code.__code__.co_varnames[:this.argcount]) |
|||
return 'function %s(%s) ' % (this.func_name, args) + this.source |
|||
|
|||
def call(): |
|||
arguments_ = arguments |
|||
if not len(arguments): |
|||
obj = this.Js(None) |
|||
else: |
|||
obj = arguments[0] |
|||
if len(arguments) <= 1: |
|||
args = () |
|||
else: |
|||
args = tuple([arguments_[e] for e in xrange(1, len(arguments_))]) |
|||
return this.call(obj, args) |
|||
|
|||
def apply(): |
|||
if not len(arguments): |
|||
obj = this.Js(None) |
|||
else: |
|||
obj = arguments[0] |
|||
if len(arguments) <= 1: |
|||
args = () |
|||
else: |
|||
appl = arguments[1] |
|||
args = tuple([appl[e] for e in xrange(len(appl))]) |
|||
return this.call(obj, args) |
|||
|
|||
def bind(thisArg): |
|||
target = this |
|||
if not target.is_callable(): |
|||
raise this.MakeError( |
|||
'Object must be callable in order to be used with bind method') |
|||
if len(arguments) <= 1: |
|||
args = () |
|||
else: |
|||
args = tuple([arguments[e] for e in xrange(1, len(arguments))]) |
|||
return this.PyJsBoundFunction(target, thisArg, args) |
|||
|
@ -1,36 +1,28 @@ |
|||
|
|||
class ObjectPrototype: |
|||
def toString(): |
|||
return '[object %s]'%this.Class |
|||
|
|||
def valueOf(): |
|||
return this.to_object() |
|||
|
|||
|
|||
def toLocaleString(): |
|||
return this.callprop('toString') |
|||
|
|||
def hasOwnProperty(prop): |
|||
return this.get_own_property(prop.to_string().value) is not None |
|||
|
|||
def isPrototypeOf(obj): |
|||
#a bit stupid specification but well |
|||
# for example Object.prototype.isPrototypeOf.call((5).__proto__, 5) gives false |
|||
if not obj.is_object(): |
|||
return False |
|||
while 1: |
|||
obj = obj.prototype |
|||
if obj is None or obj.is_null(): |
|||
return False |
|||
if obj is this: |
|||
return True |
|||
|
|||
def propertyIsEnumerable(prop): |
|||
cand = this.own.get(prop.to_string().value) |
|||
return cand is not None and cand.get('enumerable') |
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
class ObjectPrototype: |
|||
def toString(): |
|||
return '[object %s]' % this.Class |
|||
|
|||
def valueOf(): |
|||
return this.to_object() |
|||
|
|||
def toLocaleString(): |
|||
return this.callprop('toString') |
|||
|
|||
def hasOwnProperty(prop): |
|||
return this.get_own_property(prop.to_string().value) is not None |
|||
|
|||
def isPrototypeOf(obj): |
|||
#a bit stupid specification but well |
|||
# for example Object.prototype.isPrototypeOf.call((5).__proto__, 5) gives false |
|||
if not obj.is_object(): |
|||
return False |
|||
while 1: |
|||
obj = obj.prototype |
|||
if obj is None or obj.is_null(): |
|||
return False |
|||
if obj is this: |
|||
return True |
|||
|
|||
def propertyIsEnumerable(prop): |
|||
cand = this.own.get(prop.to_string().value) |
|||
return cand is not None and cand.get('enumerable') |
|||
|
@ -1,307 +1,306 @@ |
|||
# -*- coding: utf-8 -*- |
|||
from .jsregexp import Exec |
|||
import re |
|||
DIGS = set('0123456789') |
|||
WHITE = u"\u0009\u000A\u000B\u000C\u000D\u0020\u00A0\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF" |
|||
|
|||
def replacement_template(rep, source, span, npar): |
|||
"""Takes the replacement template and some info about the match and returns filled template |
|||
""" |
|||
n = 0 |
|||
res = '' |
|||
while n < len(rep)-1: |
|||
char = rep[n] |
|||
if char=='$': |
|||
if rep[n+1]=='$': |
|||
res += '$' |
|||
n += 2 |
|||
continue |
|||
elif rep[n+1]=='`': |
|||
# replace with string that is BEFORE match |
|||
res += source[:span[0]] |
|||
n += 2 |
|||
continue |
|||
elif rep[n+1]=='\'': |
|||
# replace with string that is AFTER match |
|||
res += source[span[1]:] |
|||
n += 2 |
|||
continue |
|||
elif rep[n+1] in DIGS: |
|||
dig = rep[n+1] |
|||
if n+2<len(rep) and rep[n+2] in DIGS: |
|||
dig += rep[n+2] |
|||
num = int(dig) |
|||
# we will not do any replacements if we dont have this npar or dig is 0 |
|||
if not num or num>len(npar): |
|||
res += '$'+dig |
|||
else: |
|||
# None - undefined has to be replaced with '' |
|||
res += npar[num-1] if npar[num-1] else '' |
|||
n += 1 + len(dig) |
|||
continue |
|||
res += char |
|||
n += 1 |
|||
if n<len(rep): |
|||
res += rep[-1] |
|||
return res |
|||
|
|||
|
|||
################################################### |
|||
|
|||
class StringPrototype: |
|||
def toString(): |
|||
if this.Class!='String': |
|||
raise this.MakeError('TypeError', 'String.prototype.toString is not generic') |
|||
return this.value |
|||
|
|||
def valueOf(): |
|||
if this.Class!='String': |
|||
raise this.MakeError('TypeError', 'String.prototype.valueOf is not generic') |
|||
return this.value |
|||
|
|||
def charAt(pos): |
|||
this.cok() |
|||
pos = pos.to_int() |
|||
s = this.to_string() |
|||
if 0<= pos < len(s.value): |
|||
char = s.value[pos] |
|||
if char not in s.CHAR_BANK: |
|||
s.Js(char) # add char to char bank |
|||
return s.CHAR_BANK[char] |
|||
return s.CHAR_BANK[''] |
|||
|
|||
def charCodeAt(pos): |
|||
this.cok() |
|||
pos = pos.to_int() |
|||
s = this.to_string() |
|||
if 0<= pos < len(s.value): |
|||
return s.Js(ord(s.value[pos])) |
|||
return s.NaN |
|||
|
|||
def concat(): |
|||
this.cok() |
|||
s = this.to_string() |
|||
res = s.value |
|||
for e in arguments.to_list(): |
|||
res += e.to_string().value |
|||
return res |
|||
|
|||
def indexOf(searchString, position): |
|||
this.cok() |
|||
s = this.to_string().value |
|||
search = searchString.to_string().value |
|||
pos = position.to_int() |
|||
return this.Js(s.find(search, min(max(pos, 0), len(s))) ) |
|||
|
|||
def lastIndexOf(searchString, position): |
|||
this.cok() |
|||
s = this.to_string().value |
|||
search = searchString.to_string().value |
|||
pos = position.to_number() |
|||
pos = 10**15 if pos.is_nan() else pos.to_int() |
|||
return s.rfind(search, 0, min(max(pos, 0)+1, len(s))) |
|||
|
|||
def localeCompare(that): |
|||
this.cok() |
|||
s = this.to_string() |
|||
that = that.to_string() |
|||
if s<that: |
|||
return this.Js(-1) |
|||
elif s>that: |
|||
return this.Js(1) |
|||
return this.Js(0) |
|||
|
|||
def match(regexp): |
|||
this.cok() |
|||
s = this.to_string() |
|||
r = this.RegExp(regexp) if regexp.Class!='RegExp' else regexp |
|||
if not r.glob: |
|||
return Exec(r, s) |
|||
r.put('lastIndex', this.Js(0)) |
|||
found = [] |
|||
previous_last_index = 0 |
|||
last_match = True |
|||
while last_match: |
|||
result = Exec(r, s) |
|||
if result.is_null(): |
|||
last_match=False |
|||
else: |
|||
this_index = r.get('lastIndex').value |
|||
if this_index==previous_last_index: |
|||
r.put('lastIndex', this.Js(this_index+1)) |
|||
previous_last_index += 1 |
|||
else: |
|||
previous_last_index = this_index |
|||
matchStr = result.get('0') |
|||
found.append(matchStr) |
|||
if not found: |
|||
return this.null |
|||
return found |
|||
|
|||
|
|||
def replace(searchValue, replaceValue): |
|||
# VERY COMPLICATED. to check again. |
|||
this.cok() |
|||
string = this.to_string() |
|||
s = string.value |
|||
res = '' |
|||
if not replaceValue.is_callable(): |
|||
replaceValue = replaceValue.to_string().value |
|||
func = False |
|||
else: |
|||
func = True |
|||
# Replace all ( global ) |
|||
if searchValue.Class == 'RegExp' and searchValue.glob: |
|||
last = 0 |
|||
for e in re.finditer(searchValue.pat, s): |
|||
res += s[last:e.span()[0]] |
|||
if func: |
|||
# prepare arguments for custom func (replaceValue) |
|||
args = (e.group(),) + e.groups() + (e.span()[1], string) |
|||
# convert all types to JS |
|||
args = map(this.Js, args) |
|||
res += replaceValue(*args).to_string().value |
|||
else: |
|||
res += replacement_template(replaceValue, s, e.span(), e.groups()) |
|||
last = e.span()[1] |
|||
res += s[last:] |
|||
return this.Js(res) |
|||
elif searchValue.Class=='RegExp': |
|||
e = re.search(searchValue.pat, s) |
|||
if e is None: |
|||
return string |
|||
span = e.span() |
|||
pars = e.groups() |
|||
match = e.group() |
|||
else: |
|||
match = searchValue.to_string().value |
|||
ind = s.find(match) |
|||
if ind==-1: |
|||
return string |
|||
span = ind, ind + len(match) |
|||
pars = () |
|||
res = s[:span[0]] |
|||
if func: |
|||
args = (match,) + pars + (span[1], string) |
|||
# convert all types to JS |
|||
this_ = this |
|||
args = tuple([this_.Js(x) for x in args]) |
|||
res += replaceValue(*args).to_string().value |
|||
else: |
|||
res += replacement_template(replaceValue, s, span, pars) |
|||
res += s[span[1]:] |
|||
return res |
|||
|
|||
def search(regexp): |
|||
this.cok() |
|||
string = this.to_string() |
|||
if regexp.Class=='RegExp': |
|||
rx = regexp |
|||
else: |
|||
rx = this.RegExp(regexp) |
|||
res = re.search(rx.pat, string.value) |
|||
if res is not None: |
|||
return this.Js(res.span()[0]) |
|||
return -1 |
|||
|
|||
def slice(start, end): |
|||
this.cok() |
|||
s = this.to_string() |
|||
start = start.to_int() |
|||
length = len(s.value) |
|||
end = length if end.is_undefined() else end.to_int() |
|||
#From = max(length+start, 0) if start<0 else min(length, start) |
|||
#To = max(length+end, 0) if end<0 else min(length, end) |
|||
return s.value[start:end] |
|||
|
|||
|
|||
def split (separator, limit): |
|||
# its a bit different that re.split! |
|||
this.cok() |
|||
S = this.to_string() |
|||
s = S.value |
|||
lim = 2**32-1 if limit.is_undefined() else limit.to_uint32() |
|||
if not lim: |
|||
return [] |
|||
if separator.is_undefined(): |
|||
return [s] |
|||
len_s = len(s) |
|||
res = [] |
|||
R = separator if separator.Class=='RegExp' else separator.to_string() |
|||
if not len_s: |
|||
if SplitMatch(s, 0, R) is None: |
|||
return [S] |
|||
return [] |
|||
p = q = 0 |
|||
while q!=len_s: |
|||
e, cap = SplitMatch(s, q, R) |
|||
if e is None or e==p: |
|||
q += 1 |
|||
continue |
|||
res.append(s[p:q]) |
|||
p = q = e |
|||
if len(res)==lim: |
|||
return res |
|||
for element in cap: |
|||
res.append(this.Js(element)) |
|||
if len(res)==lim: |
|||
return res |
|||
res.append(s[p:]) |
|||
return res |
|||
|
|||
|
|||
def substring (start, end): |
|||
this.cok() |
|||
s = this.to_string().value |
|||
start = start.to_int() |
|||
length = len(s) |
|||
end = length if end.is_undefined() else end.to_int() |
|||
fstart = min(max(start, 0), length) |
|||
fend = min(max(end, 0), length) |
|||
return this.Js(s[min(fstart, fend):max(fstart, fend)]) |
|||
|
|||
def substr(start, length): |
|||
#I hate this function and its description in specification |
|||
r1 = this.to_string().value |
|||
r2 = start.to_int() |
|||
r3 = 10**20 if length.is_undefined() else length.to_int() |
|||
r4 = len(r1) |
|||
r5 = r2 if r2>=0 else max(0, r2+r4) |
|||
r6 = min(max(r3 ,0), r4 - r5) |
|||
if r6<=0: |
|||
return '' |
|||
return r1[r5:r5+r6] |
|||
|
|||
def toLowerCase(): |
|||
this.cok() |
|||
return this.Js(this.to_string().value.lower()) |
|||
|
|||
def toLocaleLowerCase(): |
|||
this.cok() |
|||
return this.Js(this.to_string().value.lower()) |
|||
|
|||
def toUpperCase(): |
|||
this.cok() |
|||
return this.Js(this.to_string().value.upper()) |
|||
|
|||
def toLocaleUpperCase(): |
|||
this.cok() |
|||
return this.Js(this.to_string().value.upper()) |
|||
|
|||
def trim(): |
|||
this.cok() |
|||
return this.Js(this.to_string().value.strip(WHITE)) |
|||
|
|||
|
|||
|
|||
|
|||
def SplitMatch(s, q, R): |
|||
# s is Py String to match, q is the py int match start and R is Js RegExp or String. |
|||
if R.Class=='RegExp': |
|||
res = R.match(s, q) |
|||
return (None, ()) if res is None else (res.span()[1], res.groups()) |
|||
# R is just a string |
|||
if s[q:].startswith(R.value): |
|||
return q+len(R.value), () |
|||
return None, () |
|||
|
|||
# -*- coding: utf-8 -*- |
|||
from .jsregexp import Exec |
|||
import re |
|||
DIGS = set('0123456789') |
|||
WHITE = u"\u0009\u000A\u000B\u000C\u000D\u0020\u00A0\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF" |
|||
|
|||
|
|||
def replacement_template(rep, source, span, npar): |
|||
"""Takes the replacement template and some info about the match and returns filled template |
|||
""" |
|||
n = 0 |
|||
res = '' |
|||
while n < len(rep) - 1: |
|||
char = rep[n] |
|||
if char == '$': |
|||
if rep[n + 1] == '$': |
|||
res += '$' |
|||
n += 2 |
|||
continue |
|||
elif rep[n + 1] == '`': |
|||
# replace with string that is BEFORE match |
|||
res += source[:span[0]] |
|||
n += 2 |
|||
continue |
|||
elif rep[n + 1] == '\'': |
|||
# replace with string that is AFTER match |
|||
res += source[span[1]:] |
|||
n += 2 |
|||
continue |
|||
elif rep[n + 1] in DIGS: |
|||
dig = rep[n + 1] |
|||
if n + 2 < len(rep) and rep[n + 2] in DIGS: |
|||
dig += rep[n + 2] |
|||
num = int(dig) |
|||
# we will not do any replacements if we dont have this npar or dig is 0 |
|||
if not num or num > len(npar): |
|||
res += '$' + dig |
|||
else: |
|||
# None - undefined has to be replaced with '' |
|||
res += npar[num - 1] if npar[num - 1] else '' |
|||
n += 1 + len(dig) |
|||
continue |
|||
res += char |
|||
n += 1 |
|||
if n < len(rep): |
|||
res += rep[-1] |
|||
return res |
|||
|
|||
|
|||
################################################### |
|||
|
|||
|
|||
class StringPrototype: |
|||
def toString(): |
|||
if this.Class != 'String': |
|||
raise this.MakeError('TypeError', |
|||
'String.prototype.toString is not generic') |
|||
return this.value |
|||
|
|||
def valueOf(): |
|||
if this.Class != 'String': |
|||
raise this.MakeError('TypeError', |
|||
'String.prototype.valueOf is not generic') |
|||
return this.value |
|||
|
|||
def charAt(pos): |
|||
this.cok() |
|||
pos = pos.to_int() |
|||
s = this.to_string() |
|||
if 0 <= pos < len(s.value): |
|||
char = s.value[pos] |
|||
if char not in s.CHAR_BANK: |
|||
s.Js(char) # add char to char bank |
|||
return s.CHAR_BANK[char] |
|||
return s.CHAR_BANK[''] |
|||
|
|||
def charCodeAt(pos): |
|||
this.cok() |
|||
pos = pos.to_int() |
|||
s = this.to_string() |
|||
if 0 <= pos < len(s.value): |
|||
return s.Js(ord(s.value[pos])) |
|||
return s.NaN |
|||
|
|||
def concat(): |
|||
this.cok() |
|||
s = this.to_string() |
|||
res = s.value |
|||
for e in arguments.to_list(): |
|||
res += e.to_string().value |
|||
return res |
|||
|
|||
def indexOf(searchString, position): |
|||
this.cok() |
|||
s = this.to_string().value |
|||
search = searchString.to_string().value |
|||
pos = position.to_int() |
|||
return this.Js(s.find(search, min(max(pos, 0), len(s)))) |
|||
|
|||
def lastIndexOf(searchString, position): |
|||
this.cok() |
|||
s = this.to_string().value |
|||
search = searchString.to_string().value |
|||
pos = position.to_number() |
|||
pos = 10**15 if pos.is_nan() else pos.to_int() |
|||
return s.rfind(search, 0, min(max(pos, 0) + 1, len(s))) |
|||
|
|||
def localeCompare(that): |
|||
this.cok() |
|||
s = this.to_string() |
|||
that = that.to_string() |
|||
if s < that: |
|||
return this.Js(-1) |
|||
elif s > that: |
|||
return this.Js(1) |
|||
return this.Js(0) |
|||
|
|||
def match(regexp): |
|||
this.cok() |
|||
s = this.to_string() |
|||
r = this.RegExp(regexp) if regexp.Class != 'RegExp' else regexp |
|||
if not r.glob: |
|||
return Exec(r, s) |
|||
r.put('lastIndex', this.Js(0)) |
|||
found = [] |
|||
previous_last_index = 0 |
|||
last_match = True |
|||
while last_match: |
|||
result = Exec(r, s) |
|||
if result.is_null(): |
|||
last_match = False |
|||
else: |
|||
this_index = r.get('lastIndex').value |
|||
if this_index == previous_last_index: |
|||
r.put('lastIndex', this.Js(this_index + 1)) |
|||
previous_last_index += 1 |
|||
else: |
|||
previous_last_index = this_index |
|||
matchStr = result.get('0') |
|||
found.append(matchStr) |
|||
if not found: |
|||
return this.null |
|||
return found |
|||
|
|||
def replace(searchValue, replaceValue): |
|||
# VERY COMPLICATED. to check again. |
|||
this.cok() |
|||
string = this.to_string() |
|||
s = string.value |
|||
res = '' |
|||
if not replaceValue.is_callable(): |
|||
replaceValue = replaceValue.to_string().value |
|||
func = False |
|||
else: |
|||
func = True |
|||
# Replace all ( global ) |
|||
if searchValue.Class == 'RegExp' and searchValue.glob: |
|||
last = 0 |
|||
for e in re.finditer(searchValue.pat, s): |
|||
res += s[last:e.span()[0]] |
|||
if func: |
|||
# prepare arguments for custom func (replaceValue) |
|||
args = (e.group(), ) + e.groups() + (e.span()[1], string) |
|||
# convert all types to JS |
|||
args = map(this.Js, args) |
|||
res += replaceValue(*args).to_string().value |
|||
else: |
|||
res += replacement_template(replaceValue, s, e.span(), |
|||
e.groups()) |
|||
last = e.span()[1] |
|||
res += s[last:] |
|||
return this.Js(res) |
|||
elif searchValue.Class == 'RegExp': |
|||
e = re.search(searchValue.pat, s) |
|||
if e is None: |
|||
return string |
|||
span = e.span() |
|||
pars = e.groups() |
|||
match = e.group() |
|||
else: |
|||
match = searchValue.to_string().value |
|||
ind = s.find(match) |
|||
if ind == -1: |
|||
return string |
|||
span = ind, ind + len(match) |
|||
pars = () |
|||
res = s[:span[0]] |
|||
if func: |
|||
args = (match, ) + pars + (span[1], string) |
|||
# convert all types to JS |
|||
this_ = this |
|||
args = tuple([this_.Js(x) for x in args]) |
|||
res += replaceValue(*args).to_string().value |
|||
else: |
|||
res += replacement_template(replaceValue, s, span, pars) |
|||
res += s[span[1]:] |
|||
return res |
|||
|
|||
def search(regexp): |
|||
this.cok() |
|||
string = this.to_string() |
|||
if regexp.Class == 'RegExp': |
|||
rx = regexp |
|||
else: |
|||
rx = this.RegExp(regexp) |
|||
res = re.search(rx.pat, string.value) |
|||
if res is not None: |
|||
return this.Js(res.span()[0]) |
|||
return -1 |
|||
|
|||
def slice(start, end): |
|||
this.cok() |
|||
s = this.to_string() |
|||
start = start.to_int() |
|||
length = len(s.value) |
|||
end = length if end.is_undefined() else end.to_int() |
|||
#From = max(length+start, 0) if start<0 else min(length, start) |
|||
#To = max(length+end, 0) if end<0 else min(length, end) |
|||
return s.value[start:end] |
|||
|
|||
def split(separator, limit): |
|||
# its a bit different that re.split! |
|||
this.cok() |
|||
S = this.to_string() |
|||
s = S.value |
|||
lim = 2**32 - 1 if limit.is_undefined() else limit.to_uint32() |
|||
if not lim: |
|||
return [] |
|||
if separator.is_undefined(): |
|||
return [s] |
|||
len_s = len(s) |
|||
res = [] |
|||
R = separator if separator.Class == 'RegExp' else separator.to_string() |
|||
if not len_s: |
|||
if SplitMatch(s, 0, R) is None: |
|||
return [S] |
|||
return [] |
|||
p = q = 0 |
|||
while q != len_s: |
|||
e, cap = SplitMatch(s, q, R) |
|||
if e is None or e == p: |
|||
q += 1 |
|||
continue |
|||
res.append(s[p:q]) |
|||
p = q = e |
|||
if len(res) == lim: |
|||
return res |
|||
for element in cap: |
|||
res.append(this.Js(element)) |
|||
if len(res) == lim: |
|||
return res |
|||
res.append(s[p:]) |
|||
return res |
|||
|
|||
def substring(start, end): |
|||
this.cok() |
|||
s = this.to_string().value |
|||
start = start.to_int() |
|||
length = len(s) |
|||
end = length if end.is_undefined() else end.to_int() |
|||
fstart = min(max(start, 0), length) |
|||
fend = min(max(end, 0), length) |
|||
return this.Js(s[min(fstart, fend):max(fstart, fend)]) |
|||
|
|||
def substr(start, length): |
|||
#I hate this function and its description in specification |
|||
r1 = this.to_string().value |
|||
r2 = start.to_int() |
|||
r3 = 10**20 if length.is_undefined() else length.to_int() |
|||
r4 = len(r1) |
|||
r5 = r2 if r2 >= 0 else max(0, r2 + r4) |
|||
r6 = min(max(r3, 0), r4 - r5) |
|||
if r6 <= 0: |
|||
return '' |
|||
return r1[r5:r5 + r6] |
|||
|
|||
def toLowerCase(): |
|||
this.cok() |
|||
return this.Js(this.to_string().value.lower()) |
|||
|
|||
def toLocaleLowerCase(): |
|||
this.cok() |
|||
return this.Js(this.to_string().value.lower()) |
|||
|
|||
def toUpperCase(): |
|||
this.cok() |
|||
return this.Js(this.to_string().value.upper()) |
|||
|
|||
def toLocaleUpperCase(): |
|||
this.cok() |
|||
return this.Js(this.to_string().value.upper()) |
|||
|
|||
def trim(): |
|||
this.cok() |
|||
return this.Js(this.to_string().value.strip(WHITE)) |
|||
|
|||
|
|||
def SplitMatch(s, q, R): |
|||
# s is Py String to match, q is the py int match start and R is Js RegExp or String. |
|||
if R.Class == 'RegExp': |
|||
res = R.match(s, q) |
|||
return (None, ()) if res is None else (res.span()[1], res.groups()) |
|||
# R is just a string |
|||
if s[q:].startswith(R.value): |
|||
return q + len(R.value), () |
|||
return None, () |
|||
|
@ -0,0 +1 @@ |
|||
"""this package contains all the npm modules translated by js2py via node import""" |
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue