Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 77 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
PYTHON ?= python3
# CURDIR is reliable with spaces; lastword(MAKEFILE_LIST) breaks on "Web Browser/..."
ROOT := $(CURDIR)

export PYTHONPATH := $(ROOT)

.PHONY: help test test-simple test-es-all test-es6 test-es7 test-es8 test-es9 test-es10 test-es11 test-es12 test-es13 test-es14 test-es15 test-es16 test-esnext test-language test-all

help:
@echo "Js2Py test targets:"
@echo " make test Run quick integration tests (default)"
@echo " make test-simple Run simple_test.py (ES5 + ES6 smoke tests)"
@echo " make test-es_ Run all tests/test_es*.py tests"
@echo " make test-es6 Run tests/test_es6.py"
@echo " make test-es7 Run tests/test_es7.py"
@echo " make test-es8 Run tests/test_es8.py"
@echo " make test-es9 Run tests/test_es9.py"
@echo " make test-es10 Run tests/test_es10.py"
@echo " make test-es11 Run tests/test_es11.py"
@echo " make test-es12 Run tests/test_es12.py"
@echo " make test-es13 Run tests/test_es13.py"
@echo " make test-es14 Run tests/test_es14.py"
@echo " make test-es15 Run tests/test_es15.py"
@echo " make test-es16 Run tests/test_es16.py"
@echo " make test-esnext Run tests/test_esnext.py"
@echo " make test-language Run ES5.1 language suite (tests/run.py, slow)"
@echo " make test-all Run quick tests and the language suite"

test: test-simple test-es_
@:

test-simple:
PYTHONPATH="$(ROOT)" $(PYTHON) "$(ROOT)/simple_test.py"

test-es_: test-es6 test-es7 test-es8 test-es9 test-es10 test-es11 test-es12 test-es13 test-es14 test-es15 test-es16 test-esnext

test-es6:
PYTHONPATH="$(ROOT)" $(PYTHON) "$(ROOT)/tests/test_es6.py"

test-es7:
PYTHONPATH="$(ROOT)" $(PYTHON) "$(ROOT)/tests/test_es7.py"

test-es8:
PYTHONPATH="$(ROOT)" $(PYTHON) "$(ROOT)/tests/test_es8.py"

test-es9:
PYTHONPATH="$(ROOT)" $(PYTHON) "$(ROOT)/tests/test_es9.py"

test-es10:
PYTHONPATH="$(ROOT)" $(PYTHON) "$(ROOT)/tests/test_es10.py"

test-es11:
PYTHONPATH="$(ROOT)" $(PYTHON) "$(ROOT)/tests/test_es11.py"

test-es12:
PYTHONPATH="$(ROOT)" $(PYTHON) "$(ROOT)/tests/test_es12.py"

test-es13:
PYTHONPATH="$(ROOT)" $(PYTHON) "$(ROOT)/tests/test_es13.py"

test-es14:
PYTHONPATH="$(ROOT)" $(PYTHON) "$(ROOT)/tests/test_es14.py"

test-es15:
PYTHONPATH="$(ROOT)" $(PYTHON) "$(ROOT)/tests/test_es15.py"

test-es16:
PYTHONPATH="$(ROOT)" $(PYTHON) "$(ROOT)/tests/test_es16.py"

test-esnext:
PYTHONPATH="$(ROOT)" $(PYTHON) "$(ROOT)/tests/test_esnext.py"

test-language:
@test -f "$(ROOT)/tests/node_failed.txt" || touch "$(ROOT)/tests/node_failed.txt"
cd "$(ROOT)/tests" && PYTHONPATH="$(ROOT)" $(PYTHON) run.py

test-all: test test-language
8 changes: 7 additions & 1 deletion js2py/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,13 @@
__all__ = [
'EvalJs', 'translate_js', 'import_js', 'eval_js', 'parse_js',
'translate_file', 'run_file', 'disable_pyimport', 'eval_js6',
'translate_js6', 'PyJsException', 'get_file_contents',
'translate_js6', 'eval_js7', 'translate_js7', 'eval_js8', 'translate_js8',
'eval_js9', 'translate_js9', 'eval_js10', 'translate_js10',
'eval_js11', 'translate_js11', 'eval_js12', 'translate_js12',
'eval_js13', 'translate_js13', 'eval_js14', 'translate_js14',
'eval_js15', 'translate_js15', 'eval_js16', 'translate_js16',
'eval_jsnext', 'translate_jsnext', 'drain_event_loop',
'PyJsException', 'get_file_contents',
'write_file_contents', 'require'
]

Expand Down
59 changes: 56 additions & 3 deletions js2py/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -1150,7 +1150,10 @@ def get(self, prop, throw=True):
# fast local scope
cand = self.own.get(prop)
if cand is None:
return self.prototype.get(prop, throw)
parent = self.prototype
if isinstance(parent, Scope):
return parent.get(prop, throw)
return parent.get(prop)
return cand
# slow, global scope
if prop not in self.own:
Expand Down Expand Up @@ -1369,15 +1372,35 @@ def __repr__(self):
ObjectPrototype = PyJsObject()


def _js_argcount(fcode):
"""Number of JavaScript parameters (excluding this, arguments, and var)."""
names = fcode.co_varnames[:fcode.co_argcount]
if (len(names) >= 3 and names[-1] == 'var' and names[-3] == 'this' and
names[-2] == 'arguments'):
return len(names) - 3
if len(names) >= 2 and names[-2] == 'this' and names[-1] == 'arguments':
return len(names) - 2
return fcode.co_argcount - 2


#Function
class PyJsFunction(PyJs):
Class = 'Function'

def __init__(self, func, prototype=None, extensible=True, source=None):
cand = fix_js_args(func)
has_scope = cand is func
func = cand
self.argcount = six.get_function_code(func).co_argcount - 2 - has_scope
fcode = six.get_function_code(func)
fargs = fcode.co_varnames[fcode.co_argcount - 2:fcode.co_argcount]
if fargs == ('this', 'arguments') or fargs == ('arguments', 'var'):
self._js_global_this = False
self.argcount = _js_argcount(fcode)
elif cand is func:
self._js_global_this = True
self.argcount = fcode.co_argcount
else:
self._js_global_this = False
self.argcount = fcode.co_argcount - 2
self.code = func
self.source = source if source else '{ [python code] }'
self.func_name = func.__name__ if not func.__name__.startswith(
Expand Down Expand Up @@ -1459,6 +1482,21 @@ def call(self, this, args=()):
args = args[0:arglen]
elif len(args) < arglen:
args += (undefined, ) * (arglen - len(args))
if self._js_global_this:
g = self.code.__globals__
saved = {}
for name, val in (('this', this), ('arguments', arguments)):
if name in g:
saved[name] = g[name]
g[name] = val
try:
return Js(self.code(*args))
finally:
for name, val in six.iteritems(saved):
g[name] = val
for name in ('this', 'arguments'):
if name not in saved:
g.pop(name, None)
args += this, arguments #append extra params to the arg list
try:
return Js(self.code(*args))
Expand Down Expand Up @@ -3006,6 +3044,21 @@ def TypeError(message):
for e in ERROR_NAMES:
define_error_type(e + 'Error')


@Js
def error_is_error(value):
if not value.is_object():
return False
return value.instanceof(Error).to_boolean().value


Error.define_own_property('isError', {
'value': error_is_error,
'writable': True,
'enumerable': False,
'configurable': True
})

##############################################################################
# Import and fill prototypes here.

Expand Down
58 changes: 58 additions & 0 deletions js2py/constructors/jsiterator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
from ..base import *


def _iterable_kind(iterable):
obj = iterable.to_object()
next_fn = obj.get('next')
if next_fn.is_callable():
return 'iterator', obj
length = obj.get('length')
if length.TYPE == 'Number':
return 'array', obj
raise MakeError('TypeError', 'Iterator.concat requires iterables')


@Js
def iterator_concat():
specs = []
for i in range(len(arguments)):
specs.append(_iterable_kind(arguments[i]))
state = {'spec_idx': 0, 'index': 0}

@Js
def next_method():
while state['spec_idx'] < len(specs):
kind, data = specs[state['spec_idx']]
if kind == 'array':
idx = state['index']
length = data.get('length').to_uint32()
if idx < length:
if data.has_property(str(idx)):
val = data.get(str(idx))
else:
val = undefined
state['index'] += 1
result = PyJsObject(prototype=ObjectPrototype)
result.put('value', val)
result.put('done', false)
return result
state['spec_idx'] += 1
state['index'] = 0
continue
result = data.callprop('next')
if result.get('done').to_boolean().value:
state['spec_idx'] += 1
continue
return result
result = PyJsObject(prototype=ObjectPrototype)
result.put('value', undefined)
result.put('done', true)
return result

iterator = PyJsObject(prototype=ObjectPrototype)
iterator.put('next', next_method)
return iterator


Iterator = PyJsObject(prototype=ObjectPrototype)
Iterator.put('concat', Js(iterator_concat))
12 changes: 12 additions & 0 deletions js2py/constructors/jsmath.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,5 +153,17 @@ def max():
def random():
return random.random()

def sumPrecise():
import math
values = []
for i in range(len(arguments)):
n = arguments[i]
if n.TYPE != 'Number':
raise MakeError('TypeError', 'Math.sumPrecise requires numbers')
values.append(n.to_number().value)
if not values:
return +0.0
return math.fsum(values)


fill_prototype(Math, MathFunctions, default_attrs)
79 changes: 79 additions & 0 deletions js2py/constructors/jsobject.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,85 @@ def keys(obj):
raise MakeError('TypeError', 'Object.keys called on non-object')
return [e for e, d in six.iteritems(obj.own) if d.get('enumerable')]

def values(obj):
if not obj.is_object():
raise MakeError('TypeError', 'Object.values called on non-object')
return [obj.get(k) for k, d in six.iteritems(obj.own)
if d.get('enumerable')]

def entries(obj):
if not obj.is_object():
raise MakeError('TypeError', 'Object.entries called on non-object')
return [[Js(k), obj.get(k)] for k, d in six.iteritems(obj.own)
if d.get('enumerable')]

def assign(target):
if not target.is_object():
raise MakeError('TypeError', 'Object.assign target must be an object')
obj = target.to_object()
for i in range(1, len(arguments)):
src = arguments[i]
if src.is_null() or src.is_undefined():
continue
src_obj = src.to_object()
for name, desc in six.iteritems(src_obj.own):
if desc.get('enumerable'):
obj.put(name, src_obj.get(name))
return obj

def fromEntries(iterable):
obj = PyJsObject(prototype=ObjectPrototype)
items = iterable.to_object()
length = items.get('length')
if length.TYPE == 'Number':
count = length.to_uint32()
for i in range(count):
entry = items.get(str(i))
if entry.TYPE != 'Object':
raise MakeError('TypeError', 'Invalid entry in fromEntries')
key = entry.get('0')
val = entry.get('1')
obj.put(key.to_string().value, val)
return obj
raise MakeError('TypeError', 'Object.fromEntries requires an iterable')

def hasOwn(obj, prop):
if not obj.is_object():
raise MakeError('TypeError', 'Object.hasOwn called on non-object')
return prop.to_string().value in obj.own

def getOwnPropertyDescriptors(obj):
if not obj.is_object():
raise MakeError('TypeError',
'Object.getOwnPropertyDescriptors called on non-object')
result = PyJsObject(prototype=ObjectPrototype)
for name, desc in six.iteritems(obj.own):
desc_obj = PyJsObject(prototype=ObjectPrototype)
for key, val in six.iteritems(desc):
desc_obj.put(key, val)
result.put(name, desc_obj)
return result

def groupBy(items, callbackfn):
if not callbackfn.is_callable():
raise MakeError('TypeError', 'callbackfn must be a function')
items_obj = items.to_object()
length_val = items_obj.get('length')
if length_val.TYPE != 'Number':
raise MakeError('TypeError', 'Object.groupBy requires an iterable')
length = length_val.to_uint32()
groups = {}
for k in range(length):
if items_obj.has_property(str(k)):
value = items_obj.get(str(k))
key = callbackfn.call(undefined, (value, Js(k), items_obj))
key_str = key.to_string().value
groups.setdefault(key_str, []).append(value)
result = PyJsObject(prototype=null)
for key_str, values in six.iteritems(groups):
result.put(key_str, PyJsArray(values, ArrayPrototype))
return result


# add methods attached to Object constructor
fill_prototype(Object, ObjectMethods, default_attrs)
Expand Down
Loading