diff --git a/transcrypt/__main__.py b/transcrypt/__main__.py index f93d8dec5..0132a0561 100644 --- a/transcrypt/__main__.py +++ b/transcrypt/__main__.py @@ -31,7 +31,7 @@ sys.path = [item.replace ('\\', '/') for item in sys.path] # Unload org from a packages dir, if it happens to be there in the CPython installation -sys.modules.pop ('org', None) +sys.modules.pop ('org', None) # Transcrypt needs to find modulesDir before CPython modules, so it will favor Transcrypt modules candidateTranspilationDirs = [modulesDir] + sys.path @@ -49,7 +49,7 @@ # The following imports are needed by Transcrypt itself, not by transpiled or executed user code # They will either reload the previously unloaded org or load org from different location # CPython needs to find modulesDir after CPython modules, so it will favor CPython modules -sys.path.append (modulesDir) +sys.path.append (modulesDir) from org.transcrypt import utils from org.transcrypt import compiler exitCodeNames = ('exitSuccess', 'exitCommandArgsError', 'exitNoLicense', 'exitSourceNotGiven', 'exitCannotRunSource', 'exitSpecificCompileError', 'exitGeneralCompileError') @@ -62,17 +62,17 @@ def main (): def exitHandler (): if exitCode == exitSuccess: - utils.log (True, '\nReady\n\n') + utils.log (True, '\nReady\n\n') else: utils.log (True, '\nAborted\n\n') - + atexit.register (exitHandler) - + def setExitCode (anExitCode): nonlocal exitCode exitCode = anExitCode return exitCode - + try: __envir__ = utils.Any () with tokenize.open (f'{modulesDir}/org/transcrypt/__envir__.js') as envirFile: @@ -81,73 +81,75 @@ def setExitCode (anExitCode): utils.log (True, '\n{} (TM) Python to JavaScript Small Sane Subset Transpiler Version {}\n', __envir__.transpiler_name.capitalize (), __envir__.transpiler_version) utils.log (True, 'Copyright (C) Geatec Engineering. License: Apache 2.0\n\n') - + utils.log (True, '\n') - licensePath = '{}/{}'.format (transpilerDir, 'license_reference.txt') + licensePath = '{}/{}'.format (transpilerDir, 'license_reference.txt') if not os.path.isfile (licensePath): utils.log (True, 'Error: missing license reference file\n') return setExitCode (exitNoLicense) - + utils.commandArgs.parse () - + if utils.commandArgs.license: with open (licensePath) as licenseFile: bar = 80 * '*' utils.log (True, '\n{}\n\n', bar) utils.log (True, '{}\n', licenseFile.read ()) utils.log (True, '{}\n\n', bar) - + if utils.commandArgs.star: webbrowser.open ('https://github.com/TranscryptOrg/Transcrypt') - + if not utils.commandArgs.source: return setExitCode (exitSourceNotGiven) # Should never be here, dealth with by command arg checks already - + if utils.commandArgs.source.endswith ('.py') or utils.commandArgs.source.endswith ('.js'): utils.commandArgs.source = utils.commandArgs.source [:-3] # Ignore extension # This may be done in a more concise way, but it runs deeper than it may seem, so test any changes extensively - + # Prepend paths that are needed by transpiled or executed user code, since they have to be searched first # So user code favors Transcrypt modules over CPython modules - extraDirs = utils.commandArgs.xpath.replace ('#', ' ') .split ('$') if utils.commandArgs.xpath else [] - + extraDirs = sum((p.replace('#', ' ').split("$") + for p in (utils.commandArgs.xpath or [])), start=[]) + sourcePath = utils.commandArgs.source.replace ('\\', '/') # May be absolute or relative, in the latter case it may or may not specify a directory if '/' in sourcePath: # If directory specified sourceDir = sourcePath.rsplit ('/', 1)[0] # Use it as source directory else: # Else sourceDir = os.getcwd () .replace ('\\', '/') # Use current working directory as source directory - + projectDirs = [sourceDir] + extraDirs - + sys.path [0 : 0] = projectDirs - + global transpilationDirs - transpilationDirs [0 : 0] = projectDirs - - __symbols__ = utils.commandArgs.symbols.split ('$') if utils.commandArgs.symbols else [] - + transpilationDirs [0 : 0] = projectDirs + + + __symbols__ = sum((s.split("$") for s in (utils.commandArgs.symbols or [])), start=[]) + if utils.commandArgs.complex: __symbols__.append ('__complex__') if utils.commandArgs.sform: __symbols__.append ('__sform__') - + if utils.commandArgs.xtiny: __symbols__.append ('__xtiny__') - + __symbols__.append ('__py{}.{}__'.format (* sys.version_info [:2])) - + if utils.commandArgs.esv: __symbols__.append ('__esv{}__'.format (utils.commandArgs.esv)) else: __symbols__.append ('__esv{}__'.format (utils.defaultJavaScriptVersion)) - + # Import (ignored when transpiling) late, since commandArgs must be set already from org.transcrypt.stubs.browser import __set_stubsymbols__ - + # Make symbols available to CPython, seems that exec can't do that directly __set_stubsymbols__ (__symbols__) - + if utils.commandArgs.run: try: with open (utils.commandArgs.source + '.py') as sourceFile: @@ -163,25 +165,25 @@ def setExitCode (anExitCode): return setExitCode (exitSuccess) except utils.Error as error: utils.log (True, '\n{}\n', error) - + # Don't log anything else, even in verbose mode, since this would only be confusing if utils.commandArgs.dextex: utils.log (True, '{}\n', traceback.format_exc ()) - + return setExitCode (exitSpecificCompileError) except Exception as exception: utils.log (True, '\n{}', exception) - + # Have to log something else, because a general exception isn't informative enough utils.log (True, '{}\n', traceback.format_exc ()) - + return setExitCode (exitGeneralCompileError) - + except utils.CommandArgsError: return setExitCode (exitCommandArgsError) - + except utils.CommandArgsExit: return setExitCode (exitSuccess) - + if __name__ == '__main__': sys.exit (main ()) diff --git a/transcrypt/development/automated_tests/relimport/autotest.py b/transcrypt/development/automated_tests/relimport/autotest.py new file mode 100644 index 000000000..d4793ee82 --- /dev/null +++ b/transcrypt/development/automated_tests/relimport/autotest.py @@ -0,0 +1,7 @@ +import org.transcrypt.autotester +import rimport + +autoTester = org.transcrypt.autotester.AutoTester () + +autoTester.run(rimport, 'relative import') +autoTester.done() diff --git a/transcrypt/development/automated_tests/relimport/rimport.py b/transcrypt/development/automated_tests/relimport/rimport.py new file mode 100644 index 000000000..80b536638 --- /dev/null +++ b/transcrypt/development/automated_tests/relimport/rimport.py @@ -0,0 +1,6 @@ +import tpackage + + +def run(test): + test.check(type(tpackage.peer2.func).__name__) + test.check(type(tpackage.func1).__name__) diff --git a/transcrypt/development/automated_tests/relimport/tpackage/__init__.py b/transcrypt/development/automated_tests/relimport/tpackage/__init__.py new file mode 100644 index 000000000..1dc36cb36 --- /dev/null +++ b/transcrypt/development/automated_tests/relimport/tpackage/__init__.py @@ -0,0 +1,6 @@ +from . import peer as peer1 +from .peer import func + + +peer2 = peer1 +func1 = func diff --git a/transcrypt/development/automated_tests/relimport/tpackage/peer.py b/transcrypt/development/automated_tests/relimport/tpackage/peer.py new file mode 100644 index 000000000..b9bfa6f12 --- /dev/null +++ b/transcrypt/development/automated_tests/relimport/tpackage/peer.py @@ -0,0 +1,2 @@ +def func(): + pass diff --git a/transcrypt/development/automated_tests/transcrypt/builtin_super/__init__.py b/transcrypt/development/automated_tests/transcrypt/builtin_super/__init__.py index 40d819930..df07ee06b 100644 --- a/transcrypt/development/automated_tests/transcrypt/builtin_super/__init__.py +++ b/transcrypt/development/automated_tests/transcrypt/builtin_super/__init__.py @@ -7,7 +7,7 @@ class R: def __init__ (self, a, b): self.a = a self.b = b - + class A (R): def __init__ (self, a, b, c): super () .__init__ (a, b) @@ -15,10 +15,10 @@ def __init__ (self, a, b, c): def f (self, x, y): show ('A.f:', x, y, self.a, self.b, self.c) - + def g (self, x, y): show ('A.g:', x, y) - + class B (R): def __init__ (self, a, b, d): super () .__init__ (a, b) @@ -26,29 +26,29 @@ def __init__ (self, a, b, d): def f (self, x, y): show ('B.f:', x, y, self.a, self.b, self.d) - + def h (self, x, y): show ('A.h:', x, y, self.a, self.b, self.d) class C (A): def __init__ (self, a, b, c): super () .__init__ (a, b, c) - + def f (self, x, y): super () .f (x, y) show ('C.f:', x, y, self.a, self.b, self.c) - + class D (B): def __init__ (self, a, b, d): super () .__init__ (a, b, d) - + def f (self, x, y): super () .f (x, y) show ('D.f:', x, y, self.a, self.b, self.d) - + # Diamond inheritance, use super () only to call exactly one target method via unique path. # In case of multiple target methods or multiple paths, don't use super (), but refer to ancestor classes explicitly instead - class E (C, D): + class E (C, D): def __init__ (self, a, b, c, d): R.__init__ (self, a, b) # Inherited via multiple legs of a diamond, so call explicitly self.c = c # Could also have used C.__init__, but symmetry preferred @@ -59,15 +59,15 @@ def f (self, x, y): C.f (self, x, y) # Ambiguous part of diamond, don't use super () D.f (self, x, y) # Ambiguous part of diamond, don't use super () show ('E.f:', x, y, self.a, self.b, self.c, self.d) - + def g (self, x, y): super () .g (x, y) # Unique, using super () is OK show ('E.g:', x, y, self.a, self.b, self.c, self.d) - + def h (self, x, y): super () .h (x, y) # Unique, using super () is OK show ('E.h:', x, y, self.a, self.b, self.c, self.d) - + rr = R (100, 200) show ('--1--') @@ -100,3 +100,70 @@ def h (self, x, y): e.f (715, 815) e.g (725, 825) e.h (735, 835) + + run_chain_test(autoTester) + + +def run_chain_test(autoTester): + class Z: + def __init__(self, call_order): + call_order.append("Z") + super().__init__(call_order) + + class X(Z): + def __init__(self, call_order): + call_order.append("X") + super().__init__(call_order) + + class Y: + def __init__(self, call_order): + call_order.append("Y") + super().__init__() + + class W(X, Y): + def __init__(self, call_order): + call_order.append("W") + super().__init__(call_order) + + class U(W): + def __init__(self, call_order): + call_order.append("U") + super().__init__(call_order) + + call_order = [] + U(call_order) + autoTester.check(call_order) + + class Mixin: + def plugin(self, call_order): + call_order.append("Mixin") + super().plugin(call_order) + + class Base: + def __init__(self, call_order): + call_order.append("Base") + + def plugin(self, call_order): + call_order.append("Base") + + class Child(Mixin, Base): + pass + + class GrandChild(Child): + pass + + call_order = [] + mixed = Child(call_order) + autoTester.check (call_order) + + call_order = [] + mixed.plugin(call_order) + autoTester.check (call_order) + + call_order = [] + mixed = GrandChild(call_order) + autoTester.check (call_order) + + call_order = [] + mixed.plugin(call_order) + autoTester.check (call_order) diff --git a/transcrypt/development/automated_tests/transcrypt/metaclasses/__init__.py b/transcrypt/development/automated_tests/transcrypt/metaclasses/__init__.py index 0ff07d2a1..cc3591467 100644 --- a/transcrypt/development/automated_tests/transcrypt/metaclasses/__init__.py +++ b/transcrypt/development/automated_tests/transcrypt/metaclasses/__init__.py @@ -6,17 +6,17 @@ def __new__ (meta, name, bases, attribs): # Using bare {} as attribs parameter to __new__ avoids dict attributes masking regular attributes # For more flexibility use __pragma__ ('js', '{}', '''...''') upperAttribs = {} - + for attribKey in attribs: # Translates to 'for (var attribKey in attribs)' by virtue of __pragma__ ('jsiter'), to iterate over the attributes of a bare JavaScript {} upperAttribs [attribKey if attribKey.startswith ('__') else attribKey.upper ()] = attribs [attribKey] - + __pragma__ ('nojsiter') - + return type.__new__ (meta, name, bases, upperAttribs) class Uppercaser (metaclass = UppercaserMeta): pass - + class Animal (Uppercaser): class Thoughts: quantity = 7 @@ -37,23 +37,40 @@ class Thoughts: def grow (self): return 'Grow' - + class Stone: class Thoughts: quantity = 5 color = 'Gray' - + def be (self): return ('Being') + +class Mixin: + pass + + +class Car(Mixin, Uppercaser): + class Thoughts: + quantity = 4 + + color = 'red' + + def drive(self): + return "drive" + + def run (autoTester): animal = Animal () autoTester.check (animal.THOUGHTS.quantity, Animal.COLOR, animal.COLOR, animal.MOVE ()) - + plant = Plant () autoTester.check (plant.THOUGHTS.quantity, Plant.COLOR, plant.COLOR, plant.GROW ()) - + stone = Stone () autoTester.check (stone.Thoughts.quantity, Stone.color, stone.color, stone.be ()) - \ No newline at end of file + + car = Car() + autoTester.check (car.THOUGHTS.quantity, Car.COLOR, car.COLOR, car.DRIVE ()) diff --git a/transcrypt/modules/bisect/__init__.py b/transcrypt/modules/bisect/__init__.py new file mode 100644 index 000000000..a662e582c --- /dev/null +++ b/transcrypt/modules/bisect/__init__.py @@ -0,0 +1,109 @@ +"""Bisection algorithms.""" + + +def insort_right(a, x, lo=0, hi=None, *, key=None): + """Insert item x in list a, and keep it sorted assuming a is sorted. + + If x is already in a, insert it to the right of the rightmost x. + + Optional args lo (default 0) and hi (default len(a)) bound the + slice of a to be searched. + """ + if key is None: + lo = bisect_right(a, x, lo, hi) + else: + lo = bisect_right(a, key(x), lo, hi, key=key) + a.insert(lo, x) + + +def bisect_right(a, x, lo=0, hi=None, *, key=None): + """Return the index where to insert item x in list a, assuming a is sorted. + + The return value i is such that all e in a[:i] have e <= x, and all e in + a[i:] have e > x. So if x already appears in the list, a.insert(i, x) will + insert just after the rightmost x already there. + + Optional args lo (default 0) and hi (default len(a)) bound the + slice of a to be searched. + """ + + if lo < 0: + raise ValueError('lo must be non-negative') + if hi is None: + hi = len(a) + # Note, the comparison uses "<" to match the + # __lt__() logic in list.sort() and in heapq. + # __pragma__ ("opov") + if key is None: + while lo < hi: + mid = (lo + hi) // 2 + if x < a[mid]: + hi = mid + else: + lo = mid + 1 + else: + while lo < hi: + mid = (lo + hi) // 2 + if x < key(a[mid]): + hi = mid + else: + lo = mid + 1 + # __pragma__ ("noopov") + return lo + + +def insort_left(a, x, lo=0, hi=None, *, key=None): + """Insert item x in list a, and keep it sorted assuming a is sorted. + + If x is already in a, insert it to the left of the leftmost x. + + Optional args lo (default 0) and hi (default len(a)) bound the + slice of a to be searched. + """ + + if key is None: + lo = bisect_left(a, x, lo, hi) + else: + lo = bisect_left(a, key(x), lo, hi, key=key) + a.insert(lo, x) + + +def bisect_left(a, x, lo=0, hi=None, *, key=None): + """Return the index where to insert item x in list a, assuming a is sorted. + + The return value i is such that all e in a[:i] have e < x, and all e in + a[i:] have e >= x. So if x already appears in the list, a.insert(i, x) will + insert just before the leftmost x already there. + + Optional args lo (default 0) and hi (default len(a)) bound the + slice of a to be searched. + """ + + if lo < 0: + raise ValueError('lo must be non-negative') + if hi is None: + hi = len(a) + # Note, the comparison uses "<" to match the + # __lt__() logic in list.sort() and in heapq. + # __pragma__ ("opov") + if key is None: + while lo < hi: + mid = (lo + hi) // 2 + if a[mid] < x: + lo = mid + 1 + else: + hi = mid + else: + while lo < hi: + mid = (lo + hi) // 2 + if key(a[mid]) < x: + lo = mid + 1 + else: + hi = mid + # __pragma__ ("noopov") + return lo + + +# Create aliases +bisect = bisect_right +insort = insort_right diff --git a/transcrypt/modules/collections/__init__.py b/transcrypt/modules/collections/__init__.py new file mode 100644 index 000000000..e979c9aab --- /dev/null +++ b/transcrypt/modules/collections/__init__.py @@ -0,0 +1,29 @@ +# __pragma__ ('skip') +def __pragma__(*args): pass +# __pragma__ ('noskip') + + +__pragma__('js', '{}', ''' +var deque_ = function(src) { + this.push.apply(this, src); + return this +} +deque_.prototype = Object.create(Array.prototype); +deque_.prototype.appendleft = function(item) { + this.unshift(item); +} +deque_.prototype.popleft = function() { + return this.shift(); +} +''') + + +def deque(src=[]): + return __pragma__('js', '{}', "new deque_(src)") + + +__pragma__('js', '{}', ''' +deque_.prototype.__class__ = deque; +deque.__name__ = 'deque'; +deque.__bases__ = [object]; +''') diff --git a/transcrypt/modules/functools/__init__.py b/transcrypt/modules/functools/__init__.py new file mode 100644 index 000000000..a9e89d22e --- /dev/null +++ b/transcrypt/modules/functools/__init__.py @@ -0,0 +1,46 @@ + +class _initial_missing: + pass + + +def reduce(function_, sequence, initial=_initial_missing): + """ + reduce(function, iterable[, initial]) -> value + Apply a function of two arguments cumulatively to the items of a sequence + or iterable, from left to right, so as to reduce the iterable to a single + value. For example, reduce(lambda x, y: x+y, [1, 2, 3, 4, 5]) calculates + ((((1+2)+3)+4)+5). If initial is present, it is placed before the items + of the iterable in the calculation, and serves as a default when the + iterable is empty. + """ + + it = iter(sequence) + + if initial is _initial_missing: + try: + value = next(it) + except StopIteration: + raise TypeError( + "reduce() of empty iterable with no initial value") from None + else: + value = initial + + for element in it: + value = function_(value, element) + + return value + + +def wraps(wrapped): + """Decorator factory to apply update_wrapper() to a wrapper function + Returns a decorator that invokes update_wrapper() with the decorated + function as the wrapper argument and the arguments to wraps() as the + remaining arguments. Default arguments are as for update_wrapper(). + This is a convenience function to simplify applying partial() to + update_wrapper(). + """ + return wrapped # do nothing + + +def update_wrapper(wrapper, wrapped): + return wrapper diff --git a/transcrypt/modules/inspect/__init__.py b/transcrypt/modules/inspect/__init__.py index 2b3153b54..d0045f493 100644 --- a/transcrypt/modules/inspect/__init__.py +++ b/transcrypt/modules/inspect/__init__.py @@ -1,3 +1,19 @@ def isclass(object): """Return true if the object is a class.""" return hasattr(object, '__metaclass__') and not hasattr(object, '__class__') + + +def _searchbases(cls, accum): + # Simulate the "classic class" search order. + if cls in accum: + return + accum.append(cls) + for base in cls.__bases__: + _searchbases(base, accum) + + +def getmro(cls): + "Return tuple of base classes (including cls) in method resolution order." + result = [] + _searchbases(cls, result) + return tuple(result) diff --git a/transcrypt/modules/operator/__init__.py b/transcrypt/modules/operator/__init__.py new file mode 100644 index 000000000..8637aae24 --- /dev/null +++ b/transcrypt/modules/operator/__init__.py @@ -0,0 +1,24 @@ + +def itemgetter(item, *items): + if not len(items): + def func(obj): + return obj[item] # __:opov + else: + items = (item,) + items # __:opov + + def func(obj): + return [obj[i] for i in items] # __:opov + return func + + +def attrgetter(attr): + if not isinstance(attr, str): + raise TypeError('attribute name must be a string') + names = attr.split('.') + + def func(obj): + for name in names: + obj = getattr(obj, name) + return obj + + return func diff --git a/transcrypt/modules/org/transcrypt/__builtin__.js b/transcrypt/modules/org/transcrypt/__builtin__.js index 110bd0d63..21af7406c 100644 --- a/transcrypt/modules/org/transcrypt/__builtin__.js +++ b/transcrypt/modules/org/transcrypt/__builtin__.js @@ -1,2568 +1,2696 @@ -__pragma__ ('stripcomments') - -// Needed for __base__ and __standard__ if global 'opov' switch is on -export function __call__ (/* , , * */) { - var args = [] .slice.apply (arguments); - if (typeof args [0] == 'object' && '__call__' in args [0]) { // Overloaded - return args [0] .__call__ .apply (args [1], args.slice (2)); - } - else { // Native - return args [0] .apply (args [1], args.slice (2)); - } -}; - -// Complete __envir__, that was created in __base__, for non-stub mode -__envir__.executor_name = __envir__.transpiler_name; - -// Make make __main__ available in browser -var __main__ = {__file__: ''}; - -// Define current exception, there's at most one exception in the air at any time -var __except__ = null; - - // Creator of a marked dictionary, used to pass **kwargs parameter -export function __kwargtrans__ (anObject) { - anObject.__kwargtrans__ = null; // Removable marker - anObject.constructor = Object; - return anObject; -} - -/* ... OBSOLETE, remove on or after y18m10d01 -// 'Oneshot' dict promotor, used to enrich __all__ and help globals () return a true dict -export function __globals__ (anObject) { - if (isinstance (anObject, dict)) { // Don't attempt to promote (enrich) again, since it will make a copy - return anObject; - } - else { - return dict (anObject) - } -} -*/ - -// Partial implementation of super () . () -export function __super__ (aClass, methodName) { - // Lean and fast, no C3 linearization, only call first implementation encountered - // Will allow __super__ ('') (self, ) rather than only . (self, ) - for (let base of aClass.__bases__) { - if (methodName in base) { - return base [methodName]; - } - } - - throw new Exception ('Superclass method not found'); // !!! Improve! -} - -// Python property installer function, no member since that would bloat classes -export function property (getter, setter) { // Returns a property descriptor rather than a property - if (!setter) { // ??? Make setter optional instead of dummy? - setter = function () {}; - } - return {get: function () {return getter (this)}, set: function (value) {setter (this, value)}, enumerable: true}; -} - -// Conditional JavaScript property installer function, prevents redefinition of properties if multiple Transcrypt apps are on one page -export function __setproperty__ (anObject, name, descriptor) { - if (!anObject.hasOwnProperty (name)) { - Object.defineProperty (anObject, name, descriptor); - } -} - -// Assert function, call to it only generated when compiling with --dassert option -export function assert (condition, message) { // Message may be undefined - if (!condition) { - throw AssertionError (message, new Error ()); - } -} - -// Merge function for keyword transfer objects -export function __mergekwargtrans__ (object0, object1) { - var result = {}; - for (var attrib in object0) { - result [attrib] = object0 [attrib]; - } - for (var attrib in object1) { - result [attrib] = object1 [attrib]; - } - return result; -}; - -// Merge function for dataclass fields -export function __mergefields__ (targetClass, sourceClass) { - let fieldNames = ['__reprfields__', '__comparefields__', '__initfields__'] - if (sourceClass [fieldNames [0]]) { - if (targetClass [fieldNames [0]]) { - for (let fieldName of fieldNames) { - targetClass [fieldName] = new Set ([...targetClass [fieldName], ...sourceClass [fieldName]]); - } - } - else { - for (let fieldName of fieldNames) { - targetClass [fieldName] = new Set (sourceClass [fieldName]); - } - } - } -} - -// Context manager support - -export function __withblock__ (manager, statements) { - if (hasattr (manager, '__enter__')) { - try { - manager.__enter__ (); - statements (); - manager.__exit__ (); - } - catch (exception) { - // Same signature as CPython : type, value, traceback - if (! (manager.__exit__ (exception.name, exception, exception.stack))) { - throw exception; - } - } - } - else { // Close an open file object, even if it doesn't support context management - statements (); - manager.close (); - } -}; - -// Manipulating attributes by name - -export function dir (obj) { - var aList = []; - for (var aKey in obj) { - aList.push (aKey.startsWith ('py_') ? aKey.slice (3) : aKey); - } - aList.sort (); - return aList; -}; - -export function setattr (obj, name, value) { - obj [name] = value; // Will not work in combination with static retrieval of aliased attributes, too expensive -}; - -export function getattr (obj, name) { - return name in obj ? obj [name] : obj ['py_' + name]; -}; - -export function hasattr (obj, name) { - try { - return name in obj || 'py_' + name in obj; - } - catch (exception) { - return false; - } -}; - -export function delattr (obj, name) { - if (name in obj) { - delete obj [name]; - } - else { - delete obj ['py_' + name]; - } -}; - -// The __in__ function, used to mimic Python's 'in' operator -// In addition to CPython's semantics, the 'in' operator is also allowed to work on objects, avoiding a counterintuitive separation between Python dicts and JavaScript objects -// In general many Transcrypt compound types feature a deliberate blend of Python and JavaScript facilities, facilitating efficient integration with JavaScript libraries -// If only Python objects and Python dicts are dealt with in a certain context, the more pythonic 'hasattr' is preferred for the objects as opposed to 'in' for the dicts -export function __in__ (element, container) { - if (container === undefined || container === null) { - return false; - } - if (container.__contains__ instanceof Function) { - return container.__contains__ (element); - } - else { // Parameter 'element' itself is an array, string or a plain, non-dict JavaScript object - return ( - container.indexOf ? // If it has an indexOf - container.indexOf (element) > -1 : // it's an array or a string, - container.hasOwnProperty (element) // else it's a plain, non-dict JavaScript object - ); - } -}; - -// Find out if an attribute is special -export function __specialattrib__ (attrib) { - return (attrib.startswith ('__') && attrib.endswith ('__')) || attrib == 'constructor' || attrib.startswith ('py_'); -}; - -// Compute length of any object -export function len (anObject) { - if (anObject === undefined || anObject === null) { - return 0; - } - - if (anObject.__len__ instanceof Function) { - return anObject.__len__ (); - } - - if (anObject.length !== undefined) { - return anObject.length; - } - - var length = 0; - for (var attr in anObject) { - if (!__specialattrib__ (attr)) { - length++; - } - } - - return length; -}; - -// General conversions and checks - -export function __i__ (any) { // Convert to iterable - return py_typeof (any) == dict ? any.py_keys () : any; -} - -export function __k__ (keyed, key) { // Check existence of dict key via retrieved element - var result = keyed [key]; - if (typeof result == 'undefined') { - if (keyed instanceof Array) - if (key == +key && key >= 0 && keyed.length > key) - return result; - else - throw IndexError (key, new Error()); - else - throw KeyError (key, new Error()); - } - return result; -} - -// If the target object is somewhat true, return it. Otherwise return false. -// Try to follow Python conventions of truthyness -export function __t__ (target) { - return ( - // Avoid invalid checks - target === undefined || target === null ? false : - - // Take a quick shortcut if target is a simple type - ['boolean', 'number'] .indexOf (typeof target) >= 0 ? target : - - // Use __bool__ (if present) to decide if target is true - target.__bool__ instanceof Function ? (target.__bool__ () ? target : false) : - - // There is no __bool__, use __len__ (if present) instead - target.__len__ instanceof Function ? (target.__len__ () !== 0 ? target : false) : - - // There is no __bool__ and no __len__, declare Functions true. - // Python objects are transpiled into instances of Function and if - // there is no __bool__ or __len__, the object in Python is true. - target instanceof Function ? target : - - // Target is something else, compute its len to decide - len (target) !== 0 ? target : - - // When all else fails, declare target as false - false - ); -} - -export function float (any) { - if (any == 'inf') { - return Infinity; - } - else if (any == '-inf') { - return -Infinity; - } - else if (any == 'nan') { - return NaN; - } - else if (isNaN (parseFloat (any))) { // Call to parseFloat needed to exclude '', ' ' etc. - if (any === false) { - return 0; - } - else if (any === true) { - return 1; - } - else { // Needed e.g. in autoTester.check, so "return any ? true : false" won't do - throw ValueError ("could not convert string to float: '" + str(any) + "'", new Error ()); - } - } - else { - return +any; - } -}; -float.__name__ = 'float'; -float.__bases__ = [object]; - -export function int (any) { - return float (any) | 0 -}; -int.__name__ = 'int'; -int.__bases__ = [object]; - -__pragma__ ('ifdef', '__sform__') -Number.prototype.__format__ = function (fmt_spec) { - if (fmt_spec == undefined || fmt_spec.strip ().length == 0) { - return this.toString (); - } - var thousand_sep = false; - var g_default = false; - var width = 0; - var zero = false; - var alternate = false; - var sign = '-'; - var align = '>'; - var fill = ' '; - var precision = undefined; - var ftype = undefined; - var val = this.valueOf (); - var is_negative = val < 0; - val = Math.abs (val); - - function pad (s, width, fill, align) { - if (fill == undefined) { - fill = ' '; - } - if (align == undefined) { - align = '>'; - } - var alt = ''; - var sign = ''; - if (s.startswith (['+', '-'])) { - sign = s [0]; - s = s.substr (1); - } - if (alternate && s.startswith (['0b', '0o', '0x'])) { - alt = s.slice (0, 2); - s = s.substr (2); - } - var len = s.length + sign.length + alt.length; - var c = width - len; - switch (align) { - case '=': - return sign + alt + __mul__ (fill, c) + s; - case '>': - return __mul__ (fill, c) + sign + alt + s; - case '<': - return sign + alt + s + __mul__ (fill, c); - case '^': - var m = ((c % 2) + 2) % 2; - var c = Math.floor (c / 2); - return __mul__ (fill, c) + sign + alt + s + __mul__ (fill, c + m); - default: - throw ValueError ("Invalid align type: '" + align + "'", new Error ()); - } - }; - - function format_float (val) { - if (val.indexOf ('e+') == -1 && (ftype == 'g' || ftype == 'G')) { - var parts = val.py_split ('.'); - var d = parts [0]; - var t = parts [1]; - while (t [t.length - 1] == '0') { - t = t.slice (0, -1); - } - val = t != '' ? '.'.join ([d, t]) : d; - } - if (alternate && val.indexOf ('.') == -1) { - val = val + '.'; - } - return val; - }; - - if (fmt_spec.endswith (['b', 'c', 'd', 'e', 'E', 'f', 'F', 'g', 'G', 'n', 'o', 'x', 'X', '%'])) { - ftype = fmt_spec [fmt_spec.length - 1]; - fmt_spec = fmt_spec.slice (0, -1); - if (ftype == 'n') { - ftype = Number.isInteger (val) ? 'd' : 'f'; - } - } - else { - ftype = Number.isInteger (val) ? 'd' : 'g'; - g_default = true; - } - - var parts = fmt_spec.split ('.'); - fmt_spec = parts [0]; - precision = parts [1]; - if (precision != undefined) { - precision = parseInt (precision); - } - if (fmt_spec.length > 0 && fmt_spec [fmt_spec.length - 1] == ',') { - thousand_sep = true; - fmt_spec = fmt_spec.slice (0, -1); - } - if (fmt_spec.length > 0) { - var _width = ''; - while (fmt_spec && fmt_spec [fmt_spec.length - 1].isnumeric ()) { - _width = fmt_spec [fmt_spec.length - 1] + _width; - fmt_spec = fmt_spec.slice (0, -1); - } - if (_width.length > 0) { - if (_width [0] == '0') { - width = parseInt (_width.substr (1)); - zero = true; - } - else { - width = parseInt (_width); - } - } - if (fmt_spec.length > 0 && fmt_spec [fmt_spec.length - 1] == '#') { - alternate = true; - fmt_spec = fmt_spec.slice (0, -1); - } - if (fmt_spec.length > 0 && fmt_spec.endswith (['+', '-', ' '])) { - sign = fmt_spec [fmt_spec.length - 1]; - fmt_spec = fmt_spec.slice (0, -1); - } - if (fmt_spec.length > 0 && fmt_spec.endswith (['<', '>', '=', '^'])) { - align = fmt_spec [fmt_spec.length - 1]; - fmt_spec = fmt_spec.slice (0, -1); - } - if (fmt_spec.length > 0) { - fill = fmt_spec [0]; - } - } - - if (isNaN (val)) { - val = 'nan'; - } - else if (val == Infinity) { - val = 'inf'; - } - else { - switch (ftype) { - case 'b': - val = Math.floor (val).toString (2); - if (alternate) { - val = '0b' + val; - } - break; - case 'c': - val = String.fromCharCode (Math.floor (val)); - break; - case 'd': - val = Math.floor (val).toString (); - if (thousand_sep) { - val = val.replace (/\B(?=(\d{3})+(?!\d))/g, ','); - } - break; - case 'o': - val = Math.floor (val).toString (8); - if (alternate) { - val = '0o' + val; - } - break; - case 'x': - case 'X': - val = Math.floor (val).toString (16); - if (alternate) { - val = '0x' + val; - } - break; - case 'e': - case 'E': - if (precision == undefined) { - precision = 6; - } - var num_exp = val.toExponential (precision).split ('e+'); - var num = num_exp [0]; - var exp = num_exp [1]; - val = num.toString () + 'e+' + pad (exp.toString(), 2, '0'); - val = format_float (val); - break; - case 'f': - case 'F': - case '%': - if (precision == undefined) { - precision = 6; - } - if (ftype == '%') { - val *= 100; - } - val = val.toFixed (precision); - val = format_float (val); - if (ftype == '%') { - val += '%'; - } - break; - case 'g': - case 'G': - if (precision == undefined) { - precision = g_default ? 1 : 6; - } - if (precision == 0) { - precision = 1; - } - var convert_to_exponent = false; - if (g_default) { - var parts = val.toString ().split ('.'); - var digit_count = parts [0].length + parts [1].length; - if (digit_count >= precision) { - convert_to_exponent = true; - } - } - var num_exp = val.toExponential (precision - 1).split ('e+'); - var num = num_exp [0]; - var exp = num_exp [1]; - convert_to_exponent |= !((-4 <= exp && exp < precision)); - if (convert_to_exponent) { - val = num.toString() + 'e+' + pad (exp.toString(), 2, '0'); - } - else { - val = val.toFixed (precision - 1 - exp); - } - val = format_float (val); - break; - default: - throw ValueError ("Invalid format type: '" + ftype + "'", new Error ()); - } - } - if (ftype === ftype.toUpperCase ()) { - val = val.toUpperCase () - } - if (ftype != 'c') { - if (sign == '-') { - if (is_negative) { - val = '-' + val; - } - } - else { - val = is_negative ? '-' + val : sign + val; - } - } - if (zero) { - fill = '0'; - align = '='; - } - if (width > 0) { - val = pad (val, width, fill, align); - } - return val; -}; -__pragma__ ('endif') - -export function bool (any) { // Always truly returns a bool, rather than something truthy or falsy - return !!__t__ (any); -}; -bool.__name__ = 'bool'; // So it can be used as a type with a name -bool.__bases__ = [int]; - -export function py_typeof (anObject) { - var aType = typeof anObject; - if (aType == 'object') { // Directly trying '__class__ in anObject' turns out to wreck anObject in Chrome if its a primitive - try { - return '__class__' in anObject ? anObject.__class__ : object; - } - catch (exception) { - return aType; - } - } - else { - return ( // Oddly, the braces are required here - aType == 'boolean' ? bool : - aType == 'string' ? str : - aType == 'number' ? (anObject % 1 == 0 ? int : float) : - null - ); - } -}; - -export function issubclass (aClass, classinfo) { - if (classinfo instanceof Array) { // Assume in most cases it isn't, then making it recursive rather than two functions saves a call - for (let aClass2 of classinfo) { - if (issubclass (aClass, aClass2)) { - return true; - } - } - return false; - } - try { - var aClass2 = aClass; - if (aClass2 == classinfo) { - return true; - } - else { - var bases = [].slice.call (aClass2.__bases__); - while (bases.length) { - aClass2 = bases.shift (); - if (aClass2 == classinfo) { - return true; - } - if (aClass2.__bases__.length) { - bases = [].slice.call (aClass2.__bases__).concat (bases); - } - } - return false; - } - } - catch (exception) { // Using issubclass on primitives assumed rare - return aClass == classinfo || classinfo == object; - } -}; - -export function isinstance (anObject, classinfo) { - try { - return '__class__' in anObject ? issubclass (anObject.__class__, classinfo) : issubclass (py_typeof (anObject), classinfo); - } - catch (exception) { - return issubclass (py_typeof (anObject), classinfo); - } -}; - -export function callable (anObject) { - return anObject && typeof anObject == 'object' && '__call__' in anObject ? true : typeof anObject === 'function'; -}; - -// Repr function uses __repr__ method, then __str__, then toString -export function repr (anObject) { - try { - return anObject.__repr__ (); - } - catch (exception) { - try { - return anObject.__str__ (); - } - catch (exception) { // anObject has no __repr__ and no __str__ - try { - if (anObject == null) { - return 'None'; - } - else if (anObject.constructor == Object) { - var result = '{'; - var comma = false; - for (var attrib in anObject) { - if (!__specialattrib__ (attrib)) { - if (attrib.isnumeric ()) { - var attribRepr = attrib; // If key can be interpreted as numerical, we make it numerical - } // So we accept that '1' is misrepresented as 1 - else { - var attribRepr = '\'' + attrib + '\''; // Alpha key in dict - } - - if (comma) { - result += ', '; - } - else { - comma = true; - } - result += attribRepr + ': ' + repr (anObject [attrib]); - } - } - result += '}'; - return result; - } - else { - return typeof anObject == 'boolean' ? anObject.toString () .capitalize () : anObject.toString (); - } - } - catch (exception) { - return ''; - } - } - } -}; - -// Char from Unicode or ASCII -export function chr (charCode) { - return String.fromCharCode (charCode); -}; - -// Unicode or ASCII from char -export function ord (aChar) { - return aChar.charCodeAt (0); -}; - -function min_max (f_compare, ...args) { - // Assume no kwargs - let dflt = undefined; - function key(x) {return x} - - if (args.length > 0) { - if (args[args.length-1] && args[args.length-1].hasOwnProperty ("__kwargtrans__")) { - const kwargs = args[args.length - 1]; - args = args.slice(0, -1); - if (kwargs.hasOwnProperty('py_default')) dflt = kwargs['py_default']; - if (kwargs.hasOwnProperty('key')) key = kwargs['key']; - if (Object.prototype.toString.call(key) !== '[object Function]') throw TypeError("object is not callable", new Error()); - } - } - - if (args.length === 0) throw TypeError("expected at least 1 argument, got 0", new Error ()); - if (args.length > 1 && dflt !== undefined) throw TypeError("Cannot specify a default with multiple positional arguments", new Error ()); - if (args.length === 1){ - if (Object.prototype.toString.call(args[0]) !== '[object Array]') throw TypeError("object is not iterable", new Error()); - args = args[0]; // Passed in arg is itself an iterable - } - if (args.length === 0){ - if (dflt === undefined) throw ValueError ("arg is an empty sequence", new Error ()); - return dflt - } - - return args.reduce((max_val, cur_val) => f_compare(key(cur_val), key(max_val)) ? cur_val : max_val); -} - -// Maximum of n values -export function max (...args) { - return min_max(function (a, b){return a > b}, ...args) -} - -// Minimum of n numbers -export function min (...args) { - return min_max(function (a, b){return a < b}, ...args) -} - -// Integer to binary -export function bin (nbr) { - const sign = nbr<0 ? '-' : ''; - const bin_val = Math.abs(parseInt(nbr)).toString(2); - return sign.concat('0b', bin_val); -}; - -// Integer to octal -export function oct (nbr) { - const sign = nbr<0 ? '-' : ''; - const oct_val = Math.abs(parseInt(nbr)).toString(8); - return sign.concat('0o', oct_val); -}; - -// Integer to hexadecimal -export function hex (nbr) { - const sign = nbr<0 ? '-' : ''; - const hex_val = Math.abs(parseInt(nbr)).toString(16); - return sign.concat('0x', hex_val); -}; - -// Absolute value -__pragma__ ('ifdef', '__complex__') -export function abs (x) { - try { - return Math.abs (x); - } - catch (exception) { - return Math.sqrt (x.real * x.real + x.imag * x.imag); - } -}; -__pragma__ ('else') -export var abs = Math.abs; -__pragma__ ('endif') - -// Bankers rounding -export function round (number, ndigits) { - if (ndigits) { - var scale = Math.pow (10, ndigits); - number *= scale; - } - - var rounded = Math.round (number); - if (rounded - number == 0.5 && rounded % 2) { // Has rounded up to odd, should have rounded down to even - rounded -= 1; - } - - if (ndigits) { - rounded /= scale; - } - - return rounded; -}; - -__pragma__ ('ifdef', '__sform__') -export function format (value, fmt_spec) { - if (value == undefined) { - return 'None'; - } - fmt_spec = fmt_spec || ''; - var tval = typeof value; - switch (tval) { - case 'number': - case 'string': - return value.__format__(fmt_spec); - case 'boolean': - return fmt_spec ? (value ? 1 : 0).__format__(fmt_spec) : str (value); - case 'object': - if ('__format__' in value) { - return value.__format__ (fmt_spec); - } - else { - return str (value).__format__ (fmt_spec); - } - default: - return str (value).__format__ (fmt_spec); - } -} -__pragma__ ('endif') - -// BEGIN unified iterator model - -export function __jsUsePyNext__ () { // Add as 'next' method to make Python iterator JavaScript compatible - try { - var result = this.__next__ (); - return {value: result, done: false}; - } - catch (exception) { - return {value: undefined, done: true}; - } -} - -export function __pyUseJsNext__ () { // Add as '__next__' method to make JavaScript iterator Python compatible - var result = this.next (); - if (result.done) { - throw StopIteration (new Error ()); - } - else { - return result.value; - } -} - -export function py_iter (iterable) { // Alias for Python's iter function, produces a universal iterator / iterable, usable in Python and JavaScript - if (typeof iterable == 'string' || '__iter__' in iterable) { // JavaScript Array or string or Python iterable (string has no 'in') - var result = iterable.__iter__ (); // Iterator has a __next__ - result.next = __jsUsePyNext__; // Give it a next - } - else if ('selector' in iterable) { // Assume it's a JQuery iterator - var result = list (iterable) .__iter__ (); // Has a __next__ - result.next = __jsUsePyNext__; // Give it a next - } - else if ('next' in iterable) { // It's a JavaScript iterator already, maybe a generator, has a next and may have a __next__ - var result = iterable - if (! ('__next__' in result)) { // If there's no danger of recursion - result.__next__ = __pyUseJsNext__; // Give it a __next__ - } - } - else if (Symbol.iterator in iterable) { // It's a JavaScript iterable such as a typed array, but not an iterator - var result = iterable [Symbol.iterator] (); // Has a next - result.__next__ = __pyUseJsNext__; // Give it a __next__ - } - else { - throw IterableError (new Error ()); // No iterator at all - } - result [Symbol.iterator] = function () {return result;}; - return result; -} - -export function py_next (iterator, value) { // Called only in a Python context, could receive Python or JavaScript iterator - try { // Primarily assume Python iterator, for max speed - var result = iterator.__next__ (); - } - catch (exception) { // JavaScript iterators are the exception here - var result = iterator.next (); - if (result.done) { - if(!(value === undefined)) return value - throw StopIteration (new Error ()); - } - else { - return result.value; - } - } - if (result === undefined) { - if(!(value === undefined)) return value - throw StopIteration (new Error ()); - } - else { - return result; - } -} - -export function __PyIterator__ (iterable) { - this.iterable = iterable; - this.index = 0; - this.__len__ = function () {return iterable.length}; -} - -__PyIterator__.prototype.__next__ = function() { - if (this.index < this.iterable.length) { - return this.iterable [this.index++]; - } - else { - throw StopIteration (new Error ()); - } -}; - -export function __JsIterator__ (iterable) { - this.iterable = iterable; - this.index = 0; -} - -__JsIterator__.prototype.next = function () { - if (this.index < this.iterable.py_keys.length) { - return {value: this.index++, done: false}; - } - else { - return {value: undefined, done: true}; - } -}; - -// END unified iterator model - -// Reversed function for arrays -export function py_reversed (iterable) { - iterable = iterable.slice (); - iterable.reverse (); - return iterable; -}; - -// Zip method for arrays and strings -export function zip () { - var args = [] .slice.call (arguments); - for (var i = 0; i < args.length; i++) { - if (typeof args [i] == 'string') { - args [i] = args [i] .split (''); - } - else if (!Array.isArray (args [i])) { - args [i] = Array.from (args [i]); - } - } - var shortest = args.length == 0 ? [] : args.reduce ( // Find shortest array in arguments - function (array0, array1) { - return array0.length < array1.length ? array0 : array1; - } - ); - return shortest.map ( // Map each element of shortest array - function (current, index) { // To the result of this function - return args.map ( // Map each array in arguments - function (current) { // To the result of this function - return current [index]; // Namely it's index't entry - } - ); - } - ); -}; - -// Range method, returning an array -export function range (start, stop, step) { - if (stop == undefined) { - // one param defined - stop = start; - start = 0; - } - if (step == undefined) { - step = 1; - } - if ((step > 0 && start >= stop) || (step < 0 && start <= stop)) { - return []; - } - var result = []; - for (var i = start; step > 0 ? i < stop : i > stop; i += step) { - result.push(i); - } - return result; -}; - -// Any, all and sum - -export function any (iterable) { - for (let item of iterable) { - if (bool (item)) { - return true; - } - } - return false; -} -export function all (iterable) { - for (let item of iterable) { - if (! bool (item)) { - return false; - } - } - return true; -} -export function sum (iterable) { - let result = 0; - for (let item of iterable) { - result += item; - } - return result; -} - -function* __enumerate__ (iterable, start=0) { - if (start.hasOwnProperty("__kwargtrans__")) { - // start was likely passed in as kwarg - start = start['start']; - } - let n = start - for (const item of iterable) { - yield [n, item] - n += 1 - } -} -export var py_enumerate = __enumerate__; // Exporting a generator function in JS may be problematic but it allows enumerate to be lazy - -// List extensions to Array - -export function list (iterable) { // All such creators should be callable without new - let instance = iterable ? Array.from (iterable) : []; - // Sort is the normal JavaScript sort, Python sort is a non-member function - return instance; -} -Array.prototype.__class__ = list; // All arrays are lists (not only if constructed by the list ctor), unless constructed otherwise -list.__name__ = 'list'; -list.__bases__ = [object]; - -Array.prototype.__iter__ = function () {return new __PyIterator__ (this);}; - -Array.prototype.__getslice__ = function (start, stop, step) { - if (step === null) { - step = 1; - } - if (start === null) { - start = (step < 0 ? -1 : 0); - } - if (start < 0) { - start = Math.max(this.length + start, 0); - } else if (start > this.length || (start === this.length && step < 0)) { - start = this.length > 0 ? this.length - 1 : 0; - } - - if (stop === null) { - stop = (step < 0 && this.length > 0 ? -1 : this.length); - } else if (stop < 0) { - stop = Math.max(this.length + stop, (step < 0 && this.length > 0 ? -1 : 0)); - } else if (stop > this.length) { - stop = this.length; - } - - if (step === 1) { - return Array.prototype.slice.call(this, start, stop); - } - - let result = list ([]); - if (step > 0) { - for (let index = start; index < stop; index += step) { - result.push (this [index]); - } - } else if (step < 0) { - for (let index = start; index > stop; index += step) { - result.push (this [index]); - } - } else { - throw ValueError ("slice step cannot be zero", new Error ()); - } - - return result; -}; - -Array.prototype.__setslice__ = function (start, stop, step, source) { - if (step === null) { - step = 1; - } - if (start === null) { - start = (step < 0 ? -1 : 0); - } - if (start < 0) { - start = Math.max(this.length + start, 0); - } else if (start > this.length || (start === this.length && step < 0)) { - start = this.length > 0 ? this.length - 1 : 0; - } - - if (stop === null) { - stop = (step < 0 && this.length > 0 ? -1 : this.length); - } else if (stop < 0) { - stop = Math.max(this.length + stop, (step < 0 && this.length > 0 ? -1 : 0)); - } else if (stop > this.length) { - stop = this.length; - } - - if (step === 1) { // Assign to 'ordinary' slice, replace subsequence - Array.prototype.splice.apply (this, [start, stop - start] .concat (Array.from(source))); - } - else { - // Validate assignment is valid based on Python's size rules - const seq_len = Math.ceil((stop - start) / step) - if((source.length > 0 || seq_len > 0) && (seq_len !== source.length)){ - // throw ValueError ("Invalid slice assignment", new Error ()); - throw ValueError ("attempt to assign sequence of size " + source.length + " to extended slice of size " + seq_len, new Error ()); - } - // Assign to extended slice, replace designated items one by one - let sourceIndex = 0; - if (step > 0) { - for (let targetIndex = start; targetIndex < stop; targetIndex += step) { - this [targetIndex] = source [sourceIndex++]; - } - } else if (step < 0) { - for (let targetIndex = start; targetIndex > stop; targetIndex += step) { - this [targetIndex] = source [sourceIndex++]; - } - } else { - throw ValueError ("slice step cannot be zero", new Error ()); - } - - } -}; - -Array.prototype.__repr__ = function () { - if (this.__class__ == set && !this.length) { - return 'set()'; - } - - let result = !this.__class__ || this.__class__ == list ? '[' : this.__class__ == tuple ? '(' : '{'; - - for (let index = 0; index < this.length; index++) { - if (index) { - result += ', '; - } - result += repr (this [index]); - } - - if (this.__class__ == tuple && this.length == 1) { - result += ','; - } - - result += !this.__class__ || this.__class__ == list ? ']' : this.__class__ == tuple ? ')' : '}';; - return result; -}; - -Array.prototype.__str__ = Array.prototype.__repr__; - -Array.prototype.append = function (element) { - this.push (element); -}; - -Array.prototype.py_clear = function () { - this.length = 0; -}; - -Array.prototype.py_copy = function () { - return this.slice(); -}; - -Array.prototype.extend = function (aList) { - this.push.apply (this, aList); -}; - -Array.prototype.insert = function (index, element) { - this.splice (index, 0, element); -}; - -Array.prototype.remove = function (element) { - let index = this.indexOf (element); - if (index === -1) { - throw ValueError("list.remove(x): x not in list", new Error ()); - } - this.splice (index, 1); -}; - -Array.prototype.index = function (element) { - return this.indexOf (element); -}; - -Array.prototype.py_pop = function (index) { - if(this.length === 0){ - throw IndexError("pop from empty list", new Error()) - } - if (index === undefined) { - return this.pop (); // Remove last element - } - else { - const idx = index < 0 ? this.length + index : index - if(this[idx] === undefined){ - throw IndexError("pop index out of range", new Error()) - } - return this.splice (idx, 1) [0]; - } -}; - -Array.prototype.py_sort = function () { - __sort__.apply (null, [this].concat ([] .slice.apply (arguments))); // Can't work directly with arguments - // Python params: (iterable, key = None, reverse = False) - // py_sort is called with the Transcrypt kwargs mechanism, and just passes the params on to __sort__ - // __sort__ is def'ed with the Transcrypt kwargs mechanism -}; - -Array.prototype.__add__ = function (aList) { - return list (this.concat (aList)); -}; - -Array.prototype.__mul__ = function (scalar) { - let result = this; - for (let i = 1; i < scalar; i++) { - result = result.concat (this); - } - return result; -}; - -Array.prototype.__rmul__ = Array.prototype.__mul__; - -// Tuple extensions to Array - -export function tuple (iterable) { - let instance = iterable ? [] .slice.apply (iterable) : []; - instance.__class__ = tuple; // Not all arrays are tuples - return instance; -} -tuple.__name__ = 'tuple'; -tuple.__bases__ = [object]; - -// Set extensions to Array -// N.B. Since sets are unordered, set operations will occasionally alter the 'this' array by sorting it - -export function set (iterable) { - let instance = []; - if (iterable) { - for (let index = 0; index < iterable.length; index++) { - instance.add (iterable [index]); - } - } - instance.__class__ = set; // Not all arrays are sets - return instance; -} -set.__name__ = 'set'; -set.__bases__ = [object]; - -Array.prototype.__bindexOf__ = function (element) { // Used to turn O (n^2) into O (n log n) -// Since sorting is lex, compare has to be lex. This also allows for mixed lists - - element += ''; - - let mindex = 0; - let maxdex = this.length - 1; - - while (mindex <= maxdex) { - let index = (mindex + maxdex) / 2 | 0; - let middle = this [index] + ''; - - if (middle < element) { - mindex = index + 1; - } - else if (middle > element) { - maxdex = index - 1; - } - else { - return index; - } - } - - return -1; -}; - -Array.prototype.add = function (element) { - if (this.indexOf (element) == -1) { // Avoid duplicates in set - this.push (element); - } -}; - -Array.prototype.discard = function (element) { - var index = this.indexOf (element); - if (index != -1) { - this.splice (index, 1); - } -}; - -Array.prototype.isdisjoint = function (other) { - this.sort (); - for (let i = 0; i < other.length; i++) { - if (this.__bindexOf__ (other [i]) != -1) { - return false; - } - } - return true; -}; - -Array.prototype.issuperset = function (other) { - this.sort (); - for (let i = 0; i < other.length; i++) { - if (this.__bindexOf__ (other [i]) == -1) { - return false; - } - } - return true; -}; - -Array.prototype.issubset = function (other) { - return set (other.slice ()) .issuperset (this); // Sort copy of 'other', not 'other' itself, since it may be an ordered sequence -}; - -Array.prototype.union = function (other) { - let result = set (this.slice () .sort ()); - for (let i = 0; i < other.length; i++) { - if (result.__bindexOf__ (other [i]) == -1) { - result.push (other [i]); - } - } - return result; -}; - -Array.prototype.intersection = function (other) { - this.sort (); - let result = set (); - for (let i = 0; i < other.length; i++) { - if (this.__bindexOf__ (other [i]) != -1) { - result.push (other [i]); - } - } - return result; -}; - -Array.prototype.difference = function (other) { - let sother = set (other.slice () .sort ()); - let result = set (); - for (let i = 0; i < this.length; i++) { - if (sother.__bindexOf__ (this [i]) == -1) { - result.push (this [i]); - } - } - return result; -}; - -Array.prototype.symmetric_difference = function (other) { - return this.union (other) .difference (this.intersection (other)); -}; - -Array.prototype.py_update = function () { // O (n) - let updated = [] .concat.apply (this.slice (), arguments) .sort (); - this.py_clear (); - for (let i = 0; i < updated.length; i++) { - if (updated [i] != updated [i - 1]) { - this.push (updated [i]); - } - } -}; - -Array.prototype.__eq__ = function (other) { // Also used for list - if (this.length != other.length) { - return false; - } - if (this.__class__ == set) { - this.sort (); - other.sort (); - } - for (let i = 0; i < this.length; i++) { - if (this [i] != other [i]) { - return false; - } - } - return true; -}; - -Array.prototype.__ne__ = function (other) { // Also used for list - return !this.__eq__ (other); -}; - -Array.prototype.__le__ = function (other) { - if (this.__class__ == set) { - return this.issubset (other); - } - else { - for (let i = 0; i < this.length; i++) { - if (this [i] > other [i]) { - return false; - } - else if (this [i] < other [i]) { - return true; - } - } - return true; - } -}; - -Array.prototype.__ge__ = function (other) { - if (this.__class__ == set) { - return this.issuperset (other); - } - else { - for (let i = 0; i < this.length; i++) { - if (this [i] < other [i]) { - return false; - } - else if (this [i] > other [i]) { - return true; - } - } - return true; - } -}; - -Array.prototype.__lt__ = function (other) { - return ( - this.__class__ == set ? - this.issubset (other) && !this.issuperset (other) : - !this.__ge__ (other) - ); -}; - -Array.prototype.__gt__ = function (other) { - return ( - this.__class__ == set ? - this.issuperset (other) && !this.issubset (other) : - !this.__le__ (other) - ); -}; - -// Byte array extensions - -export function bytearray (bytable, encoding) { - if (bytable == undefined) { - return new Uint8Array (0); - } - else { - let aType = py_typeof (bytable); - if (aType == int) { - return new Uint8Array (bytable); - } - else if (aType == str) { - let aBytes = new Uint8Array (len (bytable)); - for (let i = 0; i < len (bytable); i++) { - aBytes [i] = bytable.charCodeAt (i); - } - return aBytes; - } - else if (aType == list || aType == tuple) { - return new Uint8Array (bytable); - } - else { - throw py_TypeError; - } - } -} - -export var bytes = bytearray; - - -Uint8Array.prototype.__add__ = function (aBytes) { - let result = new Uint8Array (this.length + aBytes.length); - result.set (this); - result.set (aBytes, this.length); - return result; -}; - -Uint8Array.prototype.__mul__ = function (scalar) { - let result = new Uint8Array (scalar * this.length); - for (let i = 0; i < scalar; i++) { - result.set (this, i * this.length); - } - return result; -}; - -Uint8Array.prototype.__rmul__ = Uint8Array.prototype.__mul__; - -// String extensions - -export function str (stringable) { - if (typeof stringable === 'number') - return stringable.toString(); - else { - try { - return stringable.__str__ (); - } - catch (exception) { - try { - return repr (stringable); - } - catch (exception) { - return String (stringable); // No new, so no permanent String object but a primitive in a temporary 'just in time' wrapper - } - } - } -}; - -String.prototype.__class__ = str; // All strings are str -str.__name__ = 'str'; -str.__bases__ = [object]; - -String.prototype.__iter__ = function () {new __PyIterator__ (this);}; - -String.prototype.__repr__ = function () { - return (this.indexOf ('\'') == -1 ? '\'' + this + '\'' : '"' + this + '"') .py_replace ('\t', '\\t') .py_replace ('\n', '\\n'); -}; - -String.prototype.__str__ = function () { - return this; -}; - -String.prototype.capitalize = function () { - return this.charAt (0).toUpperCase () + this.slice (1); -}; - -String.prototype.endswith = function (suffix, start=0, end) { - if (end === undefined) {end = this.length} - const str = this.slice(start, end) - - if (suffix instanceof Array) { - for (var i=0;i this.length) { - start = this.length > 0 ? this.length + (step < 0 ? -1 : 0) : 0; - } - - if (stop === null) { - stop = (step < 0 && this.length > 0 ? -1 : this.length); - } else if (stop < 0) { - stop = Math.max(this.length + stop, (step < 0 && this.length > 0 ? -1 : 0)); - } else if (stop > this.length) { - stop = this.length; - } - - if (step === 1) { - return this.substring (start, (start > stop ? start : stop)); - } - - let result = ''; - if (step > 0) { - for (var index = start; index < stop; index += step) { - result = result.concat (this.charAt(index)); - } - } else if (step < 0) { - for (var index = start; index > stop; index += step) { - result = result.concat (this.charAt(index)); - } - } - else { - throw ValueError ("slice step cannot be zero", new Error ()); - } - - return result; -}; - -__pragma__ ('ifdef', '__sform__') -String.prototype.__format__ = function (fmt_spec) { - if (fmt_spec == undefined || fmt_spec.strip ().length == 0) { - return this.valueOf (); - } - var width = 0; - var align = '<'; - var fill = ' '; - var val = this.valueOf (); - - function pad (s, width, fill, align) { - var len = s.length; - var c = width - len; - switch (align) { - case '>': - return __mul__ (fill, c) + s; - case '<': - return s + __mul__ (fill, c); - case '^': - var m = ((c % 2) + 2) % 2; - var c = Math.floor (c / 2); - return __mul__ (fill, c) + s + __mul__ (fill, c + m); - default: - return s; - } - }; - - if (fmt_spec [fmt_spec.length - 1] == 's') { - fmt_spec = fmt_spec.slice (0, -1); - } - if (fmt_spec.length > 0) { - var _width = ''; - while (fmt_spec && fmt_spec [fmt_spec.length - 1].isnumeric ()) { - _width = fmt_spec [fmt_spec.length - 1] + _width; - fmt_spec = fmt_spec.slice (0, -1); - } - if (_width.length > 0) { - width = parseInt (_width); - } - if (fmt_spec.length > 0 && fmt_spec.endswith (['<', '>', '^'])) { - align = fmt_spec [fmt_spec.length - 1]; - fmt_spec = fmt_spec.slice (0, -1); - } - if (fmt_spec.length > 0) { - fill = fmt_spec [0]; - } - } - if (width > 0) { - val = pad (val, width, fill, align); - } - return val; -}; -__pragma__ ('endif') - -// Since it's worthwhile for the 'format' function to be able to deal with *args, it is defined as a property -// __get__ will produce a bound function if there's something before the dot -// Since a call using *args is compiled to e.g. ..apply (null, args), the function has to be bound already -// Otherwise it will never be, because of the null argument -// Using 'this' rather than 'null' contradicts the requirement to be able to pass bound functions around -// The object 'before the dot' won't be available at call time in that case, unless implicitly via the function bound to it -// While for Python methods this mechanism is generated by the compiler, for JavaScript methods it has to be provided manually -// Call memoizing is unattractive here, since every string would then have to hold a reference to a bound format method -__setproperty__ (String.prototype, 'format', { - get: function () {return __get__ (this, function (self) { - var args = tuple ([] .slice.apply (arguments).slice (1)); - var autoIndex = 0; -__pragma__ ('ifdef', '__sform__') - return self.replace (/\{([^\{]*)\}/g, function (match, key) { - var parts = key.split (':'); - key = parts [0]; - var fmt_spec = parts [1]; - parts = key.split ('!') - key = parts [0]; - var conversion = parts [1]; - var value = undefined; - if (key == '') { - key = autoIndex++; - } - if (key == +key && args [key] !== undefined) { // So key is numerical - value = args [key]; - } - else { // Key is a string - var attr = undefined; - var idx = ("" + key) .indexOf ('.'); // ??? Why is conversion to string suddenly needed? - if (idx != -1) { - attr = key.substring (idx + 1); - key = key.substring (0, idx); - } - else { - idx = ("" + key) .indexOf ('['); // ??? Why is conversion to string suddenly needed? - if (idx != -1) { - attr = key.substring (idx + 1).slice (0, -1); - key = key.substring (0, idx); - } - } - - if ((key == +key) && attr && args [key] !== undefined) { - value = args [key][attr]; - } - else { - for (var index = 0; index < args.length; index++) { - // Find first 'dict' that has that key and the right field - if (typeof args [index] == 'object' && args [index] != null && args [index][key] !== undefined) { // Why is check for null suddenly needed? - // Return that field field - if (attr) { - value = args [index][key][attr]; - } - else { - value = args [index][key]; - } - break; - } - } - } - } - if (value === undefined) { - return match; - } - if (conversion == 'r') { - value = repr (value); - } - else if (conversion == 's') { - value = str (value); - } - else if (conversion == 'a') { - throw ValueError ("Conversion to ascii not yet supported: '" + match + "'", new Error ()); - } - return format (value, fmt_spec); - }); -__pragma__ ('else') - return self.replace (/\{(\w*)\}/g, function (match, key) { - if (key == '') { - key = autoIndex++; - } - if (key == +key) { // So key is numerical - return args [key] === undefined ? match : str (args [key]); - } - else { // Key is a string - for (var index = 0; index < args.length; index++) { - // Find first 'dict' that has that key and the right field - if (typeof args [index] == 'object' && args [index][key] !== undefined) { - return str (args [index][key]); // Return that field field - } - } - return match; - } - }); -__pragma__ ('endif') - });}, - enumerable: true -}); - -String.prototype.isalnum = function () { - return /^[0-9a-zA-Z]{1,}$/.test(this) -} - -String.prototype.isalpha = function () { - return /^[a-zA-Z]{1,}$/.test(this) -} - -String.prototype.isdecimal = function () { - return /^[0-9]{1,}$/.test(this) -} - -String.prototype.isdigit = function () { - return this.isdecimal() -} - -String.prototype.islower = function () { - return /^[a-z]{1,}$/.test(this) -} - -String.prototype.isupper = function () { - return /^[A-Z]{1,}$/.test(this) -} - -String.prototype.isspace = function () { - return /^[\s]{1,}$/.test(this) -} - -String.prototype.isnumeric = function () { - return !isNaN (parseFloat (this)) && isFinite (this); -}; - -String.prototype.join = function (strings) { - strings = Array.from (strings); // Much faster than iterating through strings char by char - return strings.join (this); -}; - -String.prototype.lower = function () { - return this.toLowerCase (); -}; - -String.prototype.py_replace = function (old, aNew, maxreplace) { - if (maxreplace === undefined || maxreplace < 0) { - return this.split(old).join(aNew); - } else if (maxreplace === 0) { - return this; - } else { - const pre = this.split(old, maxreplace).join(aNew); - const rest = this.slice(this.split(old, maxreplace).join(old).length + 1) - return pre.concat(rest.length>0 ? aNew : '', rest); - } -}; - -String.prototype.lstrip = function () { - return this.replace (/^\s*/g, ''); -}; - -String.prototype.rfind = function (sub, start) { - return this.lastIndexOf (sub, start); -}; - -String.prototype.rsplit = function (sep, maxsplit) { // Combination of general whitespace sep and positive maxsplit neither supported nor checked, expensive and rare - if (sep == undefined || sep == null) { - sep = /\s+/; - var stripped = this.strip (); - } - else { - var stripped = this; - } - - if (maxsplit == undefined || maxsplit == -1) { - return stripped.split (sep); - } - else { - var result = stripped.split (sep); - if (maxsplit < result.length) { - var maxrsplit = result.length - maxsplit; - return [result.slice (0, maxrsplit) .join (sep)] .concat (result.slice (maxrsplit)); - } - else { - return result; - } - } -}; - -String.prototype.rstrip = function () { - return this.replace (/\s*$/g, ''); -}; - -String.prototype.py_split = function (sep, maxsplit) { // Combination of general whitespace sep and positive maxsplit neither supported nor checked, expensive and rare - if (sep == undefined || sep == null) { - sep = /\s+/; - var stripped = this.strip (); - } - else { - var stripped = this; - } - - if (maxsplit == undefined || maxsplit == -1) { - return stripped.split (sep); - } - else { - var result = stripped.split (sep); - if (maxsplit < result.length) { - return result.slice (0, maxsplit).concat ([result.slice (maxsplit).join (sep)]); - } - else { - return result; - } - } -}; - -String.prototype.splitlines = function (keepends) { - if (this.length === 0) { - return []; - } - - if (keepends === undefined || keepends === null || keepends === false) { - return this.trimEnd().split(/\r?\n|\r|\n/g); - } - else { - return this.split(/(?<=\n)(?=\n)|(?<=[\r\n])(?=[^\r\n])/g); - } -}; - -String.prototype.startswith = function (prefix, start=0, end) { - if (end === undefined) {end = this.length} - const str = this.slice(start, end) - - if (prefix instanceof Array) { - for (let i=0;i> b; - } -}; - -export function __or__ (a, b) { - if (typeof a == 'object' && '__or__' in a) { - return a.__or__ (b); - } - else if (typeof b == 'object' && '__ror__' in b) { - return b.__ror__ (a); - } - else { - return a | b; - } -}; - -export function __xor__ (a, b) { - if (typeof a == 'object' && '__xor__' in a) { - return a.__xor__ (b); - } - else if (typeof b == 'object' && '__rxor__' in b) { - return b.__rxor__ (a); - } - else { - return a ^ b; - } -}; - -export function __and__ (a, b) { - if (typeof a == 'object' && '__and__' in a) { - return a.__and__ (b); - } - else if (typeof b == 'object' && '__rand__' in b) { - return b.__rand__ (a); - } - else { - return a & b; - } -}; - -// Overloaded binary compare - -export function __eq__ (a, b) { - if (typeof a == 'object' && '__eq__' in a) { - return a.__eq__ (b); - } - else { - return a == b; - } -}; - -export function __ne__ (a, b) { - if (typeof a == 'object' && '__ne__' in a) { - return a.__ne__ (b); - } - else { - return a != b - } -}; - -export function __lt__ (a, b) { - if (typeof a == 'object' && '__lt__' in a) { - return a.__lt__ (b); - } - else { - return a < b; - } -}; - -export function __le__ (a, b) { - if (typeof a == 'object' && '__le__' in a) { - return a.__le__ (b); - } - else { - return a <= b; - } -}; - -export function __gt__ (a, b) { - if (typeof a == 'object' && '__gt__' in a) { - return a.__gt__ (b); - } - else { - return a > b; - } -}; - -export function __ge__ (a, b) { - if (typeof a == 'object' && '__ge__' in a) { - return a.__ge__ (b); - } - else { - return a >= b; - } -}; - -// Overloaded augmented general - -export function __imatmul__ (a, b) { - if ('__imatmul__' in a) { - return a.__imatmul__ (b); - } - else { - return a.__matmul__ (b); - } -}; - -export function __ipow__ (a, b) { - if (typeof a == 'object' && '__pow__' in a) { - return a.__ipow__ (b); - } - else if (typeof a == 'object' && '__ipow__' in a) { - return a.__pow__ (b); - } - else if (typeof b == 'object' && '__rpow__' in b) { - return b.__rpow__ (a); - } - else { - return Math.pow (a, b); - } -}; - -export function __ijsmod__ (a, b) { - if (typeof a == 'object' && '__imod__' in a) { - return a.__ismod__ (b); - } - else if (typeof a == 'object' && '__mod__' in a) { - return a.__mod__ (b); - } - else if (typeof b == 'object' && '__rpow__' in b) { - return b.__rmod__ (a); - } - else { - return a % b; - } -}; - -export function __imod__ (a, b) { - if (typeof a == 'object' && '__imod__' in a) { - return a.__imod__ (b); - } - else if (typeof a == 'object' && '__mod__' in a) { - return a.__mod__ (b); - } - else if (typeof b == 'object' && '__rmod__' in b) { - return b.__rmod__ (a); - } - else { - return ((a % b) + b) % b; - } -}; - -// Overloaded augmented arithmetic - -export function __imul__ (a, b) { - if (typeof a == 'object' && '__imul__' in a) { - return a.__imul__ (b); - } - else if (typeof a == 'object' && '__mul__' in a) { - return a = a.__mul__ (b); - } - else if (typeof b == 'object' && '__rmul__' in b) { - return a = b.__rmul__ (a); - } - else if (typeof a == 'string') { - return a = a.__mul__ (b); - } - else if (typeof b == 'string') { - return a = b.__rmul__ (a); - } - else { - return a *= b; - } -}; - -export function __idiv__ (a, b) { - if (typeof a == 'object' && '__idiv__' in a) { - return a.__idiv__ (b); - } - else if (typeof a == 'object' && '__div__' in a) { - return a = a.__div__ (b); - } - else if (typeof b == 'object' && '__rdiv__' in b) { - return a = b.__rdiv__ (a); - } - else { - return a /= b; - } -}; - -export function __iadd__ (a, b) { - if (typeof a == 'object' && '__iadd__' in a) { - return a.__iadd__ (b); - } - else if (typeof a == 'object' && '__add__' in a) { - return a = a.__add__ (b); - } - else if (typeof b == 'object' && '__radd__' in b) { - return a = b.__radd__ (a); - } - else { - return a += b; - } -}; - -export function __isub__ (a, b) { - if (typeof a == 'object' && '__isub__' in a) { - return a.__isub__ (b); - } - else if (typeof a == 'object' && '__sub__' in a) { - return a = a.__sub__ (b); - } - else if (typeof b == 'object' && '__rsub__' in b) { - return a = b.__rsub__ (a); - } - else { - return a -= b; - } -}; - -// Overloaded augmented bitwise - -export function __ilshift__ (a, b) { - if (typeof a == 'object' && '__ilshift__' in a) { - return a.__ilshift__ (b); - } - else if (typeof a == 'object' && '__lshift__' in a) { - return a = a.__lshift__ (b); - } - else if (typeof b == 'object' && '__rlshift__' in b) { - return a = b.__rlshift__ (a); - } - else { - return a <<= b; - } -}; - -export function __irshift__ (a, b) { - if (typeof a == 'object' && '__irshift__' in a) { - return a.__irshift__ (b); - } - else if (typeof a == 'object' && '__rshift__' in a) { - return a = a.__rshift__ (b); - } - else if (typeof b == 'object' && '__rrshift__' in b) { - return a = b.__rrshift__ (a); - } - else { - return a >>= b; - } -}; - -export function __ior__ (a, b) { - if (typeof a == 'object' && '__ior__' in a) { - return a.__ior__ (b); - } - else if (typeof a == 'object' && '__or__' in a) { - return a = a.__or__ (b); - } - else if (typeof b == 'object' && '__ror__' in b) { - return a = b.__ror__ (a); - } - else { - return a |= b; - } -}; - -export function __ixor__ (a, b) { - if (typeof a == 'object' && '__ixor__' in a) { - return a.__ixor__ (b); - } - else if (typeof a == 'object' && '__xor__' in a) { - return a = a.__xor__ (b); - } - else if (typeof b == 'object' && '__rxor__' in b) { - return a = b.__rxor__ (a); - } - else { - return a ^= b; - } -}; - -export function __iand__ (a, b) { - if (typeof a == 'object' && '__iand__' in a) { - return a.__iand__ (b); - } - else if (typeof a == 'object' && '__and__' in a) { - return a = a.__and__ (b); - } - else if (typeof b == 'object' && '__rand__' in b) { - return a = b.__rand__ (a); - } - else { - return a &= b; - } -}; - -// Indices and slices - -export function __getitem__ (container, key) { // Slice c.q. index, direct generated call to runtime switch - if (typeof container == 'object' && '__getitem__' in container) { - return container.__getitem__ (key); // Overloaded on container - } - else if ( ['[object Array]', '[object String]'].includes(Object.prototype.toString.call(container)) ) { - const result = container[key < 0 ? container.length + key : key]; - if (result === undefined) { - throw IndexError ("index out of range", new Error()); - } - return result; - } - else { - return container [key]; // Container must support bare JavaScript brackets - /* - If it turns out keychecks really have to be supported here, the following will work - return __k__ (container, key); - Could be inlined rather than a call, but performance not crucial since non-overloaded [] in context of overloaded [] is rare - High volume numerical code will use Numscrypt anyhow which does many things via shortcuts - */ - } -}; - -export function __setitem__ (container, key, value) { // Slice c.q. index, direct generated call to runtime switch - if (typeof container == 'object' && '__setitem__' in container) { - container.__setitem__ (key, value); // Overloaded on container - } - else if ((typeof container == 'string' || container instanceof Array) && key < 0) { - container [container.length + key] = value; - } - else { - container [key] = value; // Container must support bare JavaScript brackets - } -}; - -export function __getslice__ (container, lower, upper, step) { // Slice only, no index, direct generated call to runtime switch - if (typeof container == 'object' && '__getitem__' in container) { - return container.__getitem__ ([lower, upper, step]); // Container supports overloaded slicing c.q. indexing - } - else { - return container.__getslice__ (lower, upper, step); // Container only supports slicing injected natively in prototype - } -}; - -export function __setslice__ (container, lower, upper, step, value) { // Slice, no index, direct generated call to runtime switch - if (typeof container == 'object' && '__setitem__' in container) { - container.__setitem__ ([lower, upper, step], value); // Container supports overloaded slicing c.q. indexing - } - else { - container.__setslice__ (lower, upper, step, value); // Container only supports slicing injected natively in prototype - } -}; - -__pragma__ ('endif') +__pragma__ ('stripcomments') + +// Needed for __base__ and __standard__ if global 'opov' switch is on +export function __call__ (/* , , * */) { + var args = [] .slice.apply (arguments); + if (typeof args [0] == 'object' && '__call__' in args [0]) { // Overloaded + return args [0] .__call__ .apply (args [1], args.slice (2)); + } + else { // Native + return args [0] .apply (args [1], args.slice (2)); + } +}; + +// Complete __envir__, that was created in __base__, for non-stub mode +__envir__.executor_name = __envir__.transpiler_name; + +// Make make __main__ available in browser +var __main__ = {__file__: ''}; + +// Define current exception, there's at most one exception in the air at any time +var __except__ = null; + + // Creator of a marked dictionary, used to pass **kwargs parameter +export function __kwargtrans__ (anObject) { + anObject.__kwargtrans__ = null; // Removable marker + anObject.constructor = Object; + return anObject; +} + +/* ... OBSOLETE, remove on or after y18m10d01 +// 'Oneshot' dict promotor, used to enrich __all__ and help globals () return a true dict +export function __globals__ (anObject) { + if (isinstance (anObject, dict)) { // Don't attempt to promote (enrich) again, since it will make a copy + return anObject; + } + else { + return dict (anObject) + } +} +*/ + +function build_mro(aClass) { + let done = {} + let result = []; + function iterate_bases(bases) { + for(var i = bases.length-1; i >= 0; i--) { + let base = bases[i]; + let key = base.__name__+"-"+base.__module__; + if (! (key in done)) { + iterate_bases(base.__bases__) + done[key] = true; + result.unshift(base); + } + } + } + iterate_bases(aClass.__bases__); + return result; +} + +function make_mro(cls) { + if (! cls.__mro__) { + Object.defineProperty(cls, "__mro__", {value: build_mro(cls), configurable: false}); + } + return cls.__mro__; +} + +function create_next_super(cls) { + var mro = make_mro(cls); + var index = 0; + return function(methodName) { + while(index < mro.length) { + let base = mro[index++]; + if (methodName in base.__class_attribs__) + return base; + } + throw new Exception ('Superclass method not found'); // !!! Improve! + } +} + +// Partial implementation of super () . () +export function __super__ (aClass, methodName, self) { + let context = this; + if (!context || !context.__next_super__) { + let next_super = null; + let cls = self.__class__ ? self.__class__ : self; + if (cls !== aClass) { + // we have to decide which mro to use + let long_chain = create_next_super(cls); + if (long_chain(methodName) === aClass) + next_super = long_chain + else + next_super = create_next_super(aClass); + } + else + next_super = create_next_super(aClass); + + if (context) { + context = new Proxy(context, { + get: function (target, prop) { + if (prop === "__next_super__") + return next_super; + return Reflect.get(...arguments); + }}); + } + else { + context = {__next_super__: next_super}; + } + } + + return context.__next_super__(methodName)[methodName].bind(context); +} + +// Python property installer function, no member since that would bloat classes +export function property (getter, setter) { // Returns a property descriptor rather than a property + if (!setter) { // ??? Make setter optional instead of dummy? + setter = function () {}; + } + return {get: function () {return getter (this)}, set: function (value) {setter (this, value)}, enumerable: true}; +} + +// Conditional JavaScript property installer function, prevents redefinition of properties if multiple Transcrypt apps are on one page +export function __setproperty__ (anObject, name, descriptor) { + if (!anObject.hasOwnProperty (name)) { + Object.defineProperty (anObject, name, descriptor); + } +} + +// Assert function, call to it only generated when compiling with --dassert option +export function assert (condition, message) { // Message may be undefined + if (!condition) { + throw AssertionError (message, new Error ()); + } +} + +// Merge function for keyword transfer objects +export function __mergekwargtrans__ (object0, object1) { + var result = {}; + for (var attrib in object0) { + result [attrib] = object0 [attrib]; + } + for (var attrib in object1) { + result [attrib] = object1 [attrib]; + } + return result; +}; + +// Merge function for dataclass fields +export function __mergefields__ (targetClass, sourceClass) { + let fieldNames = ['__reprfields__', '__comparefields__', '__initfields__'] + if (sourceClass [fieldNames [0]]) { + if (targetClass [fieldNames [0]]) { + for (let fieldName of fieldNames) { + targetClass [fieldName] = new Set ([...targetClass [fieldName], ...sourceClass [fieldName]]); + } + } + else { + for (let fieldName of fieldNames) { + targetClass [fieldName] = new Set (sourceClass [fieldName]); + } + } + } +} + +// Context manager support + +export function __withblock__ (manager, statements) { + if (hasattr (manager, '__enter__')) { + try { + manager.__enter__ (); + statements (); + manager.__exit__ (); + } + catch (exception) { + // Same signature as CPython : type, value, traceback + if (! (manager.__exit__ (exception.name, exception, exception.stack))) { + throw exception; + } + } + } + else { // Close an open file object, even if it doesn't support context management + statements (); + manager.close (); + } +}; + +// Manipulating attributes by name + +export function dir (obj) { + var aList = []; + for (var aKey in obj) { + aList.push (aKey.startsWith ('py_') ? aKey.slice (3) : aKey); + } + aList.sort (); + return aList; +}; + +export function setattr (obj, name, value) { + obj [name] = value; // Will not work in combination with static retrieval of aliased attributes, too expensive +}; + +export function getattr (obj, name, default_) { + if (default_ === undefined) + return name in obj ? obj [name] : obj ['py_' + name]; + try { + return name in obj ? obj [name] : default_; + } + catch (exception) { + } + return default_; +}; + +export function hasattr (obj, name) { + try { + return name in obj || 'py_' + name in obj; + } + catch (exception) { + return false; + } +}; + +export function delattr (obj, name) { + if (name in obj) { + delete obj [name]; + } + else { + delete obj ['py_' + name]; + } +}; + +// The __in__ function, used to mimic Python's 'in' operator +// In addition to CPython's semantics, the 'in' operator is also allowed to work on objects, avoiding a counterintuitive separation between Python dicts and JavaScript objects +// In general many Transcrypt compound types feature a deliberate blend of Python and JavaScript facilities, facilitating efficient integration with JavaScript libraries +// If only Python objects and Python dicts are dealt with in a certain context, the more pythonic 'hasattr' is preferred for the objects as opposed to 'in' for the dicts +export function __in__ (element, container) { + if (container === undefined || container === null) { + return false; + } + if (container.__contains__ instanceof Function) { + return container.__contains__ (element); + } + else { // Parameter 'element' itself is an array, string or a plain, non-dict JavaScript object + return ( + container.indexOf ? // If it has an indexOf + container.indexOf (element) > -1 : // it's an array or a string, + container.hasOwnProperty (element) // else it's a plain, non-dict JavaScript object + ); + } +}; + +// Find out if an attribute is special +export function __specialattrib__ (attrib) { + return (attrib.startswith ('__') && attrib.endswith ('__')) || attrib == 'constructor' || attrib.startswith ('py_'); +}; + +// Compute length of any object +export function len (anObject) { + if (anObject === undefined || anObject === null) { + return 0; + } + + if (anObject.__len__ instanceof Function) { + return anObject.__len__ (); + } + + if (anObject.length !== undefined) { + return anObject.length; + } + + var length = 0; + for (var attr in anObject) { + if (!__specialattrib__ (attr)) { + length++; + } + } + + return length; +}; + +// General conversions and checks + +export function __i__ (any) { // Convert to iterable + return py_typeof (any) == dict ? any.py_keys () : any; +} + +export function __k__ (keyed, key) { // Check existence of dict key via retrieved element + var result = keyed [key]; + if (typeof result == 'undefined') { + if (keyed instanceof Array) + if (key == +key && key >= 0 && keyed.length > key) + return result; + else + throw IndexError (key, new Error()); + else + throw KeyError (key, new Error()); + } + return result; +} + +// If the target object is somewhat true, return it. Otherwise return false. +// Try to follow Python conventions of truthyness +export function __t__ (target) { + return ( + // Avoid invalid checks + target === undefined || target === null ? false : + + // Take a quick shortcut if target is a simple type + ['boolean', 'number'] .indexOf (typeof target) >= 0 ? target : + + // Use __bool__ (if present) to decide if target is true + target.__bool__ instanceof Function ? (target.__bool__ () ? target : false) : + + // There is no __bool__, use __len__ (if present) instead + target.__len__ instanceof Function ? (target.__len__ () !== 0 ? target : false) : + + // There is no __bool__ and no __len__, declare Functions true. + // Python objects are transpiled into instances of Function and if + // there is no __bool__ or __len__, the object in Python is true. + target instanceof Function ? target : + + // Target is something else, compute its len to decide + len (target) !== 0 ? target : + + // When all else fails, declare target as false + false + ); +} + +export function float (any) { + if (any == 'inf') { + return Infinity; + } + else if (any == '-inf') { + return -Infinity; + } + else if (any == 'nan') { + return NaN; + } + else if (isNaN (parseFloat (any))) { // Call to parseFloat needed to exclude '', ' ' etc. + if (any === false) { + return 0; + } + else if (any === true) { + return 1; + } + else { // Needed e.g. in autoTester.check, so "return any ? true : false" won't do + throw ValueError ("could not convert string to float: '" + str(any) + "'", new Error ()); + } + } + else { + return +any; + } +}; +float.__name__ = 'float'; +float.__bases__ = [object]; + +export function int (any) { + return float (any) | 0 +}; +int.__name__ = 'int'; +int.__bases__ = [object]; + +__pragma__ ('ifdef', '__sform__') +Number.prototype.__format__ = function (fmt_spec) { + if (fmt_spec == undefined || fmt_spec.strip ().length == 0) { + return this.toString (); + } + var thousand_sep = false; + var g_default = false; + var width = 0; + var zero = false; + var alternate = false; + var sign = '-'; + var align = '>'; + var fill = ' '; + var precision = undefined; + var ftype = undefined; + var val = this.valueOf (); + var is_negative = val < 0; + val = Math.abs (val); + + function pad (s, width, fill, align) { + if (fill == undefined) { + fill = ' '; + } + if (align == undefined) { + align = '>'; + } + var alt = ''; + var sign = ''; + if (s.startswith (['+', '-'])) { + sign = s [0]; + s = s.substr (1); + } + if (alternate && s.startswith (['0b', '0o', '0x'])) { + alt = s.slice (0, 2); + s = s.substr (2); + } + var len = s.length + sign.length + alt.length; + var c = width - len; + switch (align) { + case '=': + return sign + alt + __mul__ (fill, c) + s; + case '>': + return __mul__ (fill, c) + sign + alt + s; + case '<': + return sign + alt + s + __mul__ (fill, c); + case '^': + var m = ((c % 2) + 2) % 2; + var c = Math.floor (c / 2); + return __mul__ (fill, c) + sign + alt + s + __mul__ (fill, c + m); + default: + throw ValueError ("Invalid align type: '" + align + "'", new Error ()); + } + }; + + function format_float (val) { + if (val.indexOf ('e+') == -1 && (ftype == 'g' || ftype == 'G')) { + var parts = val.py_split ('.'); + var d = parts [0]; + var t = parts [1]; + while (t [t.length - 1] == '0') { + t = t.slice (0, -1); + } + val = t != '' ? '.'.join ([d, t]) : d; + } + if (alternate && val.indexOf ('.') == -1) { + val = val + '.'; + } + return val; + }; + + if (fmt_spec.endswith (['b', 'c', 'd', 'e', 'E', 'f', 'F', 'g', 'G', 'n', 'o', 'x', 'X', '%'])) { + ftype = fmt_spec [fmt_spec.length - 1]; + fmt_spec = fmt_spec.slice (0, -1); + if (ftype == 'n') { + ftype = Number.isInteger (val) ? 'd' : 'f'; + } + } + else { + ftype = Number.isInteger (val) ? 'd' : 'g'; + g_default = true; + } + + var parts = fmt_spec.split ('.'); + fmt_spec = parts [0]; + precision = parts [1]; + if (precision != undefined) { + precision = parseInt (precision); + } + if (fmt_spec.length > 0 && fmt_spec [fmt_spec.length - 1] == ',') { + thousand_sep = true; + fmt_spec = fmt_spec.slice (0, -1); + } + if (fmt_spec.length > 0) { + var _width = ''; + while (fmt_spec && fmt_spec [fmt_spec.length - 1].isnumeric ()) { + _width = fmt_spec [fmt_spec.length - 1] + _width; + fmt_spec = fmt_spec.slice (0, -1); + } + if (_width.length > 0) { + if (_width [0] == '0') { + width = parseInt (_width.substr (1)); + zero = true; + } + else { + width = parseInt (_width); + } + } + if (fmt_spec.length > 0 && fmt_spec [fmt_spec.length - 1] == '#') { + alternate = true; + fmt_spec = fmt_spec.slice (0, -1); + } + if (fmt_spec.length > 0 && fmt_spec.endswith (['+', '-', ' '])) { + sign = fmt_spec [fmt_spec.length - 1]; + fmt_spec = fmt_spec.slice (0, -1); + } + if (fmt_spec.length > 0 && fmt_spec.endswith (['<', '>', '=', '^'])) { + align = fmt_spec [fmt_spec.length - 1]; + fmt_spec = fmt_spec.slice (0, -1); + } + if (fmt_spec.length > 0) { + fill = fmt_spec [0]; + } + } + + if (isNaN (val)) { + val = 'nan'; + } + else if (val == Infinity) { + val = 'inf'; + } + else { + switch (ftype) { + case 'b': + val = Math.floor (val).toString (2); + if (alternate) { + val = '0b' + val; + } + break; + case 'c': + val = String.fromCharCode (Math.floor (val)); + break; + case 'd': + val = Math.floor (val).toString (); + if (thousand_sep) { + val = val.replace (/\B(?=(\d{3})+(?!\d))/g, ','); + } + break; + case 'o': + val = Math.floor (val).toString (8); + if (alternate) { + val = '0o' + val; + } + break; + case 'x': + case 'X': + val = Math.floor (val).toString (16); + if (alternate) { + val = '0x' + val; + } + break; + case 'e': + case 'E': + if (precision == undefined) { + precision = 6; + } + var num_exp = val.toExponential (precision).split ('e+'); + var num = num_exp [0]; + var exp = num_exp [1]; + val = num.toString () + 'e+' + pad (exp.toString(), 2, '0'); + val = format_float (val); + break; + case 'f': + case 'F': + case '%': + if (precision == undefined) { + precision = 6; + } + if (ftype == '%') { + val *= 100; + } + val = val.toFixed (precision); + val = format_float (val); + if (ftype == '%') { + val += '%'; + } + break; + case 'g': + case 'G': + if (precision == undefined) { + precision = g_default ? 1 : 6; + } + if (precision == 0) { + precision = 1; + } + var convert_to_exponent = false; + if (g_default) { + var parts = val.toString ().split ('.'); + var digit_count = parts [0].length + parts [1].length; + if (digit_count >= precision) { + convert_to_exponent = true; + } + } + var num_exp = val.toExponential (precision - 1).split ('e+'); + var num = num_exp [0]; + var exp = num_exp [1]; + convert_to_exponent |= !((-4 <= exp && exp < precision)); + if (convert_to_exponent) { + val = num.toString() + 'e+' + pad (exp.toString(), 2, '0'); + } + else { + val = val.toFixed (precision - 1 - exp); + } + val = format_float (val); + break; + default: + throw ValueError ("Invalid format type: '" + ftype + "'", new Error ()); + } + } + if (ftype === ftype.toUpperCase ()) { + val = val.toUpperCase () + } + if (ftype != 'c') { + if (sign == '-') { + if (is_negative) { + val = '-' + val; + } + } + else { + val = is_negative ? '-' + val : sign + val; + } + } + if (zero) { + fill = '0'; + align = '='; + } + if (width > 0) { + val = pad (val, width, fill, align); + } + return val; +}; +__pragma__ ('endif') + +export function bool (any) { // Always truly returns a bool, rather than something truthy or falsy + return !!__t__ (any); +}; +bool.__name__ = 'bool'; // So it can be used as a type with a name +bool.__bases__ = [int]; + + +export var FunctionType = {__name__: "function", __module__: "builtins", __bases__: [object]}; + +export function py_typeof (anObject) { + var aType = typeof anObject; + if (aType == 'object') { // Directly trying '__class__ in anObject' turns out to wreck anObject in Chrome if its a primitive + try { + return '__class__' in anObject ? anObject.__class__ : object; + } + catch (exception) { + return aType; + } + } + else if (aType == 'function') { + return FunctionType; + } + else { + return ( // Oddly, the braces are required here + aType == 'boolean' ? bool : + aType == 'string' ? str : + aType == 'number' ? (anObject % 1 == 0 ? int : float) : + null + ); + } +}; + +var pid_counter = 0 + +export function py_id (anObject) { + var pid = anObject.py_identifier + if (pid) { + return pid; + } + + var aType = typeof anObject; + if (aType == 'object') { + pid = anObject.py_identifier = "__po" + pid_counter; + pid_counter++; + } + else { + pid = anObject.toString(); + } + return pid; +} + +export function issubclass (aClass, classinfo) { + if (classinfo instanceof Array) { // Assume in most cases it isn't, then making it recursive rather than two functions saves a call + for (let aClass2 of classinfo) { + if (issubclass (aClass, aClass2)) { + return true; + } + } + return false; + } + try { + var aClass2 = aClass; + if (aClass2 == classinfo) { + return true; + } + else { + var bases = [].slice.call (aClass2.__bases__); + while (bases.length) { + aClass2 = bases.shift (); + if (aClass2 == classinfo) { + return true; + } + if (aClass2.__bases__.length) { + bases = [].slice.call (aClass2.__bases__).concat (bases); + } + } + return false; + } + } + catch (exception) { // Using issubclass on primitives assumed rare + return aClass == classinfo || classinfo == object; + } +}; + +export function isinstance (anObject, classinfo) { + try { + return '__class__' in anObject ? issubclass (anObject.__class__, classinfo) : issubclass (py_typeof (anObject), classinfo); + } + catch (exception) { + return issubclass (py_typeof (anObject), classinfo); + } +}; + +export function callable (anObject) { + return anObject && typeof anObject == 'object' && '__call__' in anObject ? true : typeof anObject === 'function'; +}; + +// Repr function uses __repr__ method, then __str__, then toString +export function repr (anObject) { + try { + return anObject.__repr__ (); + } + catch (exception) { + try { + return anObject.__str__ (); + } + catch (exception) { // anObject has no __repr__ and no __str__ + try { + if (anObject == null) { + return 'None'; + } + else if (anObject.constructor == Object) { + var result = '{'; + var comma = false; + for (var attrib in anObject) { + if (!__specialattrib__ (attrib)) { + if (attrib.isnumeric ()) { + var attribRepr = attrib; // If key can be interpreted as numerical, we make it numerical + } // So we accept that '1' is misrepresented as 1 + else { + var attribRepr = '\'' + attrib + '\''; // Alpha key in dict + } + + if (comma) { + result += ', '; + } + else { + comma = true; + } + result += attribRepr + ': ' + repr (anObject [attrib]); + } + } + result += '}'; + return result; + } + else { + return typeof anObject == 'boolean' ? anObject.toString () .capitalize () : anObject.toString (); + } + } + catch (exception) { + return ''; + } + } + } +}; + +// Char from Unicode or ASCII +export function chr (charCode) { + return String.fromCharCode (charCode); +}; + +// Unicode or ASCII from char +export function ord (aChar) { + return aChar.charCodeAt (0); +}; + +function min_max (f_compare, ...args) { + // Assume no kwargs + let dflt = undefined; + function key(x) {return x} + + if (args.length > 0) { + if (args[args.length-1] && args[args.length-1].hasOwnProperty ("__kwargtrans__")) { + const kwargs = args[args.length - 1]; + args = args.slice(0, -1); + if (kwargs.hasOwnProperty('py_default')) dflt = kwargs['py_default']; + if (kwargs.hasOwnProperty('key')) key = kwargs['key']; + if (Object.prototype.toString.call(key) !== '[object Function]') throw TypeError("object is not callable", new Error()); + } + } + + if (args.length === 0) throw TypeError("expected at least 1 argument, got 0", new Error ()); + if (args.length > 1 && dflt !== undefined) throw TypeError("Cannot specify a default with multiple positional arguments", new Error ()); + if (args.length === 1){ + if (Object.prototype.toString.call(args[0]) !== '[object Array]') throw TypeError("object is not iterable", new Error()); + args = args[0]; // Passed in arg is itself an iterable + } + if (args.length === 0){ + if (dflt === undefined) throw ValueError ("arg is an empty sequence", new Error ()); + return dflt + } + + return args.reduce((max_val, cur_val) => f_compare(key(cur_val), key(max_val)) ? cur_val : max_val); +} + +// Maximum of n values +export function max (...args) { + return min_max(function (a, b){return a > b}, ...args) +} + +// Minimum of n numbers +export function min (...args) { + return min_max(function (a, b){return a < b}, ...args) +} + +// Integer to binary +export function bin (nbr) { + const sign = nbr<0 ? '-' : ''; + const bin_val = Math.abs(parseInt(nbr)).toString(2); + return sign.concat('0b', bin_val); +}; + +// Integer to octal +export function oct (nbr) { + const sign = nbr<0 ? '-' : ''; + const oct_val = Math.abs(parseInt(nbr)).toString(8); + return sign.concat('0o', oct_val); +}; + +// Integer to hexadecimal +export function hex (nbr) { + const sign = nbr<0 ? '-' : ''; + const hex_val = Math.abs(parseInt(nbr)).toString(16); + return sign.concat('0x', hex_val); +}; + +// Absolute value +__pragma__ ('ifdef', '__complex__') +export function abs (x) { + try { + return Math.abs (x); + } + catch (exception) { + return Math.sqrt (x.real * x.real + x.imag * x.imag); + } +}; +__pragma__ ('else') +export var abs = Math.abs; +__pragma__ ('endif') + +// Bankers rounding +export function round (number, ndigits) { + if (ndigits) { + var scale = Math.pow (10, ndigits); + number *= scale; + } + + var rounded = Math.round (number); + if (rounded - number == 0.5 && rounded % 2) { // Has rounded up to odd, should have rounded down to even + rounded -= 1; + } + + if (ndigits) { + rounded /= scale; + } + + return rounded; +}; + +__pragma__ ('ifdef', '__sform__') +export function format (value, fmt_spec) { + if (value == undefined) { + return 'None'; + } + fmt_spec = fmt_spec || ''; + var tval = typeof value; + switch (tval) { + case 'number': + case 'string': + return value.__format__(fmt_spec); + case 'boolean': + return fmt_spec ? (value ? 1 : 0).__format__(fmt_spec) : str (value); + case 'object': + if ('__format__' in value) { + return value.__format__ (fmt_spec); + } + else { + return str (value).__format__ (fmt_spec); + } + default: + return str (value).__format__ (fmt_spec); + } +} +__pragma__ ('endif') + +// BEGIN unified iterator model + +export function __jsUsePyNext__ () { // Add as 'next' method to make Python iterator JavaScript compatible + try { + var result = this.__next__ (); + return {value: result, done: false}; + } + catch (exception) { + return {value: undefined, done: true}; + } +} + +export function __pyUseJsNext__ () { // Add as '__next__' method to make JavaScript iterator Python compatible + var result = this.next (); + if (result.done) { + throw StopIteration (new Error ()); + } + else { + return result.value; + } +} + +export function py_iter (iterable) { // Alias for Python's iter function, produces a universal iterator / iterable, usable in Python and JavaScript + if (typeof iterable == 'string' || '__iter__' in iterable) { // JavaScript Array or string or Python iterable (string has no 'in') + var result = iterable.__iter__ (); // Iterator has a __next__ + result.next = __jsUsePyNext__; // Give it a next + } + else if ('selector' in iterable) { // Assume it's a JQuery iterator + var result = list (iterable) .__iter__ (); // Has a __next__ + result.next = __jsUsePyNext__; // Give it a next + } + else if ('next' in iterable) { // It's a JavaScript iterator already, maybe a generator, has a next and may have a __next__ + var result = iterable + if (! ('__next__' in result)) { // If there's no danger of recursion + result.__next__ = __pyUseJsNext__; // Give it a __next__ + } + } + else if (Symbol.iterator in iterable) { // It's a JavaScript iterable such as a typed array, but not an iterator + var result = iterable [Symbol.iterator] (); // Has a next + result.__next__ = __pyUseJsNext__; // Give it a __next__ + } + else { + throw IterableError (new Error ()); // No iterator at all + } + result [Symbol.iterator] = function () {return result;}; + return result; +} + +export function py_next (iterator, value) { // Called only in a Python context, could receive Python or JavaScript iterator + try { // Primarily assume Python iterator, for max speed + var result = iterator.__next__ (); + } + catch (exception) { // JavaScript iterators are the exception here + var result = iterator.next (); + if (result.done) { + if(!(value === undefined)) return value + throw StopIteration (new Error ()); + } + else { + return result.value; + } + } + if (result === undefined) { + if(!(value === undefined)) return value + throw StopIteration (new Error ()); + } + else { + return result; + } +} + +export function __PyIterator__ (iterable) { + this.iterable = iterable; + this.index = 0; + this.__len__ = function () {return iterable.length}; +} + +__PyIterator__.prototype.__next__ = function() { + if (this.index < this.iterable.length) { + return this.iterable [this.index++]; + } + else { + throw StopIteration (new Error ()); + } +}; + +export function __JsIterator__ (iterable) { + this.iterable = iterable; + this.index = 0; +} + +__JsIterator__.prototype.next = function () { + if (this.index < this.iterable.py_keys.length) { + return {value: this.index++, done: false}; + } + else { + return {value: undefined, done: true}; + } +}; + +// END unified iterator model + +// Reversed function for arrays +export function py_reversed (iterable) { + iterable = iterable.slice (); + iterable.reverse (); + return iterable; +}; + +// Zip method for arrays and strings +export function zip () { + var args = [] .slice.call (arguments); + for (var i = 0; i < args.length; i++) { + if (typeof args [i] == 'string') { + args [i] = args [i] .split (''); + } + else if (!Array.isArray (args [i])) { + args [i] = Array.from (args [i]); + } + } + var shortest = args.length == 0 ? [] : args.reduce ( // Find shortest array in arguments + function (array0, array1) { + return array0.length < array1.length ? array0 : array1; + } + ); + return shortest.map ( // Map each element of shortest array + function (current, index) { // To the result of this function + return args.map ( // Map each array in arguments + function (current) { // To the result of this function + return current [index]; // Namely it's index't entry + } + ); + } + ); +}; + +// Range method, returning an array +export function range (start, stop, step) { + if (stop == undefined) { + // one param defined + stop = start; + start = 0; + } + if (step == undefined) { + step = 1; + } + if ((step > 0 && start >= stop) || (step < 0 && start <= stop)) { + return []; + } + var result = []; + for (var i = start; step > 0 ? i < stop : i > stop; i += step) { + result.push(i); + } + return result; +}; + +// Any, all and sum + +export function any (iterable) { + for (let item of iterable) { + if (bool (item)) { + return true; + } + } + return false; +} +export function all (iterable) { + for (let item of iterable) { + if (! bool (item)) { + return false; + } + } + return true; +} +export function sum (iterable) { + let result = 0; + for (let item of iterable) { + result += item; + } + return result; +} + +function* __enumerate__ (iterable, start=0) { + if (start.hasOwnProperty("__kwargtrans__")) { + // start was likely passed in as kwarg + start = start['start']; + } + let n = start + for (const item of iterable) { + yield [n, item] + n += 1 + } +} +export var py_enumerate = __enumerate__; // Exporting a generator function in JS may be problematic but it allows enumerate to be lazy + +// List extensions to Array + +export function list (iterable) { // All such creators should be callable without new + let instance = iterable ? Array.from (iterable) : []; + // Sort is the normal JavaScript sort, Python sort is a non-member function + return instance; +} +Array.prototype.__class__ = list; // All arrays are lists (not only if constructed by the list ctor), unless constructed otherwise +list.__name__ = 'list'; +list.__bases__ = [object]; + +Array.prototype.__iter__ = function () {return new __PyIterator__ (this);}; + +Array.prototype.__getslice__ = function (start, stop, step) { + if (step === null) { + step = 1; + } + if (start === null) { + start = (step < 0 ? -1 : 0); + } + if (start < 0) { + start = Math.max(this.length + start, 0); + } else if (start > this.length || (start === this.length && step < 0)) { + start = this.length > 0 ? this.length - 1 : 0; + } + + if (stop === null) { + stop = (step < 0 && this.length > 0 ? -1 : this.length); + } else if (stop < 0) { + stop = Math.max(this.length + stop, (step < 0 && this.length > 0 ? -1 : 0)); + } else if (stop > this.length) { + stop = this.length; + } + + if (step === 1) { + return Array.prototype.slice.call(this, start, stop); + } + + let result = list ([]); + if (step > 0) { + for (let index = start; index < stop; index += step) { + result.push (this [index]); + } + } else if (step < 0) { + for (let index = start; index > stop; index += step) { + result.push (this [index]); + } + } else { + throw ValueError ("slice step cannot be zero", new Error ()); + } + + return result; +}; + +Array.prototype.__setslice__ = function (start, stop, step, source) { + if (step === null) { + step = 1; + } + if (start === null) { + start = (step < 0 ? -1 : 0); + } + if (start < 0) { + start = Math.max(this.length + start, 0); + } else if (start > this.length || (start === this.length && step < 0)) { + start = this.length > 0 ? this.length - 1 : 0; + } + + if (stop === null) { + stop = (step < 0 && this.length > 0 ? -1 : this.length); + } else if (stop < 0) { + stop = Math.max(this.length + stop, (step < 0 && this.length > 0 ? -1 : 0)); + } else if (stop > this.length) { + stop = this.length; + } + + if (step === 1) { // Assign to 'ordinary' slice, replace subsequence + Array.prototype.splice.apply (this, [start, stop - start] .concat (Array.from(source))); + } + else { + // Validate assignment is valid based on Python's size rules + const seq_len = Math.ceil((stop - start) / step) + if((source.length > 0 || seq_len > 0) && (seq_len !== source.length)){ + // throw ValueError ("Invalid slice assignment", new Error ()); + throw ValueError ("attempt to assign sequence of size " + source.length + " to extended slice of size " + seq_len, new Error ()); + } + // Assign to extended slice, replace designated items one by one + let sourceIndex = 0; + if (step > 0) { + for (let targetIndex = start; targetIndex < stop; targetIndex += step) { + this [targetIndex] = source [sourceIndex++]; + } + } else if (step < 0) { + for (let targetIndex = start; targetIndex > stop; targetIndex += step) { + this [targetIndex] = source [sourceIndex++]; + } + } else { + throw ValueError ("slice step cannot be zero", new Error ()); + } + + } +}; + +Array.prototype.__repr__ = function () { + if (this.__class__ == set && !this.length) { + return 'set()'; + } + + let result = !this.__class__ || this.__class__ == list ? '[' : this.__class__ == tuple ? '(' : '{'; + + for (let index = 0; index < this.length; index++) { + if (index) { + result += ', '; + } + result += repr (this [index]); + } + + if (this.__class__ == tuple && this.length == 1) { + result += ','; + } + + result += !this.__class__ || this.__class__ == list ? ']' : this.__class__ == tuple ? ')' : '}';; + return result; +}; + +Array.prototype.__str__ = Array.prototype.__repr__; + +Array.prototype.append = function (element) { + this.push (element); +}; + +Array.prototype.py_clear = function () { + this.length = 0; +}; + +Array.prototype.py_copy = function () { + return this.slice(); +}; + +Array.prototype.extend = function (aList) { + this.push.apply (this, aList); +}; + +Array.prototype.insert = function (index, element) { + this.splice (index, 0, element); +}; + +Array.prototype.remove = function (element) { + let index = this.indexOf (element); + if (index === -1) { + throw ValueError("list.remove(x): x not in list", new Error ()); + } + this.splice (index, 1); +}; + +Array.prototype.index = function (element) { + return this.indexOf (element); +}; + +Array.prototype.py_pop = function (index) { + if(this.length === 0){ + throw IndexError("pop from empty list", new Error()) + } + if (index === undefined) { + return this.pop (); // Remove last element + } + else { + const idx = index < 0 ? this.length + index : index + if(this[idx] === undefined){ + throw IndexError("pop index out of range", new Error()) + } + return this.splice (idx, 1) [0]; + } +}; + +Array.prototype.py_sort = function () { + __sort__.apply (null, [this].concat ([] .slice.apply (arguments))); // Can't work directly with arguments + // Python params: (iterable, key = None, reverse = False) + // py_sort is called with the Transcrypt kwargs mechanism, and just passes the params on to __sort__ + // __sort__ is def'ed with the Transcrypt kwargs mechanism +}; + +Array.prototype.__add__ = function (aList) { + return list (this.concat (aList)); +}; + +Array.prototype.__mul__ = function (scalar) { + let result = this; + for (let i = 1; i < scalar; i++) { + result = result.concat (this); + } + return result; +}; + +Array.prototype.__rmul__ = Array.prototype.__mul__; + +// Tuple extensions to Array + +export function tuple (iterable) { + let instance = iterable ? [] .slice.apply (iterable) : []; + instance.__class__ = tuple; // Not all arrays are tuples + return instance; +} +tuple.__name__ = 'tuple'; +tuple.__bases__ = [object]; + +// Set extensions to Array +// N.B. Since sets are unordered, set operations will occasionally alter the 'this' array by sorting it + +export function set (iterable) { + let instance = []; + if (iterable) { + for (let index = 0; index < iterable.length; index++) { + instance.add (iterable [index]); + } + } + instance.__class__ = set; // Not all arrays are sets + return instance; +} +set.__name__ = 'set'; +set.__bases__ = [object]; + + +export function frozenset (iterable) { + let instance = []; + if (iterable) { + for (let index = 0; index < iterable.length; index++) { + instance.add (iterable [index]); + } + } + instance.__class__ = frozenset; // Not all arrays are sets + return instance; +} +frozenset.__name__ = 'frozenset'; +frozenset.__bases__ = [object]; + + +Array.prototype.__bindexOf__ = function (element) { // Used to turn O (n^2) into O (n log n) +// Since sorting is lex, compare has to be lex. This also allows for mixed lists + + element += ''; + + let mindex = 0; + let maxdex = this.length - 1; + + while (mindex <= maxdex) { + let index = (mindex + maxdex) / 2 | 0; + let middle = this [index] + ''; + + if (middle < element) { + mindex = index + 1; + } + else if (middle > element) { + maxdex = index - 1; + } + else { + return index; + } + } + + return -1; +}; + +Array.prototype.add = function (element) { + if (this.indexOf (element) == -1) { // Avoid duplicates in set + this.push (element); + } +}; + +Array.prototype.discard = function (element) { + var index = this.indexOf (element); + if (index != -1) { + this.splice (index, 1); + } +}; + +Array.prototype.isdisjoint = function (other) { + this.sort (); + for (let i = 0; i < other.length; i++) { + if (this.__bindexOf__ (other [i]) != -1) { + return false; + } + } + return true; +}; + +Array.prototype.issuperset = function (other) { + this.sort (); + for (let i = 0; i < other.length; i++) { + if (this.__bindexOf__ (other [i]) == -1) { + return false; + } + } + return true; +}; + +Array.prototype.issubset = function (other) { + return set (other.slice ()) .issuperset (this); // Sort copy of 'other', not 'other' itself, since it may be an ordered sequence +}; + +Array.prototype.union = function (other) { + let result = set (this.slice () .sort ()); + for (let i = 0; i < other.length; i++) { + if (result.__bindexOf__ (other [i]) == -1) { + result.push (other [i]); + } + } + return result; +}; + +Array.prototype.intersection = function (other) { + this.sort (); + let result = set (); + for (let i = 0; i < other.length; i++) { + if (this.__bindexOf__ (other [i]) != -1) { + result.push (other [i]); + } + } + return result; +}; + +Array.prototype.difference = function (other) { + let sother = set (other.slice () .sort ()); + let result = set (); + for (let i = 0; i < this.length; i++) { + if (sother.__bindexOf__ (this [i]) == -1) { + result.push (this [i]); + } + } + return result; +}; + +Array.prototype.symmetric_difference = function (other) { + return this.union (other) .difference (this.intersection (other)); +}; + +Array.prototype.py_update = function () { // O (n) + let updated = [] .concat.apply (this.slice (), arguments) .sort (); + this.py_clear (); + for (let i = 0; i < updated.length; i++) { + if (updated [i] != updated [i - 1]) { + this.push (updated [i]); + } + } +}; + +Array.prototype.__eq__ = function (other) { // Also used for list + if (this.length != other.length) { + return false; + } + if (this.__class__ == set) { + this.sort (); + other.sort (); + } + for (let i = 0; i < this.length; i++) { + if (this [i] != other [i]) { + return false; + } + } + return true; +}; + +Array.prototype.__ne__ = function (other) { // Also used for list + return !this.__eq__ (other); +}; + +Array.prototype.__le__ = function (other) { + if (this.__class__ == set) { + return this.issubset (other); + } + else { + for (let i = 0; i < this.length; i++) { + if (this [i] > other [i]) { + return false; + } + else if (this [i] < other [i]) { + return true; + } + } + return true; + } +}; + +Array.prototype.__ge__ = function (other) { + if (this.__class__ == set) { + return this.issuperset (other); + } + else { + for (let i = 0; i < this.length; i++) { + if (this [i] < other [i]) { + return false; + } + else if (this [i] > other [i]) { + return true; + } + } + return true; + } +}; + +Array.prototype.__lt__ = function (other) { + return ( + this.__class__ == set ? + this.issubset (other) && !this.issuperset (other) : + !this.__ge__ (other) + ); +}; + +Array.prototype.__gt__ = function (other) { + return ( + this.__class__ == set ? + this.issuperset (other) && !this.issubset (other) : + !this.__le__ (other) + ); +}; + +// Byte array extensions + +export function bytearray (bytable, encoding) { + if (bytable == undefined) { + return new Uint8Array (0); + } + else { + let aType = py_typeof (bytable); + if (aType == int) { + return new Uint8Array (bytable); + } + else if (aType == str) { + let aBytes = new Uint8Array (len (bytable)); + for (let i = 0; i < len (bytable); i++) { + aBytes [i] = bytable.charCodeAt (i); + } + return aBytes; + } + else if (aType == list || aType == tuple) { + return new Uint8Array (bytable); + } + else { + throw py_TypeError; + } + } +} + +export var bytes = bytearray; + + +Uint8Array.prototype.__add__ = function (aBytes) { + let result = new Uint8Array (this.length + aBytes.length); + result.set (this); + result.set (aBytes, this.length); + return result; +}; + +Uint8Array.prototype.__mul__ = function (scalar) { + let result = new Uint8Array (scalar * this.length); + for (let i = 0; i < scalar; i++) { + result.set (this, i * this.length); + } + return result; +}; + +Uint8Array.prototype.__rmul__ = Uint8Array.prototype.__mul__; + +// String extensions + +export function str (stringable) { + if (typeof stringable === 'number') + return stringable.toString(); + else { + try { + return stringable.__str__ (); + } + catch (exception) { + try { + return repr (stringable); + } + catch (exception) { + return String (stringable); // No new, so no permanent String object but a primitive in a temporary 'just in time' wrapper + } + } + } +}; + +String.prototype.__class__ = str; // All strings are str +str.__name__ = 'str'; +str.__bases__ = [object]; + +String.prototype.__iter__ = function () {new __PyIterator__ (this);}; + +String.prototype.__repr__ = function () { + return (this.indexOf ('\'') == -1 ? '\'' + this + '\'' : '"' + this + '"') .py_replace ('\t', '\\t') .py_replace ('\n', '\\n'); +}; + +String.prototype.__str__ = function () { + return this; +}; + +String.prototype.capitalize = function () { + return this.charAt (0).toUpperCase () + this.slice (1); +}; + +String.prototype.endswith = function (suffix, start=0, end) { + if (end === undefined) {end = this.length} + const str = this.slice(start, end) + + if (suffix instanceof Array) { + for (var i=0;i this.length) { + start = this.length > 0 ? this.length + (step < 0 ? -1 : 0) : 0; + } + + if (stop === null) { + stop = (step < 0 && this.length > 0 ? -1 : this.length); + } else if (stop < 0) { + stop = Math.max(this.length + stop, (step < 0 && this.length > 0 ? -1 : 0)); + } else if (stop > this.length) { + stop = this.length; + } + + if (step === 1) { + return this.substring (start, (start > stop ? start : stop)); + } + + let result = ''; + if (step > 0) { + for (var index = start; index < stop; index += step) { + result = result.concat (this.charAt(index)); + } + } else if (step < 0) { + for (var index = start; index > stop; index += step) { + result = result.concat (this.charAt(index)); + } + } + else { + throw ValueError ("slice step cannot be zero", new Error ()); + } + + return result; +}; + +__pragma__ ('ifdef', '__sform__') +String.prototype.__format__ = function (fmt_spec) { + if (fmt_spec == undefined || fmt_spec.strip ().length == 0) { + return this.valueOf (); + } + var width = 0; + var align = '<'; + var fill = ' '; + var val = this.valueOf (); + + function pad (s, width, fill, align) { + var len = s.length; + var c = width - len; + switch (align) { + case '>': + return __mul__ (fill, c) + s; + case '<': + return s + __mul__ (fill, c); + case '^': + var m = ((c % 2) + 2) % 2; + var c = Math.floor (c / 2); + return __mul__ (fill, c) + s + __mul__ (fill, c + m); + default: + return s; + } + }; + + if (fmt_spec [fmt_spec.length - 1] == 's') { + fmt_spec = fmt_spec.slice (0, -1); + } + if (fmt_spec.length > 0) { + var _width = ''; + while (fmt_spec && fmt_spec [fmt_spec.length - 1].isnumeric ()) { + _width = fmt_spec [fmt_spec.length - 1] + _width; + fmt_spec = fmt_spec.slice (0, -1); + } + if (_width.length > 0) { + width = parseInt (_width); + } + if (fmt_spec.length > 0 && fmt_spec.endswith (['<', '>', '^'])) { + align = fmt_spec [fmt_spec.length - 1]; + fmt_spec = fmt_spec.slice (0, -1); + } + if (fmt_spec.length > 0) { + fill = fmt_spec [0]; + } + } + if (width > 0) { + val = pad (val, width, fill, align); + } + return val; +}; +__pragma__ ('endif') + +// Since it's worthwhile for the 'format' function to be able to deal with *args, it is defined as a property +// __get__ will produce a bound function if there's something before the dot +// Since a call using *args is compiled to e.g. ..apply (null, args), the function has to be bound already +// Otherwise it will never be, because of the null argument +// Using 'this' rather than 'null' contradicts the requirement to be able to pass bound functions around +// The object 'before the dot' won't be available at call time in that case, unless implicitly via the function bound to it +// While for Python methods this mechanism is generated by the compiler, for JavaScript methods it has to be provided manually +// Call memoizing is unattractive here, since every string would then have to hold a reference to a bound format method +__setproperty__ (String.prototype, 'format', { + get: function () {return __get__ (this, function (self) { + var args = tuple ([] .slice.apply (arguments).slice (1)); + var autoIndex = 0; +__pragma__ ('ifdef', '__sform__') + return self.replace (/\{([^\{]*)\}/g, function (match, key) { + var parts = key.split (':'); + key = parts [0]; + var fmt_spec = parts [1]; + parts = key.split ('!') + key = parts [0]; + var conversion = parts [1]; + var value = undefined; + if (key == '') { + key = autoIndex++; + } + if (key == +key && args [key] !== undefined) { // So key is numerical + value = args [key]; + } + else { // Key is a string + var attr = undefined; + var idx = ("" + key) .indexOf ('.'); // ??? Why is conversion to string suddenly needed? + if (idx != -1) { + attr = key.substring (idx + 1); + key = key.substring (0, idx); + } + else { + idx = ("" + key) .indexOf ('['); // ??? Why is conversion to string suddenly needed? + if (idx != -1) { + attr = key.substring (idx + 1).slice (0, -1); + key = key.substring (0, idx); + } + } + + if ((key == +key) && attr && args [key] !== undefined) { + value = args [key][attr]; + } + else { + for (var index = 0; index < args.length; index++) { + // Find first 'dict' that has that key and the right field + if (typeof args [index] == 'object' && args [index] != null && args [index][key] !== undefined) { // Why is check for null suddenly needed? + // Return that field field + if (attr) { + value = args [index][key][attr]; + } + else { + value = args [index][key]; + } + break; + } + } + } + } + if (value === undefined) { + return match; + } + if (conversion == 'r') { + value = repr (value); + } + else if (conversion == 's') { + value = str (value); + } + else if (conversion == 'a') { + throw ValueError ("Conversion to ascii not yet supported: '" + match + "'", new Error ()); + } + return format (value, fmt_spec); + }); +__pragma__ ('else') + return self.replace (/\{(\w*)\}/g, function (match, key) { + if (key == '') { + key = autoIndex++; + } + if (key == +key) { // So key is numerical + return args [key] === undefined ? match : str (args [key]); + } + else { // Key is a string + for (var index = 0; index < args.length; index++) { + // Find first 'dict' that has that key and the right field + if (typeof args [index] == 'object' && args [index][key] !== undefined) { + return str (args [index][key]); // Return that field field + } + } + return match; + } + }); +__pragma__ ('endif') + });}, + enumerable: true +}); + +String.prototype.isalnum = function () { + return /^[0-9a-zA-Z]{1,}$/.test(this) +} + +String.prototype.isalpha = function () { + return /^[a-zA-Z]{1,}$/.test(this) +} + +String.prototype.isdecimal = function () { + return /^[0-9]{1,}$/.test(this) +} + +String.prototype.isdigit = function () { + return this.isdecimal() +} + +String.prototype.islower = function () { + return /^[a-z]{1,}$/.test(this) +} + +String.prototype.isupper = function () { + return /^[A-Z]{1,}$/.test(this) +} + +String.prototype.isspace = function () { + return /^[\s]{1,}$/.test(this) +} + +String.prototype.isnumeric = function () { + return !isNaN (parseFloat (this)) && isFinite (this); +}; + +String.prototype.join = function (strings) { + strings = Array.from (strings); // Much faster than iterating through strings char by char + return strings.join (this); +}; + +String.prototype.lower = function () { + return this.toLowerCase (); +}; + +String.prototype.py_replace = function (old, aNew, maxreplace) { + if (maxreplace === undefined || maxreplace < 0) { + return this.split(old).join(aNew); + } else if (maxreplace === 0) { + return this; + } else { + const pre = this.split(old, maxreplace).join(aNew); + const rest = this.slice(this.split(old, maxreplace).join(old).length + 1) + return pre.concat(rest.length>0 ? aNew : '', rest); + } +}; + +String.prototype.lstrip = function (chars) { + if (chars) { + var start = 0; + while (chars.indexOf (this[start]) >= 0) { + start += 1; + } + return this.slice (start); + } + return this.replace (/^\s*/g, ''); +}; + +String.prototype.rfind = function (sub, start) { + return this.lastIndexOf (sub, start); +}; + +String.prototype.rsplit = function (sep, maxsplit) { // Combination of general whitespace sep and positive maxsplit neither supported nor checked, expensive and rare + if (sep == undefined || sep == null) { + sep = /\s+/; + var stripped = this.strip (); + } + else { + var stripped = this; + } + + if (maxsplit == undefined || maxsplit == -1) { + return stripped.split (sep); + } + else { + var result = stripped.split (sep); + if (maxsplit < result.length) { + var maxrsplit = result.length - maxsplit; + return [result.slice (0, maxrsplit) .join (sep)] .concat (result.slice (maxrsplit)); + } + else { + return result; + } + } +}; + +String.prototype.rstrip = function (chars) { + if (chars) { + var end = this.length - 1; + while (chars.indexOf (this[end]) >= 0) { + end -= 1; + } + return this.slice (0, end + 1); + } + return this.replace (/\s*$/g, ''); +}; + +String.prototype.py_split = function (sep, maxsplit) { // Combination of general whitespace sep and positive maxsplit neither supported nor checked, expensive and rare + if (sep == undefined || sep == null) { + sep = /\s+/; + var stripped = this.strip (); + } + else { + var stripped = this; + } + + if (maxsplit == undefined || maxsplit == -1) { + return stripped.split (sep); + } + else { + var result = stripped.split (sep); + if (maxsplit < result.length) { + return result.slice (0, maxsplit).concat ([result.slice (maxsplit).join (sep)]); + } + else { + return result; + } + } +}; + +String.prototype.splitlines = function (keepends) { + if (this.length === 0) { + return []; + } + + if (keepends === undefined || keepends === null || keepends === false) { + return this.trimEnd().split(/\r?\n|\r|\n/g); + } + else { + return this.split(/(?<=\n)(?=\n)|(?<=[\r\n])(?=[^\r\n])/g); + } +}; + +String.prototype.startswith = function (prefix, start=0, end) { + if (end === undefined) {end = this.length} + const str = this.slice(start, end) + + if (prefix instanceof Array) { + for (let i=0;i> b; + } +}; + +export function __or__ (a, b) { + if (a && typeof a == 'object' && '__or__' in a) { + return a.__or__ (b); + } + else if (b && typeof b == 'object' && '__ror__' in b) { + return b.__ror__ (a); + } + else { + return a | b; + } +}; + +export function __xor__ (a, b) { + if (a && typeof a == 'object' && '__xor__' in a) { + return a.__xor__ (b); + } + else if (b && typeof b == 'object' && '__rxor__' in b) { + return b.__rxor__ (a); + } + else { + return a ^ b; + } +}; + +export function __and__ (a, b) { + if (a && typeof a == 'object' && '__and__' in a) { + return a.__and__ (b); + } + else if (b && typeof b == 'object' && '__rand__' in b) { + return b.__rand__ (a); + } + else { + return a & b; + } +}; + +// Overloaded binary compare + +export function __eq__ (a, b) { + if (a && typeof a == 'object' && '__eq__' in a) { + return a.__eq__ (b); + } + else if (b && typeof b == 'object' && '__eq__' in b) { + return b.__eq__ (a); + } + else { + return a == b; + } +}; + +export function __ne__ (a, b) { + if (a && typeof a == 'object' && '__ne__' in a) { + return a.__ne__ (b); + } + else if (b && typeof b == 'object' && '__ne__' in b) { + return b.__ne__ (a); + } + else { + return a != b + } +}; + +export function __lt__ (a, b) { + if (a && typeof a == 'object' && '__lt__' in a) { + return a.__lt__ (b); + } + else { + return a < b; + } +}; + +export function __le__ (a, b) { + if (a && typeof a == 'object' && '__le__' in a) { + return a.__le__ (b); + } + else { + return a <= b; + } +}; + +export function __gt__ (a, b) { + if (a && typeof a == 'object' && '__gt__' in a) { + return a.__gt__ (b); + } + else { + return a > b; + } +}; + +export function __ge__ (a, b) { + if (a && typeof a == 'object' && '__ge__' in a) { + return a.__ge__ (b); + } + else { + return a >= b; + } +}; + +// Overloaded augmented general + +export function __imatmul__ (a, b) { + if ('__imatmul__' in a) { + return a.__imatmul__ (b); + } + else { + return a.__matmul__ (b); + } +}; + +export function __ipow__ (a, b) { + if (typeof a == 'object' && '__pow__' in a) { + return a.__ipow__ (b); + } + else if (typeof a == 'object' && '__ipow__' in a) { + return a.__pow__ (b); + } + else if (typeof b == 'object' && '__rpow__' in b) { + return b.__rpow__ (a); + } + else { + return Math.pow (a, b); + } +}; + +export function __ijsmod__ (a, b) { + if (typeof a == 'object' && '__imod__' in a) { + return a.__ismod__ (b); + } + else if (typeof a == 'object' && '__mod__' in a) { + return a.__mod__ (b); + } + else if (typeof b == 'object' && '__rpow__' in b) { + return b.__rmod__ (a); + } + else { + return a % b; + } +}; + +export function __imod__ (a, b) { + if (typeof a == 'object' && '__imod__' in a) { + return a.__imod__ (b); + } + else if (typeof a == 'object' && '__mod__' in a) { + return a.__mod__ (b); + } + else if (typeof b == 'object' && '__rmod__' in b) { + return b.__rmod__ (a); + } + else { + return ((a % b) + b) % b; + } +}; + +// Overloaded augmented arithmetic + +export function __imul__ (a, b) { + if (typeof a == 'object' && '__imul__' in a) { + return a.__imul__ (b); + } + else if (typeof a == 'object' && '__mul__' in a) { + return a = a.__mul__ (b); + } + else if (typeof b == 'object' && '__rmul__' in b) { + return a = b.__rmul__ (a); + } + else if (typeof a == 'string') { + return a = a.__mul__ (b); + } + else if (typeof b == 'string') { + return a = b.__rmul__ (a); + } + else { + return a *= b; + } +}; + +export function __idiv__ (a, b) { + if (typeof a == 'object' && '__idiv__' in a) { + return a.__idiv__ (b); + } + else if (typeof a == 'object' && '__div__' in a) { + return a = a.__div__ (b); + } + else if (typeof b == 'object' && '__rdiv__' in b) { + return a = b.__rdiv__ (a); + } + else { + return a /= b; + } +}; + +export function __iadd__ (a, b) { + if (typeof a == 'object' && '__iadd__' in a) { + return a.__iadd__ (b); + } + else if (typeof a == 'object' && '__add__' in a) { + return a = a.__add__ (b); + } + else if (typeof b == 'object' && '__radd__' in b) { + return a = b.__radd__ (a); + } + else { + return a += b; + } +}; + +export function __isub__ (a, b) { + if (typeof a == 'object' && '__isub__' in a) { + return a.__isub__ (b); + } + else if (typeof a == 'object' && '__sub__' in a) { + return a = a.__sub__ (b); + } + else if (typeof b == 'object' && '__rsub__' in b) { + return a = b.__rsub__ (a); + } + else { + return a -= b; + } +}; + +// Overloaded augmented bitwise + +export function __ilshift__ (a, b) { + if (typeof a == 'object' && '__ilshift__' in a) { + return a.__ilshift__ (b); + } + else if (typeof a == 'object' && '__lshift__' in a) { + return a = a.__lshift__ (b); + } + else if (typeof b == 'object' && '__rlshift__' in b) { + return a = b.__rlshift__ (a); + } + else { + return a <<= b; + } +}; + +export function __irshift__ (a, b) { + if (typeof a == 'object' && '__irshift__' in a) { + return a.__irshift__ (b); + } + else if (typeof a == 'object' && '__rshift__' in a) { + return a = a.__rshift__ (b); + } + else if (typeof b == 'object' && '__rrshift__' in b) { + return a = b.__rrshift__ (a); + } + else { + return a >>= b; + } +}; + +export function __ior__ (a, b) { + if (typeof a == 'object' && '__ior__' in a) { + return a.__ior__ (b); + } + else if (typeof a == 'object' && '__or__' in a) { + return a = a.__or__ (b); + } + else if (typeof b == 'object' && '__ror__' in b) { + return a = b.__ror__ (a); + } + else { + return a |= b; + } +}; + +export function __ixor__ (a, b) { + if (typeof a == 'object' && '__ixor__' in a) { + return a.__ixor__ (b); + } + else if (typeof a == 'object' && '__xor__' in a) { + return a = a.__xor__ (b); + } + else if (typeof b == 'object' && '__rxor__' in b) { + return a = b.__rxor__ (a); + } + else { + return a ^= b; + } +}; + +export function __iand__ (a, b) { + if (typeof a == 'object' && '__iand__' in a) { + return a.__iand__ (b); + } + else if (typeof a == 'object' && '__and__' in a) { + return a = a.__and__ (b); + } + else if (typeof b == 'object' && '__rand__' in b) { + return a = b.__rand__ (a); + } + else { + return a &= b; + } +}; + +// Indices and slices + +export function __getitem__ (container, key) { // Slice c.q. index, direct generated call to runtime switch + if (typeof container == 'object' && '__getitem__' in container) { + return container.__getitem__ (key); // Overloaded on container + } + else if ( ['[object Array]', '[object String]'].includes(Object.prototype.toString.call(container)) ) { + const result = container[key < 0 ? container.length + key : key]; + if (result === undefined) { + throw IndexError ("index out of range", new Error()); + } + return result; + } + else { + return container [key]; // Container must support bare JavaScript brackets + /* + If it turns out keychecks really have to be supported here, the following will work + return __k__ (container, key); + Could be inlined rather than a call, but performance not crucial since non-overloaded [] in context of overloaded [] is rare + High volume numerical code will use Numscrypt anyhow which does many things via shortcuts + */ + } +}; + +export function __setitem__ (container, key, value) { // Slice c.q. index, direct generated call to runtime switch + if (typeof container == 'object' && '__setitem__' in container) { + container.__setitem__ (key, value); // Overloaded on container + } + else if ((typeof container == 'string' || container instanceof Array) && key < 0) { + container [container.length + key] = value; + } + else { + container [key] = value; // Container must support bare JavaScript brackets + } +}; + +export function __getslice__ (container, lower, upper, step) { // Slice only, no index, direct generated call to runtime switch + if (typeof container == 'object' && '__getitem__' in container) { + return container.__getitem__ ([lower, upper, step]); // Container supports overloaded slicing c.q. indexing + } + else { + return container.__getslice__ (lower, upper, step); // Container only supports slicing injected natively in prototype + } +}; + +export function __setslice__ (container, lower, upper, step, value) { // Slice, no index, direct generated call to runtime switch + if (typeof container == 'object' && '__setitem__' in container) { + container.__setitem__ ([lower, upper, step], value); // Container supports overloaded slicing c.q. indexing + } + else { + container.__setslice__ (lower, upper, step, value); // Container only supports slicing injected natively in prototype + } +}; + +__pragma__ ('endif') diff --git a/transcrypt/modules/org/transcrypt/__core__.js b/transcrypt/modules/org/transcrypt/__core__.js index e5d364965..4a73b1cc1 100644 --- a/transcrypt/modules/org/transcrypt/__core__.js +++ b/transcrypt/modules/org/transcrypt/__core__.js @@ -56,18 +56,18 @@ will generate var modules = {}; __nest__ (a, 'b.c.d.e', __init__ (__world__.a.b.c.d.e)); __nest__ (a, 'b.c', __init__ (__world__.a.b.c)); - + The task of the __nest__ function is to start at the head object and then walk to the chain of objects behind it (tail), -creating the ones that do not exist already, and insert the necessary module reference attributes into them. +creating the ones that do not exist already, and insert the necessary module reference attributes into them. */ -export function __nest__ (headObject, tailNames, value) { +export function __nest__ (headObject, tailNames, value) { var current = headObject; // In some cases this will be
.__all__, // which is the main module and is also known under the synonym
is the entry point of a Transcrypt application, // Carrying the same name as the application except the file name extension. - + if (tailNames != '') { // Split on empty string doesn't give empty list // Find the last already created object in tailNames var tailChain = tailNames.split ('.'); @@ -79,20 +79,20 @@ export function __nest__ (headObject, tailNames, value) { } current = current [tailChain [index]]; } - + // Create the rest of the objects, if any for (var index = firstNewIndex; index < tailChain.length; index++) { current [tailChain [index]] = {}; current = current [tailChain [index]]; } } - - // Insert its new properties, it may have been created earlier and have other attributes - for (let attrib of Object.getOwnPropertyNames (value)) { + + // Insert its new properties, it may have been created earlier and have other attributes + for (let attrib of Object.getOwnPropertyNames (value)) { Object.defineProperty (current, attrib, { get () {return value [attrib];}, enumerable: true, - configurable: true + configurable: true }); } }; @@ -113,21 +113,28 @@ export function __init__ (module) { export function __get__ (aThis, func, quotedFuncName) {// Param aThis is thing before the dot, if it's there if (aThis) { if (aThis.hasOwnProperty ('__class__') || typeof aThis == 'string' || aThis instanceof String) { // Object before the dot + var bound = function () { // Return bound function, code duplication for efficiency if no memoizing + var args = [] .slice.apply (arguments); // So multilayer search prototype, apply __get__, call curry func that calls func + return func.apply (null, [aThis.__proxy__ ? aThis.__proxy__ : aThis] .concat (args)); + }; + if (quotedFuncName) { // Memoize call since fcall is on, by installing bound function in instance + Object.defineProperty (func, "name", {value:quotedFuncName}) + // copy addintional attributes + for(var n in func) { + bound[n] = func[n]; + } + bound.__repr__ = function() { + return "method {} of {}".format(quotedFuncName, repr(aThis)); }; + Object.defineProperty (aThis, quotedFuncName, { // Will override the non-own property, next time it will be called directly - value: function () { // So next time just call curry function that calls function - var args = [] .slice.apply (arguments); - return func.apply (null, [aThis] .concat (args)); - }, + value: bound, writable: true, enumerable: true, configurable: true }); } - return function () { // Return bound function, code duplication for efficiency if no memoizing - var args = [] .slice.apply (arguments); // So multilayer search prototype, apply __get__, call curry func that calls func - return func.apply (null, [aThis.__proxy__ ? aThis.__proxy__ : aThis] .concat (args)); - }; + return bound; } else { // Class before the dot return func; // Return static method @@ -156,12 +163,42 @@ export function __getcm__ (aThis, func, quotedFuncName) { export function __getsm__ (aThis, func, quotedFuncName) { return func; }; - -// Mother of all metaclasses + + +function _is_python_descryptor(descript) { + if (descript.value === undefined || descript.value === null) + return false; + + return descript.value.__get__ !== undefined; +} + +function _to_python_descriptor(instance, descript) { + // use python descriptor protocol + var value = descript.value; + + var get = value.__get__; + descript.get = function() { + return get(instance); + } + + if (value.__set__) { + var set = value.__set__; + descript.set = function(val) { + set(instance, val); + } + } + + delete descript.value; + delete descript.writable; + return descript; +} + +// Mother of all metaclasses export var py_metatype = { __name__: 'type', __bases__: [], - + __class_attribs__: {__init__: true}, + // Overridable class creation worker __new__: function (meta, name, bases, attribs) { // Create the class cls, a functor, which the class creator function will return @@ -169,7 +206,9 @@ export var py_metatype = { var args = [] .slice.apply (arguments); // It has a __new__ method, not yet but at call time, since it is copied from the parent in the loop below return cls.__new__ (args); // Each Python class directly or indirectly derives from object, which has the __new__ method }; // If there are no bases in the Python source, the compiler generates [object] for this parameter - + + var python_descriptors = [] + // Copy all methods, including __new__, properties and static attributes from base classes to new cls object // The new class object will simply be the prototype of its instances // JavaScript prototypical single inheritance will do here, since any object has only one class @@ -182,30 +221,48 @@ export var py_metatype = { continue; } Object.defineProperty (cls, attrib, descrip); - } + if (_is_python_descryptor (descrip)) + python_descriptors.push (attrib); + } for (let symbol of Object.getOwnPropertySymbols (base)) { let descrip = Object.getOwnPropertyDescriptor (base, symbol); Object.defineProperty (cls, symbol, descrip); } } - + // Add class specific attributes to the created cls object cls.__metaclass__ = meta; cls.__name__ = name.startsWith ('py_') ? name.slice (3) : name; cls.__bases__ = bases; - + cls.__class_attribs__ = attribs; + + if (! ("__init__" in attribs)) { + attribs["__init__"] = function() { + __super__.call(this, cls, "__init__", arguments[0]).apply(this, arguments); + } + } + // Add own methods, properties and own static attributes to the created cls object for (var attrib in attribs) { var descrip = Object.getOwnPropertyDescriptor (attribs, attrib); Object.defineProperty (cls, attrib, descrip); + if (_is_python_descryptor (descrip)) + python_descriptors.push (attrib); } for (let symbol of Object.getOwnPropertySymbols (attribs)) { let descrip = Object.getOwnPropertyDescriptor (attribs, symbol); Object.defineProperty (cls, symbol, descrip); } + if (python_descriptors.length) + cls.__descriptors__ = python_descriptors; + + meta.__init__(cls, name, bases, attribs) // Return created cls object return cls; + }, + + __init__: function(cls, name, bases, attribs) { } }; py_metatype.__metaclass__ = py_metatype; @@ -213,23 +270,24 @@ py_metatype.__metaclass__ = py_metatype; // Mother of all classes export var object = { __init__: function (self) {}, - + __metaclass__: py_metatype, // By default, all classes have metaclass type, since they derive from object __name__: 'object', + __class_attribs__: {__init__: true}, __bases__: [], - + // Object creator function, is inherited by all classes (so could be global) - __new__: function (args) { // Args are just the constructor args + __new__: function (args) { // Args are just the constructor args // In JavaScript the Python class is the prototype of the Python object // In this way methods and static attributes will be available both with a class and an object before the dot // The descriptor produced by __get__ will return the right method flavor var instance = Object.create (this, {__class__: {value: this, enumerable: true}}); - + if ('__getattr__' in this || '__setattr__' in this) { instance.__proxy__ = new Proxy (instance, { get: function (target, name) { let result = target [name]; - if (result == undefined) { // Target doesn't have attribute named name + if (result === undefined) { // Target doesn't have attribute named name return target.__getattr__ (name); } else { @@ -246,7 +304,14 @@ export var object = { return true; } }) - instance = instance.__proxy__ + instance = instance.__proxy__ + } + + if (this.__descriptors__) { + for (var attrib of this.__descriptors__) { + var descrip = Object.getOwnPropertyDescriptor (this, attrib); + Object.defineProperty (instance, attrib, _to_python_descriptor(instance, descrip)); + } } // Call constructor @@ -254,15 +319,20 @@ export var object = { // Return constructed instance return instance; - } + } }; // Class creator facade function, calls class creation worker export function __class__ (name, bases, attribs, meta) { // Parameter meta is optional if (meta === undefined) { - meta = bases [0] .__metaclass__; + meta = py_metatype; + for(let b of bases) { + if (b.__metaclass__ !== py_metatype) { + meta = b.__metaclass__; + break; + } + } } - return meta.__new__ (meta, name, bases, attribs); }; diff --git a/transcrypt/modules/org/transcrypt/__runtime__.py b/transcrypt/modules/org/transcrypt/__runtime__.py index a51a4417d..7d81034dd 100644 --- a/transcrypt/modules/org/transcrypt/__runtime__.py +++ b/transcrypt/modules/org/transcrypt/__runtime__.py @@ -27,15 +27,15 @@ def __init__ (self, *args, **kwargs): else: self.stack = 'No stack trace available' #__pragma__ ('nokwargs') - + def __repr__ (self): if len (self.__args__) > 1: return '{}{}'.format (self.__class__.__name__, repr (tuple (self.__args__))) elif len (self.__args__): - return '{}({})'.format (self.__class__.__name__, repr (self.__args__ [0])) + return '{}({})'.format (self.__class__.__name__, repr (self.__args__ [0])) else: return '{}()'.format (self.__class__.__name__) - + def __str__ (self): if len (self.__args__) > 1: return str (tuple (self.__args__)) @@ -43,23 +43,23 @@ def __str__ (self): return str (self.__args__ [0]) else: return '' - + class IterableError (Exception): def __init__ (self, error): Exception.__init__ (self, 'Can\'t iterate over non-iterable', error = error) - + class StopIteration (Exception): def __init__ (self, error): Exception.__init__ (self, 'Iterator exhausted', error = error) - + class ValueError (Exception): def __init__ (self, message, error): Exception.__init__ (self, message, error = error) - + class KeyError (Exception): def __init__ (self, message, error): Exception.__init__ (self, message, error = error) - + class AssertionError (Exception): def __init__ (self, message, error): if message: @@ -83,6 +83,11 @@ class TypeError (Exception): def __init__(self, message, error): Exception.__init__(self, message, error = error) +class RuntimeError (Exception): + def __init__(self, message, error): + Exception.__init__(self, message, error = error) + + # Warnings Exceptions # N.B. This is a limited subset of the warnings defined in # the cpython implementation to keep things small for now. @@ -100,7 +105,7 @@ class DeprecationWarning (Warning): class RuntimeWarning (Warning): pass - + #__pragma__ ('kwargs') def _sort(iterable, key = None, reverse = False): # Used by py_sort and sorted, can deal with kwargs @@ -147,10 +152,10 @@ def filter(func, iterable): if func == None: func = bool return [item for item in iterable if func (item)] - + def divmod (n, d): return n // d, n % d - + #__pragma__ ('ifdef', '__complex__') class complex: @@ -165,32 +170,32 @@ def __init__ (self, real, imag = None): else: self.real = real self.imag = imag - + def __neg__ (self): return complex (-self.real, -self.imag) - + def __exp__ (self): modulus = Math.exp (self.real) return complex (modulus * Math.cos (self.imag), modulus * Math.sin (self.imag)) - + def __log__ (self): return complex (Math.log (Math.sqrt (self.real * self.real + self.imag * self.imag)), Math.atan2 (self.imag, self.real)) - + def __pow__ (self, other): # a ** b = exp (b log a) return (self.__log__ () .__mul__ (other)) .__exp__ () - + def __rpow__ (self, real): # real ** comp -> comp.__rpow__ (real) return self.__mul__ (Math.log (real)) .__exp__ () - + def __mul__ (self, other): if __typeof__ (other) is 'number': return complex (self.real * other, self.imag * other) else: return complex (self.real * other.real - self.imag * other.imag, self.real * other.imag + self.imag * other.real) - + def __rmul__ (self, real): # real + comp -> comp.__rmul__ (real) return complex (self.real * real, self.imag * real) - + def __div__ (self, other): if __typeof__ (other) is 'number': return complex (self.real / other, self.imag / other) @@ -200,59 +205,59 @@ def __div__ (self, other): (self.real * other.real + self.imag * other.imag) / denom, (self.imag * other.real - self.real * other.imag) / denom ) - + def __rdiv__ (self, real): # real / comp -> comp.__rdiv__ (real) denom = self.real * self.real return complex ( (real * self.real) / denom, (real * self.imag) / denom ) - + def __add__ (self, other): if __typeof__ (other) is 'number': return complex (self.real + other, self.imag) else: # Assume other is complex return complex (self.real + other.real, self.imag + other.imag) - + def __radd__ (self, real): # real + comp -> comp.__radd__ (real) return complex (self.real + real, self.imag) - + def __sub__ (self, other): if __typeof__ (other) is 'number': return complex (self.real - other, self.imag) else: return complex (self.real - other.real, self.imag - other.imag) - + def __rsub__ (self, real): # real - comp -> comp.__rsub__ (real) return complex (real - self.real, -self.imag) - + def __repr__ (self): return '({}{}{}j)'.format (self.real, '+' if self.imag >= 0 else '', self.imag) - + def __str__ (self): return __repr__ (self) [1 : -1] - + def __eq__ (self, other): if __typeof__ (other) is 'number': return self.real == other else: return self.real == other.real and self.imag == other.imag - + def __ne__ (self, other): if __typeof__ (other) is 'number': return self.real != other else: return self.real != other.real or self.imag != other.imag - + def conjugate (self): return complex (self.real, -self.imag) - + def __conj__ (aNumber): if isinstance (aNumber, complex): return complex (aNumber.real, -aNumber.imag) else: return complex (aNumber, 0) - + #__pragma__ ('endif') class __Terminal__: @@ -267,37 +272,37 @@ class __Terminal__: def __init__ (self): self.buffer = '' - + try: self.element = document.getElementById ('__terminal__') except: self.element = None - + if self.element: self.element.style.overflowX = 'auto' self.element.style.boxSizing = 'border-box' self.element.style.padding = '5px' self.element.innerHTML = '_' - + #__pragma__ ('kwargs') - + def print (self, *args, sep = ' ', end = '\n'): - self.buffer = '{}{}{}'.format (self.buffer, sep.join ([str (arg) for arg in args]), end) [-4096 : ] - + self.buffer = '{}{}{}'.format (self.buffer, sep.join ([str (arg) for arg in args]), end) [-4096 : ] + if self.element: self.element.innerHTML = self.buffer.replace ('\n', '
') .replace (' ', ' ') self.element.scrollTop = self.element.scrollHeight else: console.log (sep.join ([str (arg) for arg in args])) - + def input (self, question): self.print ('{}'.format (question), end = '') answer = window.prompt ('\n'.join (self.buffer.split ('\n') [-8:])) self.print (answer) return answer - + #__pragma__ ('nokwargs') - + __terminal__ = __Terminal__ () print = __terminal__.print diff --git a/transcrypt/modules/org/transcrypt/compiler.py b/transcrypt/modules/org/transcrypt/compiler.py index de6b155d7..3567008de 100644 --- a/transcrypt/modules/org/transcrypt/compiler.py +++ b/transcrypt/modules/org/transcrypt/compiler.py @@ -71,10 +71,10 @@ def __init__ ( # Set paths self.sourcePrepath = os.path.abspath (utils.commandArgs.source) .replace ('\\', '/') - + self.sourceDir = '/'.join (self.sourcePrepath.split ('/') [ : -1]) self.mainModuleName = self.sourcePrepath.split ('/') [-1] - + if utils.commandArgs.outdir: if os.path.isabs (utils.commandArgs.outdir): self.targetDir = utils.commandArgs.outdir.replace ('\\', '/') @@ -82,7 +82,7 @@ def __init__ ( self.targetDir = f'{self.sourceDir}/{utils.commandArgs.outdir}'.replace ('\\', '/') else: self.targetDir = f'{self.sourceDir}/__target__'.replace ('\\', '/') - + self.projectPath = f'{self.targetDir}/{self.mainModuleName}.project' # Load the most recent project metadata @@ -381,7 +381,7 @@ def pragmasFromComments (sourceCode): # This function turns comment-like pragma's into regular ones, both for multi-line and single-line pragma's # It changes rather than regenerates the sourcecode, since tokenize/untokenize will mess up formatting # Single line pragma's are always comment-like and will be turned into multi-line function-like pragma's - # Also in this function executable comments are converted to normal code + # Also in this function executable comments are converted to normal code # Tokenize the source code, to be able to recognize comments easily tokens = tokenize.tokenize (io.BytesIO (sourceCode.encode ('utf-8')) .readline) @@ -396,16 +396,16 @@ def pragmasFromComments (sourceCode): if tokenType == tokenize.COMMENT: strippedComment = tokenString [1 : ] .lstrip () if strippedComment.startswith ('__pragma__'): - + # Remember line index of multi-line pragma, like: # __pragma__ (... pragmaCommentLineIndices.append (startRowColumn [0] - 1) elif strippedComment.replace (' ', '') .replace ('\t', '') .startswith ('__:'): - + # Remember line index of single-line pragma, like: # __: ... shortPragmaCommentLineIndices.append (startRowColumn [0] - 1) if tokenType == tokenize.NAME and tokenString == '__pragma__': pragmaIndex = tokenIndex - + if tokenIndex - pragmaIndex == 2: pragmaKind = tokenString [1:-1] if pragmaKind == 'ecom': @@ -415,20 +415,20 @@ def pragmasFromComments (sourceCode): # Convert original, non-tokenized sourcecode to a list of lines sourceLines = sourceCode.split ('\n') - + # Use line indices of multi-line function-like ecom / noecom pragma's to transform these lines into executable comment switches for ecomPragmaLineIndex in ecomPragmaLineIndices: sourceLines [ecomPragmaLineIndex] = ecom for noecomPragmaLineIndex in noecomPragmaLineIndices: sourceLines [noecomPragmaLineIndex] = noecom - + # Use line indices of multi-line comment-like pragma singles to transform these into function-like pragma singles (which often turn out te be part of a matching pair) allowExecutableComments = utils.commandArgs.ecom for pragmaCommentLineIndex in pragmaCommentLineIndices: indentation, separator, tail = sourceLines [pragmaCommentLineIndex] .partition ('#') pragma, separator, comment = tail.partition ('#') pragma = pragma.replace (' ', '') .replace ('\t', '') - + # Turn appropriate lines into executable comment switches if "('ecom')" in pragma or '("ecom")' in pragma: allowExecutableComments = True @@ -445,17 +445,17 @@ def pragmasFromComments (sourceCode): strippedHead = head.lstrip () indent = head [ : len (head) - len (strippedHead)] pragmaName = tail.replace (' ', '') .replace ('\t', '') [3:] - + # Turn appropriate lines into executable comment switches if pragmaName == 'ecom': - sourceLines [pragmaCommentLineIndex] = ecom + sourceLines [pragmaCommentLineIndex] = ecom elif pragmaName == 'noecom': - sourceLines [pragmaCommentLineIndex] = noecom + sourceLines [pragmaCommentLineIndex] = noecom elif pragmaName.startswith ('no'): sourceLines [shortPragmaCommentLineIndex] = '{}__pragma__ (\'{}\'); {}; __pragma__ (\'{}\')' .format (indent, pragmaName, head, pragmaName [2:]) # Correct! else: sourceLines [shortPragmaCommentLineIndex] = '{}__pragma__ (\'{}\'); {}; __pragma__ (\'no{}\')' .format (indent, pragmaName, head, pragmaName) - + # Switch executable comments on c.q. off and turn executable comments into normal code lines for Transcrypt (as opposed to CPython) uncommentedSourceLines = [] for sourceLine in sourceLines: @@ -469,7 +469,7 @@ def pragmasFromComments (sourceCode): uncommentedSourceLines.append (sourceLine.replace ('#?', '', 1) if lStrippedSourceLine.startswith ('#?') else sourceLine) else: uncommentedSourceLines.append (sourceLine) - + # Return joined lines, to be used for parsing return '\n'.join (uncommentedSourceLines) @@ -478,7 +478,7 @@ def pragmasFromComments (sourceCode): with tokenize.open (self.sourcePath) as sourceFile: self.sourceCode = utils.extraLines + sourceFile.read () - + self.parseTree = ast.parse (pragmasFromComments (self.sourceCode)) for node in ast.walk (self.parseTree): @@ -1479,7 +1479,21 @@ def include (fileName): # type () function if node.func.id == 'type': - self.emit ('py_typeof (') # Don't use general alias, since this is the type operator, not the type metaclass + if len(node.args) > 1: + self.emit ('__class__ (') + for i, a in enumerate(node.args): + if i: + self.emit (', ') + self.visit (a) + self.emit (')') + else: + self.emit ('py_typeof (') # Don't use general alias, since this is the type operator, not the type metaclass + self.visit (node.args [0]) + self.emit (')') + return + + elif node.func.id == 'id': + self.emit('py_id (') self.visit (node.args [0]) self.emit (')') return @@ -1757,7 +1771,7 @@ def include (fileName): ast.Call ( func = ast.Call ( func = ast.Name ( - id = '__super__', + id = '__super__.bind(this)', ctx = ast.Load ), args = [ @@ -1767,6 +1781,10 @@ def include (fileName): ), ast.Constant ( value = node.func.attr # + ), + ast.Name ( + id = 'self', + ctx = ast.Load ) ], keywords = [] @@ -2387,14 +2405,14 @@ def visit_Compare (self, node): if len (node.comparators) > 1: self.emit(')') - + def visit_Constant (self, node): if type (node.value) == str: self.emit ('{}', repr (node.s)) # Use repr (node.s) as second, rather than first parameter, since node.s may contain {} elif type (node.value) == bytes: - self.emit ('bytes (\'{}\')', node.s.decode ('ASCII')) + self.emit ('bytes (\'{}\')', node.s.decode ('ASCII')) elif type (node.value) == complex: - self.emit ('complex (0, {})'.format (node.n.imag)) + self.emit ('complex (0, {})'.format (node.n.imag)) elif type (node.value) in {float, int}: self.emit ('{}'.format (node.n)) else: @@ -2624,6 +2642,7 @@ def pushPropertyAccessor(functionName): isClassMethod = False isStaticMethod = False isProperty = False + keep_name = False getter = '__get__' if node.decorator_list: @@ -2679,6 +2698,7 @@ def pushPropertyAccessor(functionName): self.emit ('{}: ', self.filterId (nodeName)) else: self.emit ('get {} () {{return {} (this, ', self.filterId (nodeName), getter) + keep_name = True elif isGlobal: if type (node.parentNode) == ast.Module and not nodeName in self.allOwnNames: self.emit ('export ') @@ -2711,7 +2731,8 @@ def pushPropertyAccessor(functionName): if isStaticMethod: self.emit ('get {} () {{return {}function', self.filterId (nodeName), 'async ' if anAsync else '') else: - self.emit ('get {} () {{return {} (this, {}function', self.filterId (nodeName), getter, 'async ' if anAsync else '') + keep_name = True + self.emit ('get {} () {{return {} (this, ({}function', self.filterId (nodeName), getter, 'async ' if anAsync else '') elif isGlobal: if type (node.parentNode) == ast.Module and not nodeName in self.allOwnNames: self.emit ('export ') @@ -2720,7 +2741,6 @@ def pushPropertyAccessor(functionName): self.emit ('var {} = {}function', self.filterId (nodeName), 'async ' if anAsync else '') yieldStarIndex = self.fragmentIndex - self.emit (' ') skipFirstArg = jsCall and not (not isMethod or isStaticMethod or isProperty) @@ -2753,9 +2773,13 @@ def pushPropertyAccessor(functionName): if docString: self.emit (' .__setdoc__ (\'{}\')', docString.replace ('\n', '\\n ').replace('\'', '\\\'')) - if decorate: self.emit (')' * decoratorsUsed) + if keep_name: + self.emit (', "{}"', self.filterId (nodeName)) + + elif keep_name: + self.emit ('), "{}"', self.filterId (nodeName)) if isMethod: if not jsCall: @@ -2896,7 +2920,7 @@ def revisit_ImportFrom (self, importHoistMemo): # From ... import ... can import node = importHoistMemo.node self.adaptLineNrString (node) # If it isn't (again) obtained from the node, the memoed version will be used - if node.module.startswith (self.stubsName): + if node.module is not None and node.module.startswith (self.stubsName): return ''' @@ -2919,6 +2943,20 @@ def revisit_ImportFrom (self, importHoistMemo): # From ... import ... can import self.module.program.searchedModulePaths = [] # If none of the possibilities below succeeds, report all searched paths namePairs = [] facilityImported = False + + if node.level: + # from ..foo.bar import a + module_name = self.module.name.split(".") + if self.module.sourcePrename == "__init__": + module_name.append("__init__") + + module_name = module_name[:-node.level] + if node.module is not None: + module_name.append(node.module) + module_name = ".".join(module_name) + else: + module_name = node.module + for index, alias in enumerate (node.names): if alias.name == '*': # * Never refers to modules, only to facilities in modules if len (node.names) > 1: @@ -2926,21 +2964,21 @@ def revisit_ImportFrom (self, importHoistMemo): # From ... import ... can import lineNr = self.lineNr, message = '\n\tCan\'t import module \'{}\''.format (alias.name) ) - module = self.useModule (node.module) + module = self.useModule (module_name) for aName in module.exportedNames: namePairs.append (utils.Any (name = aName, asName = None)) else: try: # Try if alias denotes a module, in that case don't do the 'if namepairs' part - module = self.useModule ('{}.{}'.format (node.module, alias.name)) # So, attempt to use alias as a module + module = self.useModule ('{}.{}'.format (module_name, alias.name)) # So, attempt to use alias as a module self.emit ('import * as {} from \'{}\';\n', self.filterId (alias.asname) if alias.asname else self.filterId (alias.name), module.importRelPath) # Modules too can have asName self.allImportedNames.add (alias.asname or alias.name) # Add import to allImportedNames of this module except: # It's a facility rather than a module - module = self.useModule (node.module) + module = self.useModule (module_name) namePairs.append (utils.Any (name = alias.name, asName = alias.asname)) facilityImported = True if facilityImported: # At least one alias denoted a facility rather than a module - module = self.useModule (node.module) # Use module that contains it + module = self.useModule (module_name) # Use module that contains it namePairs.append (utils.Any (name = alias.name, asName = alias.asname)) # This part should only be done for facilities inside modules, and indeed they are the only ones adding namePairs diff --git a/transcrypt/modules/org/transcrypt/utils.py b/transcrypt/modules/org/transcrypt/utils.py index 5625f0679..d87b44615 100644 --- a/transcrypt/modules/org/transcrypt/utils.py +++ b/transcrypt/modules/org/transcrypt/utils.py @@ -17,17 +17,17 @@ def __init__ (self, **attribs): class CommandArgsError (BaseException): pass - + class CommandArgsExit (BaseException): pass - + class ArgumentParser (argparse.ArgumentParser): def error (self, message): self.print_help (sys.stdout) if message: log (True, '\nError: {}\n', message) raise CommandArgsError () - + def exit (self, status = 0, message = None): if message: log (True, 'Exit: {}', message) @@ -36,7 +36,7 @@ def exit (self, status = 0, message = None): class CommandArgs: def parse (self): self.argParser = ArgumentParser () - + self.argParser.add_argument ('source', nargs='?', help = ".py file containing source code of main module") self.argParser.add_argument ('-a', '--anno', help = "annotate target files that were compiled from Python with source file names and source line numbers", action = 'store_true') self.argParser.add_argument ('-am', '--alimod', help = "use aliasing for module paths", action = 'store_true') @@ -68,7 +68,7 @@ def parse (self): self.argParser.add_argument ('-od', '--outdir', help = 'override output directory (default = __target__)') self.argParser.add_argument ('-p', '--parent', nargs = '?', help = "object that will hold application, default is window. Use -p .none to generate orphan application, e.g. for use in node.js") self.argParser.add_argument ('-r', '--run', help = "run source file rather than compiling it", action = 'store_true') - self.argParser.add_argument ('-s', '--symbols', nargs ='?', help = "names, joined by $, separately passed to main module in __symbols__ variable") + self.argParser.add_argument ('-s', '--symbols', nargs ='?', help = "names, joined by $, separately passed to main module in __symbols__ variable", action="append") self.argParser.add_argument ('-sf', '--sform', help = "enable support for string formatting mini language", action = 'store_true') self.argParser.add_argument ('-t', '--tconv', help = "enable automatic conversion to truth value by default. Disadvised, since it will result in a conversion for each boolean. Preferably use __pragma__ ('tconv') and __pragma__ (\'notconv\') to enable automatic conversion locally", action = 'store_true') self.argParser.add_argument ('-u', '--unit', nargs='?', help = "compile to units rather than to monolithic application. Use -u .auto to autogenerate dynamically loadable native JavaScript modules, one per Python module. Use -u .run to generate the loader and the staticcally loadable runtime unit. Use -u .com to generate a statically loadable component unit.") @@ -76,38 +76,38 @@ def parse (self): self.argParser.add_argument ('-x', '--x', help = "reserved for extended options") self.argParser.add_argument ('-xr', '--xreex', help = "re-export all imported names", action = 'store_true') self.argParser.add_argument ('-xg', '--xglobs', help = "allow use of the 'globals' function", action = 'store_true') - self.argParser.add_argument ('-xp', '--xpath', nargs = '?', help = "additional module search paths, joined by $, #'s will be replaced by spaces") + self.argParser.add_argument ('-xp', '--xpath', nargs = '?', help = "additional module search paths, joined by $, #'s will be replaced by spaces", action="append") self.argParser.add_argument ('-xt', '--xtiny', help = "generate tiny version of runtime, a.o. lacking support for implicit and explicit operator overloading. Use only if generated code can be validated, since it will introduce semantic alterations in edge cases", action = 'store_true') self.argParser.add_argument ('-*', '--star', help = "Like it? Grow it! Go to GitHub and then click [* Star]", action = 'store_true') - + self.projectOptions = self.argParser.parse_args () .__dict__ self.__dict__.update (self.projectOptions) - + # Signal invalid switches - + def logAndExit (message): log (True, message) sys.exit (1) - + invalidCombi = 'Invalid combination of options' - + if not (self.license or self.star or self.source): logAndExit (self.argParser.format_usage () .capitalize ()) elif self.map and self.unit: - logAndExit ('{}: -m / --map and -u / --unit'.format (invalidCombi)) + logAndExit ('{}: -m / --map and -u / --unit'.format (invalidCombi)) elif self.parent and self.unit == '.com': logAndExit ('{}: -p / --parent and -u / --unit .com'.format (invalidCombi)) elif self.parent == '.export' and self.esv and int (self.esv) < 6: - logAndExit ('{}: -p / --parent .export and -e / --esv < 6'.format (invalidCombi)) + logAndExit ('{}: -p / --parent .export and -e / --esv < 6'.format (invalidCombi)) elif self.unit == '.auto' and self.esv and int (self.esv) < 6: logAndExit ('{}: -u / --unit .auto and -e / --esv < 6'.format (invalidCombi)) - + # Set dependent switches - + # (for future use) - + # Correcting line counts for source map - + global extraLines extraLines = [ # Make identifier __pragma__ known to static checker @@ -115,57 +115,57 @@ def logAndExit (message): # __ pragma__ ('') in JavaScript requires it to remain a function, as it was in the core # It can't be skipped, since it has to precede __pragma__ ('skip'), to make the checker accept that 'def __pragma__ (): pass', - + # Make __include__ known to the static checker - '__pragma__ (\'skip\')', - '__new__ = __include__ = 0', + '__pragma__ (\'skip\')', + '__new__ = __include__ = 0', '__pragma__ (\'noskip\')', '' ] if commandArgs.dcheck else [] global nrOfExtraLines nrOfExtraLines = max (len (extraLines) - 1, 0) # Last line only serves to force linefeed extraLines = '\n'.join (extraLines) - + commandArgs = CommandArgs () - + def create (path, binary = False): for i in range (10): try: os.makedirs (os.path.dirname (path), exist_ok = True) - + if binary: return open (path, 'wb') else: return open (path, 'w', encoding = 'utf-8') - + if i > 0: log (True, f'Created {path} at attempt {i + 1}') - + except: time.sleep (0.5) else: raise Error (f'Failed to create {path}') - + def tryRemove (filePath): try: os.remove (filePath) except: pass - + def formatted (*args): # args [0] is string, args [1 : ] are format params try: return str (args [0]) .format (*args [1 : ]) except IndexError: # Tuple index out of range in format tuple return ' '.join (args) - + logFileName = 'transcrypt.log' # ... Use envir.transpiler_name - + try: os.remove (logFileName) except: # Assume logfile doesn't yet exist pass - + def log (always, *args): if always or commandArgs.verbose: print (formatted (*args), end = '') @@ -173,15 +173,15 @@ def log (always, *args): if commandArgs.dlog: with open (logFileName, 'a') as logFile: logFile.write (formatted (*args)) - + except: # Assume too early, commandArgs not yet set pass - + program = None def setProgram (aProgram): global program program = aProgram - + class Error (Exception): # First error encountered counts, for all fields, because it's closest to the cause of trouble # One error at a time, just like Python, clear and simple @@ -191,14 +191,14 @@ def __init__ (self, lineNr = 0, message = ''): self.lineNr = lineNr - nrOfExtraLines self.message = message # The name of the module and of its import trail is known from the import stack, so no need to pass it as a parameter - - def set (self, lineNr = 0, message = ''): + + def set (self, lineNr = 0, message = ''): if not self.lineNr: self.lineNr = lineNr - nrOfExtraLines - + if not self.message: self.message = message - + def __str__ (self): result = 'Error while compiling (offending file last):' @@ -209,15 +209,15 @@ def __str__ (self): except: sourcePath = '' result += '\n\tFile \'{}\', line {}, at import of:'.format (sourcePath, importRecord [1]) - + # After that, report the module and line that caused the error # result += '\n\tFile \'{}\', line {}, namely:'.format (str (program.importStack [-1][0] .sourcePath), self.lineNr) result += '\n\tFile \'{}\', line {}, namely:'.format (str (program.importStack [-1][0] .name), self.lineNr) - + # And, lastly, report the error message result += '\n\t{}'.format (self.message) return result - + def enhanceException (exception, **kwargs): # If not all required info, such as a line number, is available at the location where the exception was raised, # it is enhanced later on by adding this missing info at the earliest occasion @@ -227,7 +227,7 @@ def enhanceException (exception, **kwargs): result = exception else: result = Error (**kwargs) - + if commandArgs.dextex: print (''' Exception of class {0} enhanced at: @@ -240,7 +240,7 @@ def enhanceException (exception, **kwargs): '''.format (exception.__class__, *inspect.stack () [1][1:-1], kwargs, result)) raise result from None - + def digestJavascript (code, symbols, mayStripComments, mayRemoveAnnotations, refuseIfAppearsMinified = False): ''' - Honor ifdefs @@ -250,24 +250,24 @@ def digestJavascript (code, symbols, mayStripComments, mayRemoveAnnotations, ref if refuseIfAppearsMinified and code [0] != '/': return None - + stripComments = False - + def stripSingleLineComments (line): pos = line.find ('//') return (line if pos < 0 else line [ : pos]) .rstrip () passStack = [] - def passable (targetLine): + def passable (targetLine): # Has to count, since comments may be inside ifdefs - + nonlocal stripComments - + def __pragma__ (name, *args): # Will be called below by executing the stripped line of source code nonlocal stripComments - + if name == 'stripcomments': stripComments = mayStripComments if name == 'ifdef': @@ -296,47 +296,47 @@ def __pragma__ (name, *args): # Will be called below by executing the stripped return False # Skip line anyhow, independent of passStack else: return all (passStack) # Skip line only if not in passing state according to passStack - + passableLines = [line for line in code.split ('\n') if passable (line)] - + if stripComments: passableLines = [commentlessLine for commentlessLine in [stripSingleLineComments (line) for line in passableLines] if commentlessLine] - + result = Any ( digestedCode = '\n'.join (passableLines), nrOfLines = len (passableLines), exportedNames = [], importedModuleNames = [] ) - + namesPattern = re.compile ('({.*})') pathPattern = re.compile ('([\'|\"].*[\'|\"])') wordPattern = re.compile (r'[\w/*$]+') # /S matches too much, e.g. { and } ??? Is this OK in all cases? Scrutinize code below... for line in passableLines: words = wordPattern.findall (line) - + if words: if mayRemoveAnnotations and words [0] == '/*': # If line starts with an annotation words = words [3 : ] # Remove the annotation before looking for export / import keywords - - if words: + + if words: if words [0] == 'export': # Deducing exported names from JavaScript is needed to facilitate * import by other modules - + if words [1] in {'var', 'function'}: # Export prefix: "export var ... or export function ..." - + result.exportedNames.append (words [2]) else: - # Transit export: "export {p, q, r, s};" - + # Transit export: "export {p, q, r, s};" + # Find exported names as "{p, q, r, s}" match = namesPattern.search (line) - + # Substitute to become "{'p', 'q', 'r', 's'}" and use that set to extend the exported names list if match: result.exportedNames.extend (eval (wordPattern.sub (lambda nameMatch: f'\'{nameMatch.group ()}\'', match.group (1)))) - + elif words [0] == 'import': # Deducing imported modules from JavaScript is needed to provide the right modules to JavaScript-only modules # They may have an explicit import list for unqualified access or an import * for qualified access @@ -344,12 +344,10 @@ def __pragma__ (name, *args): # Will be called below by executing the stripped # It can be a path without extension, allowing both .py and .js files as imported modules # # - Unqualified import: "import {p, q as Q, r, s as S} from ''" - # - Qualified import: "import * from ''" - + # - Qualified import: "import * from ''" + match = pathPattern.search (line) if match: result.importedModuleNames.append (eval (match.group (1)) [2:-3]) - - return result - + return result diff --git a/transcrypt/modules/weakref/__init__.js b/transcrypt/modules/weakref/__init__.js new file mode 100644 index 000000000..1a03862b1 --- /dev/null +++ b/transcrypt/modules/weakref/__init__.js @@ -0,0 +1,9 @@ +var __name__ = 'weakref'; + +export var ref = function (obj) { + var ref_ = new WeakRef(obj) + function deref() { + return ref_.deref() || null; + } + return deref; +}