From 0f27b29673ec35c412c3deb13120635c489e3b55 Mon Sep 17 00:00:00 2001 From: kochelmonster Date: Mon, 6 Dec 2021 18:57:31 +0100 Subject: [PATCH 01/27] enhanced relative import "from ..foo.bar import a" + support of type with 3 arguments. --- transcrypt/modules/org/transcrypt/compiler.py | 75 +++++++++++++------ 1 file changed, 51 insertions(+), 24 deletions(-) diff --git a/transcrypt/modules/org/transcrypt/compiler.py b/transcrypt/modules/org/transcrypt/compiler.py index 07e0393e..76e1f6c8 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): @@ -1475,7 +1475,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 @@ -2383,14 +2397,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: @@ -2915,6 +2929,19 @@ 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] + 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: @@ -2922,21 +2949,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 From bd45af31e6885556a3e654fe2fedd7803e695a2a Mon Sep 17 00:00:00 2001 From: kochelmonster Date: Mon, 6 Dec 2021 19:29:39 +0100 Subject: [PATCH 02/27] implemented __init__ call of meta class. --- transcrypt/modules/org/transcrypt/__core__.js | 50 ++++++++++--------- 1 file changed, 27 insertions(+), 23 deletions(-) diff --git a/transcrypt/modules/org/transcrypt/__core__.js b/transcrypt/modules/org/transcrypt/__core__.js index e5d36496..b46d166b 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 }); } }; @@ -118,7 +118,7 @@ export function __get__ (aThis, func, quotedFuncName) {// Param aThis is thing b value: function () { // So next time just call curry function that calls function var args = [] .slice.apply (arguments); return func.apply (null, [aThis] .concat (args)); - }, + }, writable: true, enumerable: true, configurable: true @@ -156,12 +156,12 @@ export function __getcm__ (aThis, func, quotedFuncName) { export function __getsm__ (aThis, func, quotedFuncName) { return func; }; - -// Mother of all metaclasses + +// Mother of all metaclasses export var py_metatype = { __name__: 'type', __bases__: [], - + // 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 +169,7 @@ 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 - + // 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,18 +182,18 @@ export var py_metatype = { continue; } Object.defineProperty (cls, attrib, descrip); - } + } 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; - + // Add own methods, properties and own static attributes to the created cls object for (var attrib in attribs) { var descrip = Object.getOwnPropertyDescriptor (attribs, attrib); @@ -204,8 +204,12 @@ export var py_metatype = { Object.defineProperty (cls, symbol, descrip); } + meta.__init__(cls, name, bases, attribs) // Return created cls object return cls; + }, + + __init__: function(cls, name, bases, attribs) { } }; py_metatype.__metaclass__ = py_metatype; @@ -213,18 +217,18 @@ 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', __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) { @@ -254,7 +258,7 @@ export var object = { // Return constructed instance return instance; - } + } }; // Class creator facade function, calls class creation worker @@ -262,7 +266,7 @@ export function __class__ (name, bases, attribs, meta) { // Parameter me if (meta === undefined) { meta = bases [0] .__metaclass__; } - + return meta.__new__ (meta, name, bases, attribs); }; From 6a08c92b835b45502f5731ebf924d2997664f997 Mon Sep 17 00:00:00 2001 From: kochelmonster Date: Mon, 6 Dec 2021 19:30:31 +0100 Subject: [PATCH 03/27] extended core with id, frozenset and getattr with default argument, --- .../modules/org/transcrypt/__builtin__.js | 101 ++++++++++++------ 1 file changed, 71 insertions(+), 30 deletions(-) diff --git a/transcrypt/modules/org/transcrypt/__builtin__.js b/transcrypt/modules/org/transcrypt/__builtin__.js index 97df31b3..eb44b7c8 100644 --- a/transcrypt/modules/org/transcrypt/__builtin__.js +++ b/transcrypt/modules/org/transcrypt/__builtin__.js @@ -51,7 +51,7 @@ export function __super__ (aClass, 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? @@ -123,7 +123,7 @@ export function __withblock__ (manager, statements) { statements (); manager.close (); } -}; +}; // Manipulating attributes by name @@ -140,8 +140,15 @@ 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 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) { @@ -233,28 +240,28 @@ export function __k__ (keyed, key) { // Check existence of dict key via retrie // If the target object is somewhat true, return it. Otherwise return false. // Try to follow Python conventions of truthyness -export function __t__ (target) { +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 ); @@ -312,7 +319,7 @@ Number.prototype.__format__ = function (fmt_spec) { var val = this.valueOf (); var is_negative = val < 0; val = Math.abs (val); - + function pad (s, width, fill, align) { if (fill == undefined) { fill = ' '; @@ -347,7 +354,7 @@ Number.prototype.__format__ = function (fmt_spec) { 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 ('.'); @@ -363,7 +370,7 @@ Number.prototype.__format__ = function (fmt_spec) { } 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); @@ -375,7 +382,7 @@ Number.prototype.__format__ = function (fmt_spec) { ftype = Number.isInteger (val) ? 'd' : 'g'; g_default = true; } - + var parts = fmt_spec.split ('.'); fmt_spec = parts [0]; precision = parts [1]; @@ -417,7 +424,7 @@ Number.prototype.__format__ = function (fmt_spec) { fill = fmt_spec [0]; } } - + if (isNaN (val)) { val = 'nan'; } @@ -535,7 +542,7 @@ Number.prototype.__format__ = function (fmt_spec) { return val; }; __pragma__ ('endif') - + export function bool (any) { // Always truly returns a bool, rather than something truthy or falsy return !!__t__ (any); }; @@ -562,6 +569,25 @@ export function py_typeof (anObject) { } }; +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) { @@ -590,7 +616,7 @@ export function issubclass (aClass, classinfo) { return false; } } - catch (exception) { // Using issubclass on primitives assumed rare + catch (exception) { // Using issubclass on primitives assumed rare return aClass == classinfo || classinfo == object; } }; @@ -669,12 +695,12 @@ export function ord (aChar) { // Maximum of n numbers export function max (nrOrSeq) { - return arguments.length == 1 ? Math.max (...nrOrSeq) : Math.max (...arguments); + return arguments.length == 1 ? Math.max (...nrOrSeq) : Math.max (...arguments); }; // Minimum of n numbers export function min (nrOrSeq) { - return arguments.length == 1 ? Math.min (...nrOrSeq) : Math.min (...arguments); + return arguments.length == 1 ? Math.min (...nrOrSeq) : Math.min (...arguments); }; // Absolute value @@ -732,7 +758,7 @@ export function format (value, fmt_spec) { } default: return str (value).__format__ (fmt_spec); - } + } } __pragma__ ('endif') @@ -1123,6 +1149,21 @@ export function set (iterable) { 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 @@ -1435,7 +1476,7 @@ String.prototype.__format__ = function (fmt_spec) { var align = '<'; var fill = ' '; var val = this.valueOf (); - + function pad (s, width, fill, align) { var len = s.length; var c = width - len; @@ -1521,7 +1562,7 @@ __pragma__ ('ifdef', '__sform__') key = key.substring (0, idx); } } - + if ((key == +key) && attr && args [key] !== undefined) { value = args [key][attr]; } @@ -1534,7 +1575,7 @@ __pragma__ ('ifdef', '__sform__') value = args [index][key][attr]; } else { - value = args [index][key]; + value = args [index][key]; } break; } @@ -1834,7 +1875,7 @@ export function dict (objectOrPairs) { // checks to make sure that these objects // get converted to dict objects instead of // leaving them as js objects. - + if (!isinstance (objectOrPairs, dict)) { val = dict (val); } @@ -1849,7 +1890,7 @@ export function dict (objectOrPairs) { // N.B. - this is a shallow copy per python std - so // it is assumed that children have already become // python objects at some point. - + var aKeys = objectOrPairs.py_keys (); for (var index = 0; index < aKeys.length; index++ ) { var key = aKeys [index]; @@ -1862,7 +1903,7 @@ export function dict (objectOrPairs) { // We have already covered Array so this indicates // that the passed object is not a js object - i.e. // it is an int or a string, which is invalid. - + throw ValueError ("Invalid type of object for dict creation", new Error ()); } } @@ -1944,7 +1985,7 @@ export function __pow__ (a, b) { export var pow = __pow__; // Make available as builin under usual name -__pragma__ ('ifndef', '__xtiny__') +__pragma__ ('ifndef', '__xtiny__') export function __neg__ (a) { if (typeof a == 'object' && '__neg__' in a) { @@ -2327,7 +2368,7 @@ export function __ior__ (a, b) { return a |= b; } }; - + export function __ixor__ (a, b) { if (typeof a == 'object' && '__ixor__' in a) { return a.__ixor__ (b); @@ -2368,7 +2409,7 @@ export function __getitem__ (container, key) { // Slic return container [container.length + key]; } else { - return container [key]; // Container must support bare JavaScript brackets + 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); From b13751d7f7cd78b8e91de8a9c75784c2b7d55786 Mon Sep 17 00:00:00 2001 From: kochelmonster Date: Mon, 6 Dec 2021 19:31:11 +0100 Subject: [PATCH 04/27] enhanced inspect.js with getmro. --- transcrypt/modules/inspect/__init__.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/transcrypt/modules/inspect/__init__.py b/transcrypt/modules/inspect/__init__.py index 2b3153b5..d0045f49 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) From 207d63d0349e5dcf8e7ede6a9c474e6258cf2666 Mon Sep 17 00:00:00 2001 From: kochelmonster Date: Tue, 7 Dec 2021 20:30:34 +0100 Subject: [PATCH 05/27] support of descriptor protocol. --- transcrypt/modules/org/transcrypt/__core__.js | 48 ++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/transcrypt/modules/org/transcrypt/__core__.js b/transcrypt/modules/org/transcrypt/__core__.js index b46d166b..f0561707 100644 --- a/transcrypt/modules/org/transcrypt/__core__.js +++ b/transcrypt/modules/org/transcrypt/__core__.js @@ -157,6 +157,35 @@ export function __getsm__ (aThis, func, quotedFuncName) { return func; }; + +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', @@ -170,6 +199,8 @@ export var py_metatype = { 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,6 +213,8 @@ 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); @@ -195,15 +228,21 @@ export var py_metatype = { cls.__bases__ = bases; // 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; @@ -250,7 +289,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 From b1b5a7bc035b40bdeccce2b4f3b295042fd37a24 Mon Sep 17 00:00:00 2001 From: kochelmonster Date: Tue, 7 Dec 2021 20:31:01 +0100 Subject: [PATCH 06/27] new weakref module. --- transcrypt/modules/weakref/__init__.js | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 transcrypt/modules/weakref/__init__.js diff --git a/transcrypt/modules/weakref/__init__.js b/transcrypt/modules/weakref/__init__.js new file mode 100644 index 00000000..dfe3a80b --- /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(); + } + return deref; +} From 2ebe55a62ae93ac9c7cd155654d405e3a9356f16 Mon Sep 17 00:00:00 2001 From: kochelmonster Date: Tue, 7 Dec 2021 20:31:13 +0100 Subject: [PATCH 07/27] new operator module. --- transcrypt/modules/operator/__init__.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 transcrypt/modules/operator/__init__.py diff --git a/transcrypt/modules/operator/__init__.py b/transcrypt/modules/operator/__init__.py new file mode 100644 index 00000000..6e960924 --- /dev/null +++ b/transcrypt/modules/operator/__init__.py @@ -0,0 +1,24 @@ + +def itemgetter(item, *items): + if not items: + def func(obj): + return obj[item] + else: + items = (item,) + items # __:opov + + def func(obj): + return [obj[i] for i in items] + 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 From d0ef2c9dd362a88b79a98c16d94a6fc9c1f93700 Mon Sep 17 00:00:00 2001 From: kochelmonster Date: Tue, 7 Dec 2021 20:31:27 +0100 Subject: [PATCH 08/27] new functools module. --- transcrypt/modules/functools/__init__.py | 42 ++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 transcrypt/modules/functools/__init__.py diff --git a/transcrypt/modules/functools/__init__.py b/transcrypt/modules/functools/__init__.py new file mode 100644 index 00000000..7a606dc2 --- /dev/null +++ b/transcrypt/modules/functools/__init__.py @@ -0,0 +1,42 @@ + +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 From 4b4ca3e502c75ec1b16effa4adc37336b20de5a9 Mon Sep 17 00:00:00 2001 From: kochelmonster Date: Tue, 7 Dec 2021 20:31:43 +0100 Subject: [PATCH 09/27] new collections module. --- transcrypt/modules/collections/__init__.py | 26 ++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 transcrypt/modules/collections/__init__.py diff --git a/transcrypt/modules/collections/__init__.py b/transcrypt/modules/collections/__init__.py new file mode 100644 index 00000000..57effca0 --- /dev/null +++ b/transcrypt/modules/collections/__init__.py @@ -0,0 +1,26 @@ +# __pragma__ ('skip') +def __pragma__(): 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); +} +''') + + +def deque(src=[]): + return __pragma__('js', '{}', "new deque_(src)") + + +__pragma__('js', '{}', ''' +deque_.prototype.__class__ = deque; +deque.__name__ = 'deque'; +deque.__bases__ = [object]; +''') From 928c0c0ed102592f7441ebc2aabedf55d535c38e Mon Sep 17 00:00:00 2001 From: kochelmonster Date: Tue, 7 Dec 2021 20:32:12 +0100 Subject: [PATCH 10/27] new bisect module. --- transcrypt/modules/bisect/__init__.py | 104 ++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 transcrypt/modules/bisect/__init__.py diff --git a/transcrypt/modules/bisect/__init__.py b/transcrypt/modules/bisect/__init__.py new file mode 100644 index 00000000..f1c37c4f --- /dev/null +++ b/transcrypt/modules/bisect/__init__.py @@ -0,0 +1,104 @@ +"""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. + 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 + 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. + 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 + return lo + + +# Create aliases +bisect = bisect_right +insort = insort_right From 3029a0bb8591fb1324de06e9618e150d46970cb4 Mon Sep 17 00:00:00 2001 From: kochelmonster Date: Tue, 21 Dec 2021 17:24:43 +0100 Subject: [PATCH 11/27] type(function) returns a function type. + string.lstrip, string.rstrip with arguments. --- .../modules/org/transcrypt/__builtin__.js | 26 ++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/transcrypt/modules/org/transcrypt/__builtin__.js b/transcrypt/modules/org/transcrypt/__builtin__.js index eb44b7c8..1a6e1504 100644 --- a/transcrypt/modules/org/transcrypt/__builtin__.js +++ b/transcrypt/modules/org/transcrypt/__builtin__.js @@ -549,6 +549,9 @@ export function bool (any) { // Always truly returns a bool, rather than som bool.__name__ = 'bool'; // So it can be used as a type with a name bool.__bases__ = [int]; + +export var aFunction = {__name__: "function"}; + 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 @@ -559,6 +562,9 @@ export function py_typeof (anObject) { return aType; } } + else if (aType == 'function') { + return aFunction; + } else { return ( // Odly, the braces are required here aType == 'boolean' ? bool : @@ -1664,7 +1670,14 @@ String.prototype.py_replace = function (old, aNew, maxreplace) { return this.split (old, maxreplace) .join (aNew); }; -String.prototype.lstrip = function () { +String.prototype.lstrip = function (chars) { + if (chars) { + var start = 0; + while (characters.indexOf (x[start]) >= 0) { + start += 1; + } + return this.slice (start); + } return this.replace (/^\s*/g, ''); }; @@ -1696,8 +1709,15 @@ String.prototype.rsplit = function (sep, maxsplit) { // Combination of genera } }; -String.prototype.rstrip = function () { - return this.replace (/\s*$/g, ''); +String.prototype.rstrip = function (chars) { + if (chars) { + var end = this.length - 1; + while (chars.indexOf (x[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 From 3addc2ef5fd78864a94fa95ab532b920dc1b52b9 Mon Sep 17 00:00:00 2001 From: kochelmonster Date: Tue, 21 Dec 2021 17:25:40 +0100 Subject: [PATCH 12/27] added RuntimeError. --- .../modules/org/transcrypt/__runtime__.py | 95 ++++++++++--------- 1 file changed, 50 insertions(+), 45 deletions(-) diff --git a/transcrypt/modules/org/transcrypt/__runtime__.py b/transcrypt/modules/org/transcrypt/__runtime__.py index 8b06f7ba..6a57a7f3 100644 --- a/transcrypt/modules/org/transcrypt/__runtime__.py +++ b/transcrypt/modules/org/transcrypt/__runtime__.py @@ -26,15 +26,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__)) @@ -42,23 +42,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: @@ -82,6 +82,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. @@ -99,7 +104,7 @@ class DeprecationWarning (Warning): class RuntimeWarning (Warning): pass - + #__pragma__ ('kwargs') def __sort__ (iterable, key = None, reverse = False): # Used by py_sort, can deal with kwargs @@ -107,16 +112,16 @@ def __sort__ (iterable, key = None, reverse = False): # Used by py iterable.sort (lambda a, b: 1 if key (a) > key (b) else -1) # JavaScript sort, case '==' is irrelevant for sorting else: iterable.sort () # JavaScript sort - + if reverse: iterable.reverse () - + def sorted (iterable, key = None, reverse = False): if type (iterable) == dict: - result = copy (iterable.keys ()) - else: + result = copy (iterable.keys ()) + else: result = copy (iterable) - + __sort__ (result, key, reverse) return result @@ -130,10 +135,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: @@ -148,32 +153,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) @@ -183,59 +188,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__: @@ -250,37 +255,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 From 61dbc7b63692473e09240a673c80fd4c63430476 Mon Sep 17 00:00:00 2001 From: kochelmonster Date: Tue, 21 Dec 2021 18:13:45 +0100 Subject: [PATCH 13/27] fixed bug in __getattr__ --- transcrypt/modules/org/transcrypt/__core__.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/transcrypt/modules/org/transcrypt/__core__.js b/transcrypt/modules/org/transcrypt/__core__.js index f0561707..4b8604a4 100644 --- a/transcrypt/modules/org/transcrypt/__core__.js +++ b/transcrypt/modules/org/transcrypt/__core__.js @@ -272,7 +272,7 @@ export var object = { 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 { From bd7b98019b14d57e6924ca866c5eaa1544a680a4 Mon Sep 17 00:00:00 2001 From: kochelmonster Date: Fri, 24 Dec 2021 00:56:06 +0100 Subject: [PATCH 14/27] fixed relative import of modules "from . import module" + tests for relative import. --- .../automated_tests/relimport/autotest.py | 7 +++ .../automated_tests/relimport/rimport.py | 6 +++ .../relimport/tpackage/__init__.py | 6 +++ .../relimport/tpackage/peer.py | 2 + transcrypt/modules/bisect/__init__.py | 5 +++ .../modules/org/transcrypt/__builtin__.js | 45 ++++++++++--------- transcrypt/modules/org/transcrypt/compiler.py | 5 ++- 7 files changed, 53 insertions(+), 23 deletions(-) create mode 100644 transcrypt/development/automated_tests/relimport/autotest.py create mode 100644 transcrypt/development/automated_tests/relimport/rimport.py create mode 100644 transcrypt/development/automated_tests/relimport/tpackage/__init__.py create mode 100644 transcrypt/development/automated_tests/relimport/tpackage/peer.py diff --git a/transcrypt/development/automated_tests/relimport/autotest.py b/transcrypt/development/automated_tests/relimport/autotest.py new file mode 100644 index 00000000..d4793ee8 --- /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 00000000..80b53663 --- /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 00000000..1dc36cb3 --- /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 00000000..b9bfa6f1 --- /dev/null +++ b/transcrypt/development/automated_tests/relimport/tpackage/peer.py @@ -0,0 +1,2 @@ +def func(): + pass diff --git a/transcrypt/modules/bisect/__init__.py b/transcrypt/modules/bisect/__init__.py index f1c37c4f..a662e582 100644 --- a/transcrypt/modules/bisect/__init__.py +++ b/transcrypt/modules/bisect/__init__.py @@ -33,6 +33,7 @@ def bisect_right(a, x, lo=0, hi=None, *, key=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 @@ -47,6 +48,7 @@ def bisect_right(a, x, lo=0, hi=None, *, key=None): hi = mid else: lo = mid + 1 + # __pragma__ ("noopov") return lo @@ -65,6 +67,7 @@ def insort_left(a, x, lo=0, hi=None, *, key=None): 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. @@ -82,6 +85,7 @@ def bisect_left(a, x, lo=0, hi=None, *, key=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 @@ -96,6 +100,7 @@ def bisect_left(a, x, lo=0, hi=None, *, key=None): lo = mid + 1 else: hi = mid + # __pragma__ ("noopov") return lo diff --git a/transcrypt/modules/org/transcrypt/__builtin__.js b/transcrypt/modules/org/transcrypt/__builtin__.js index 1a6e1504..17ef9841 100644 --- a/transcrypt/modules/org/transcrypt/__builtin__.js +++ b/transcrypt/modules/org/transcrypt/__builtin__.js @@ -550,7 +550,7 @@ bool.__name__ = 'bool'; // So it can be used as a type with a name bool.__bases__ = [int]; -export var aFunction = {__name__: "function"}; +export var FunctionType = {__name__: "function", __module__: "builtins", __bases__: [object]}; export function py_typeof (anObject) { var aType = typeof anObject; @@ -563,7 +563,7 @@ export function py_typeof (anObject) { } } else if (aType == 'function') { - return aFunction; + return FunctionType; } else { return ( // Odly, the braces are required here @@ -1673,7 +1673,7 @@ String.prototype.py_replace = function (old, aNew, maxreplace) { String.prototype.lstrip = function (chars) { if (chars) { var start = 0; - while (characters.indexOf (x[start]) >= 0) { + while (chars.indexOf (this[start]) >= 0) { start += 1; } return this.slice (start); @@ -1712,7 +1712,7 @@ String.prototype.rsplit = function (sep, maxsplit) { // Combination of genera String.prototype.rstrip = function (chars) { if (chars) { var end = this.length - 1; - while (chars.indexOf (x[end]) >= 0) { + while (chars.indexOf (this[end]) >= 0) { end -= 1; } return this.slice (0, end + 1); @@ -1754,7 +1754,10 @@ String.prototype.startswith = function (prefix) { return false; }; -String.prototype.strip = function () { +String.prototype.strip = function (chars) { + if (chars) { + return this.lstrip (chars).rstrip (chars); + } return this.trim (); }; @@ -2103,10 +2106,10 @@ export function __sub__ (a, b) { // Overloaded binary bitwise export function __lshift__ (a, b) { - if (typeof a == 'object' && '__lshift__' in a) { + if (a && typeof a == 'object' && '__lshift__' in a) { return a.__lshift__ (b); } - else if (typeof b == 'object' && '__rlshift__' in b) { + else if (b && typeof b == 'object' && '__rlshift__' in b) { return b.__rlshift__ (a); } else { @@ -2115,10 +2118,10 @@ export function __lshift__ (a, b) { }; export function __rshift__ (a, b) { - if (typeof a == 'object' && '__rshift__' in a) { + if (a && typeof a == 'object' && '__rshift__' in a) { return a.__rshift__ (b); } - else if (typeof b == 'object' && '__rrshift__' in b) { + else if (b && typeof b == 'object' && '__rrshift__' in b) { return b.__rrshift__ (a); } else { @@ -2127,10 +2130,10 @@ export function __rshift__ (a, b) { }; export function __or__ (a, b) { - if (typeof a == 'object' && '__or__' in a) { + if (a && typeof a == 'object' && '__or__' in a) { return a.__or__ (b); } - else if (typeof b == 'object' && '__ror__' in b) { + else if (b && typeof b == 'object' && '__ror__' in b) { return b.__ror__ (a); } else { @@ -2139,10 +2142,10 @@ export function __or__ (a, b) { }; export function __xor__ (a, b) { - if (typeof a == 'object' && '__xor__' in a) { + if (a && typeof a == 'object' && '__xor__' in a) { return a.__xor__ (b); } - else if (typeof b == 'object' && '__rxor__' in b) { + else if (b && typeof b == 'object' && '__rxor__' in b) { return b.__rxor__ (a); } else { @@ -2151,10 +2154,10 @@ export function __xor__ (a, b) { }; export function __and__ (a, b) { - if (typeof a == 'object' && '__and__' in a) { + if (a && typeof a == 'object' && '__and__' in a) { return a.__and__ (b); } - else if (typeof b == 'object' && '__rand__' in b) { + else if (b && typeof b == 'object' && '__rand__' in b) { return b.__rand__ (a); } else { @@ -2165,7 +2168,7 @@ export function __and__ (a, b) { // Overloaded binary compare export function __eq__ (a, b) { - if (typeof a == 'object' && '__eq__' in a) { + if (a && typeof a == 'object' && '__eq__' in a) { return a.__eq__ (b); } else { @@ -2174,7 +2177,7 @@ export function __eq__ (a, b) { }; export function __ne__ (a, b) { - if (typeof a == 'object' && '__ne__' in a) { + if (a && typeof a == 'object' && '__ne__' in a) { return a.__ne__ (b); } else { @@ -2183,7 +2186,7 @@ export function __ne__ (a, b) { }; export function __lt__ (a, b) { - if (typeof a == 'object' && '__lt__' in a) { + if (a && typeof a == 'object' && '__lt__' in a) { return a.__lt__ (b); } else { @@ -2192,7 +2195,7 @@ export function __lt__ (a, b) { }; export function __le__ (a, b) { - if (typeof a == 'object' && '__le__' in a) { + if (a && typeof a == 'object' && '__le__' in a) { return a.__le__ (b); } else { @@ -2201,7 +2204,7 @@ export function __le__ (a, b) { }; export function __gt__ (a, b) { - if (typeof a == 'object' && '__gt__' in a) { + if (a && typeof a == 'object' && '__gt__' in a) { return a.__gt__ (b); } else { @@ -2210,7 +2213,7 @@ export function __gt__ (a, b) { }; export function __ge__ (a, b) { - if (typeof a == 'object' && '__ge__' in a) { + if (a && typeof a == 'object' && '__ge__' in a) { return a.__ge__ (b); } else { diff --git a/transcrypt/modules/org/transcrypt/compiler.py b/transcrypt/modules/org/transcrypt/compiler.py index 76e1f6c8..d211c54a 100644 --- a/transcrypt/modules/org/transcrypt/compiler.py +++ b/transcrypt/modules/org/transcrypt/compiler.py @@ -2906,7 +2906,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 ''' @@ -2937,7 +2937,8 @@ def revisit_ImportFrom (self, importHoistMemo): # From ... import ... can import module_name.append("__init__") module_name = module_name[:-node.level] - module_name.append(node.module) + if node.module is not None: + module_name.append(node.module) module_name = ".".join(module_name) else: module_name = node.module From 4409aaa97a676cb7a3f39396b5d818e60fe1df64 Mon Sep 17 00:00:00 2001 From: kochelmonster Date: Tue, 28 Dec 2021 00:14:58 +0100 Subject: [PATCH 15/27] minor fixes. --- transcrypt/modules/collections/__init__.py | 7 +++++-- transcrypt/modules/org/transcrypt/__builtin__.js | 6 ++++++ transcrypt/modules/weakref/__init__.js | 2 +- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/transcrypt/modules/collections/__init__.py b/transcrypt/modules/collections/__init__.py index 57effca0..e979c9aa 100644 --- a/transcrypt/modules/collections/__init__.py +++ b/transcrypt/modules/collections/__init__.py @@ -1,5 +1,5 @@ # __pragma__ ('skip') -def __pragma__(): pass +def __pragma__(*args): pass # __pragma__ ('noskip') @@ -9,9 +9,12 @@ def __pragma__(): pass return this } deque_.prototype = Object.create(Array.prototype); -deque_.prototype.appendLeft = function(item) { +deque_.prototype.appendleft = function(item) { this.unshift(item); } +deque_.prototype.popleft = function() { + return this.shift(); +} ''') diff --git a/transcrypt/modules/org/transcrypt/__builtin__.js b/transcrypt/modules/org/transcrypt/__builtin__.js index 17ef9841..0ba7cef3 100644 --- a/transcrypt/modules/org/transcrypt/__builtin__.js +++ b/transcrypt/modules/org/transcrypt/__builtin__.js @@ -2171,6 +2171,9 @@ 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; } @@ -2180,6 +2183,9 @@ 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 } diff --git a/transcrypt/modules/weakref/__init__.js b/transcrypt/modules/weakref/__init__.js index dfe3a80b..1a03862b 100644 --- a/transcrypt/modules/weakref/__init__.js +++ b/transcrypt/modules/weakref/__init__.js @@ -3,7 +3,7 @@ var __name__ = 'weakref'; export var ref = function (obj) { var ref_ = new WeakRef(obj) function deref() { - return ref_.deref(); + return ref_.deref() || null; } return deref; } From d121e1dd929911e366e80ee2f72a519bfee6e287 Mon Sep 17 00:00:00 2001 From: kochelmonster Date: Wed, 29 Dec 2021 04:47:20 +0100 Subject: [PATCH 16/27] "$" joins for command line arguments do not work in linux -> allow multiple arguments. --- transcrypt/__main__.py | 74 ++++++------ transcrypt/modules/org/transcrypt/utils.py | 130 ++++++++++----------- 2 files changed, 102 insertions(+), 102 deletions(-) diff --git a/transcrypt/__main__.py b/transcrypt/__main__.py index a864b2d2..f4a04647 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/qquick/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/modules/org/transcrypt/utils.py b/transcrypt/modules/org/transcrypt/utils.py index 5625f067..d87b4461 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 From 653610c52a4e135b60e9fa282c0744dcdc6b5416 Mon Sep 17 00:00:00 2001 From: kochelmonster Date: Sat, 1 Jan 2022 13:04:42 +0100 Subject: [PATCH 17/27] fixes for strict mode. --- transcrypt/modules/org/transcrypt/__core__.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/transcrypt/modules/org/transcrypt/__core__.js b/transcrypt/modules/org/transcrypt/__core__.js index 4b8604a4..74901958 100644 --- a/transcrypt/modules/org/transcrypt/__core__.js +++ b/transcrypt/modules/org/transcrypt/__core__.js @@ -124,10 +124,12 @@ export function __get__ (aThis, func, quotedFuncName) {// Param aThis is thing b configurable: true }); } - return function () { // Return bound function, code duplication for efficiency if no memoizing + 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)); }; + bound.__org__ = func + return bound } else { // Class before the dot return func; // Return static method From 21efd0f1357cae685d9aa18e15f76018dd75fbbc Mon Sep 17 00:00:00 2001 From: kochelmonster Date: Sat, 1 Jan 2022 13:05:04 +0100 Subject: [PATCH 18/27] functools update_wrapper --- transcrypt/modules/functools/__init__.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/transcrypt/modules/functools/__init__.py b/transcrypt/modules/functools/__init__.py index 7a606dc2..a9e89d22 100644 --- a/transcrypt/modules/functools/__init__.py +++ b/transcrypt/modules/functools/__init__.py @@ -40,3 +40,7 @@ def wraps(wrapped): update_wrapper(). """ return wrapped # do nothing + + +def update_wrapper(wrapper, wrapped): + return wrapper From 0894db40a854648c5ecf2f333d1f0c5d3b885dab Mon Sep 17 00:00:00 2001 From: kochelmonster Date: Tue, 4 Jan 2022 00:57:42 +0100 Subject: [PATCH 19/27] fixed operator.itemgetter --- transcrypt/modules/operator/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/transcrypt/modules/operator/__init__.py b/transcrypt/modules/operator/__init__.py index 6e960924..8637aae2 100644 --- a/transcrypt/modules/operator/__init__.py +++ b/transcrypt/modules/operator/__init__.py @@ -1,13 +1,13 @@ def itemgetter(item, *items): - if not items: + if not len(items): def func(obj): - return obj[item] + return obj[item] # __:opov else: items = (item,) + items # __:opov def func(obj): - return [obj[i] for i in items] + return [obj[i] for i in items] # __:opov return func From e59fd63c8fdfa3f95f1638707a25dea9b3fca95c Mon Sep 17 00:00:00 2001 From: kochelmonster Date: Fri, 7 Jan 2022 13:28:59 +0100 Subject: [PATCH 20/27] __super__ now calls the correct mro. --- .../modules/org/transcrypt/__builtin__.js | 49 ++++++++++++++++--- transcrypt/modules/org/transcrypt/compiler.py | 6 ++- 2 files changed, 46 insertions(+), 9 deletions(-) diff --git a/transcrypt/modules/org/transcrypt/__builtin__.js b/transcrypt/modules/org/transcrypt/__builtin__.js index 0ba7cef3..565fe321 100644 --- a/transcrypt/modules/org/transcrypt/__builtin__.js +++ b/transcrypt/modules/org/transcrypt/__builtin__.js @@ -39,19 +39,54 @@ export function __globals__ (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]; + +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 fetch_base_from_mro(mro, methodName) { + while(mro.length) { + let base = mro.shift(); + if (methodName in base) + return base[methodName]; + } throw new Exception ('Superclass method not found'); // !!! Improve! } + +// Partial implementation of super () . () +export function __super__ (aClass, methodName, self) { + if (! aClass.__mro__) { + Object.defineProperty(aClass, "__mro__", {value: build_mro(aClass), configurable: false}); + } + + if (!self.__super_callchain__) { + self.__super_callchain__ = aClass.__mro__.slice(); + let method = fetch_base_from_mro(self.__super_callchain__, methodName) + return function() { + var result = method.apply(null, arguments); + delete self.__super_callchain__; + return result; + } + } + return fetch_base_from_mro(self.__super_callchain__, methodName); +} + // 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? diff --git a/transcrypt/modules/org/transcrypt/compiler.py b/transcrypt/modules/org/transcrypt/compiler.py index d211c54a..50af5e3f 100644 --- a/transcrypt/modules/org/transcrypt/compiler.py +++ b/transcrypt/modules/org/transcrypt/compiler.py @@ -1777,6 +1777,10 @@ def include (fileName): ), ast.Constant ( value = node.func.attr # + ), + ast.Name ( + id = 'self', + ctx = ast.Load ) ], keywords = [] @@ -2730,7 +2734,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) @@ -2763,7 +2766,6 @@ def pushPropertyAccessor(functionName): if docString: self.emit (' .__setdoc__ (\'{}\')', docString.replace ('\n', '\\n ')) - if decorate: self.emit (')' * decoratorsUsed) From bc1f3b4622aeedec55b628ead7ab23edce8ffc36 Mon Sep 17 00:00:00 2001 From: kochelmonster Date: Fri, 7 Jan 2022 13:30:18 +0100 Subject: [PATCH 21/27] enhanced tests for new __super__ --- .../transcrypt/builtin_super/__init__.py | 57 +++++++++++++++---- 1 file changed, 45 insertions(+), 12 deletions(-) diff --git a/transcrypt/development/automated_tests/transcrypt/builtin_super/__init__.py b/transcrypt/development/automated_tests/transcrypt/builtin_super/__init__.py index 40d81993..a07ae8ad 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,36 @@ 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) From f5000fd055a33080d823d7ec2ff1e5304cbc2433 Mon Sep 17 00:00:00 2001 From: kochelmonster Date: Fri, 7 Jan 2022 14:38:50 +0100 Subject: [PATCH 22/27] Activated the method memoizing + keep attrbutes methods. --- transcrypt/modules/org/transcrypt/__core__.js | 25 +++++++++++-------- transcrypt/modules/org/transcrypt/compiler.py | 10 +++++++- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/transcrypt/modules/org/transcrypt/__core__.js b/transcrypt/modules/org/transcrypt/__core__.js index 74901958..6f4350c9 100644 --- a/transcrypt/modules/org/transcrypt/__core__.js +++ b/transcrypt/modules/org/transcrypt/__core__.js @@ -113,23 +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 (bound, "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 }); } - 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)); - }; - bound.__org__ = func - return bound + return bound; } else { // Class before the dot return func; // Return static method diff --git a/transcrypt/modules/org/transcrypt/compiler.py b/transcrypt/modules/org/transcrypt/compiler.py index 50af5e3f..82e6b6a4 100644 --- a/transcrypt/modules/org/transcrypt/compiler.py +++ b/transcrypt/modules/org/transcrypt/compiler.py @@ -2638,6 +2638,7 @@ def pushPropertyAccessor(functionName): isClassMethod = False isStaticMethod = False isProperty = False + keep_name = False getter = '__get__' if node.decorator_list: @@ -2693,6 +2694,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 ') @@ -2725,7 +2727,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 ') @@ -2768,6 +2771,11 @@ def pushPropertyAccessor(functionName): 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: From c4a751de54eb58812130318ce649f1fa985dffca Mon Sep 17 00:00:00 2001 From: kochelmonster Date: Fri, 7 Jan 2022 14:38:50 +0100 Subject: [PATCH 23/27] Activate method memoizing + keep attributes of method. --- transcrypt/modules/org/transcrypt/__core__.js | 25 +++++++++++-------- transcrypt/modules/org/transcrypt/compiler.py | 10 +++++++- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/transcrypt/modules/org/transcrypt/__core__.js b/transcrypt/modules/org/transcrypt/__core__.js index 74901958..6f4350c9 100644 --- a/transcrypt/modules/org/transcrypt/__core__.js +++ b/transcrypt/modules/org/transcrypt/__core__.js @@ -113,23 +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 (bound, "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 }); } - 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)); - }; - bound.__org__ = func - return bound + return bound; } else { // Class before the dot return func; // Return static method diff --git a/transcrypt/modules/org/transcrypt/compiler.py b/transcrypt/modules/org/transcrypt/compiler.py index 50af5e3f..82e6b6a4 100644 --- a/transcrypt/modules/org/transcrypt/compiler.py +++ b/transcrypt/modules/org/transcrypt/compiler.py @@ -2638,6 +2638,7 @@ def pushPropertyAccessor(functionName): isClassMethod = False isStaticMethod = False isProperty = False + keep_name = False getter = '__get__' if node.decorator_list: @@ -2693,6 +2694,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 ') @@ -2725,7 +2727,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 ') @@ -2768,6 +2771,11 @@ def pushPropertyAccessor(functionName): 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: From 5c0c0657ffebfba01929e06605eda1bfbcfeacde Mon Sep 17 00:00:00 2001 From: kochelmonster Date: Sat, 8 Jan 2022 07:52:26 +0100 Subject: [PATCH 24/27] super handling: correct initialization of classes if __init__ is missing. Improved __super__. --- .../modules/org/transcrypt/__builtin__.js | 50 +++++++++++-------- transcrypt/modules/org/transcrypt/__core__.js | 6 +++ transcrypt/modules/org/transcrypt/compiler.py | 2 +- 3 files changed, 36 insertions(+), 22 deletions(-) diff --git a/transcrypt/modules/org/transcrypt/__builtin__.js b/transcrypt/modules/org/transcrypt/__builtin__.js index 565fe321..af6a45f5 100644 --- a/transcrypt/modules/org/transcrypt/__builtin__.js +++ b/transcrypt/modules/org/transcrypt/__builtin__.js @@ -59,32 +59,40 @@ function build_mro(aClass) { } -function fetch_base_from_mro(mro, methodName) { - while(mro.length) { - let base = mro.shift(); - if (methodName in base) - return base[methodName]; - } - throw new Exception ('Superclass method not found'); // !!! Improve! -} - // Partial implementation of super () . () export function __super__ (aClass, methodName, self) { - if (! aClass.__mro__) { - Object.defineProperty(aClass, "__mro__", {value: build_mro(aClass), configurable: false}); - } - - if (!self.__super_callchain__) { - self.__super_callchain__ = aClass.__mro__.slice(); - let method = fetch_base_from_mro(self.__super_callchain__, methodName) - return function() { - var result = method.apply(null, arguments); - delete self.__super_callchain__; - return result; + let context = this; + if (!context || !context.__next_super__) { + if (! aClass.__mro__) { + Object.defineProperty( + aClass, "__mro__", {value: build_mro(aClass), configurable: false}); + } + var mro = aClass.__mro__; + var index = 0; + var next_super = function() { + while(index Date: Sun, 9 Jan 2022 10:36:02 +0100 Subject: [PATCH 25/27] Another fix for super --- .../transcrypt/builtin_super/__init__.py | 23 +++++++++ .../modules/org/transcrypt/__builtin__.js | 47 ++++++++++++------- 2 files changed, 54 insertions(+), 16 deletions(-) diff --git a/transcrypt/development/automated_tests/transcrypt/builtin_super/__init__.py b/transcrypt/development/automated_tests/transcrypt/builtin_super/__init__.py index a07ae8ad..fd06958a 100644 --- a/transcrypt/development/automated_tests/transcrypt/builtin_super/__init__.py +++ b/transcrypt/development/automated_tests/transcrypt/builtin_super/__init__.py @@ -132,4 +132,27 @@ def __init__(self, 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 + + call_order = [] + mixed = Child(call_order) + autoTester.check (call_order) + + call_order = [] + mixed.plugin(call_order) autoTester.check (call_order) diff --git a/transcrypt/modules/org/transcrypt/__builtin__.js b/transcrypt/modules/org/transcrypt/__builtin__.js index af6a45f5..5b479516 100644 --- a/transcrypt/modules/org/transcrypt/__builtin__.js +++ b/transcrypt/modules/org/transcrypt/__builtin__.js @@ -39,7 +39,6 @@ export function __globals__ (anObject) { } */ - function build_mro(aClass) { let done = {} let result = []; @@ -58,30 +57,46 @@ function build_mro(aClass) { 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) + 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__) { - if (! aClass.__mro__) { - Object.defineProperty( - aClass, "__mro__", {value: build_mro(aClass), configurable: false}); - } - var mro = aClass.__mro__; - var index = 0; - var next_super = function() { - while(index Date: Sun, 9 Jan 2022 10:50:23 +0100 Subject: [PATCH 26/27] Improved metaclass handling. --- .../transcrypt/metaclasses/__init__.py | 35 ++++++++++++++----- transcrypt/modules/org/transcrypt/__core__.js | 14 +++++--- 2 files changed, 35 insertions(+), 14 deletions(-) diff --git a/transcrypt/development/automated_tests/transcrypt/metaclasses/__init__.py b/transcrypt/development/automated_tests/transcrypt/metaclasses/__init__.py index 0ff07d2a..cc359146 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/org/transcrypt/__core__.js b/transcrypt/modules/org/transcrypt/__core__.js index 9f4d20e4..f98235dd 100644 --- a/transcrypt/modules/org/transcrypt/__core__.js +++ b/transcrypt/modules/org/transcrypt/__core__.js @@ -119,7 +119,7 @@ export function __get__ (aThis, func, quotedFuncName) {// Param aThis is thing b }; if (quotedFuncName) { // Memoize call since fcall is on, by installing bound function in instance - Object.defineProperty (bound, "name", {value:quotedFuncName}) + Object.defineProperty (func, "name", {value:quotedFuncName}) // copy addintional attributes for(var n in func) { bound[n] = func[n]; @@ -234,14 +234,13 @@ export var py_metatype = { cls.__name__ = name.startsWith ('py_') ? name.slice (3) : name; cls.__bases__ = bases; - // Add own methods, properties and own static attributes to the created cls object - 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); @@ -323,9 +322,14 @@ export var object = { // 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); }; From ab150cdea872e945950d53f1d276ce76e42619ce Mon Sep 17 00:00:00 2001 From: kochelmonster Date: Tue, 18 Jan 2022 01:50:04 +0100 Subject: [PATCH 27/27] Another fix for super. --- .../transcrypt/builtin_super/__init__.py | 11 +++++++++++ transcrypt/modules/org/transcrypt/__builtin__.js | 2 +- transcrypt/modules/org/transcrypt/__core__.js | 3 +++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/transcrypt/development/automated_tests/transcrypt/builtin_super/__init__.py b/transcrypt/development/automated_tests/transcrypt/builtin_super/__init__.py index fd06958a..df07ee06 100644 --- a/transcrypt/development/automated_tests/transcrypt/builtin_super/__init__.py +++ b/transcrypt/development/automated_tests/transcrypt/builtin_super/__init__.py @@ -149,6 +149,9 @@ def plugin(self, call_order): class Child(Mixin, Base): pass + class GrandChild(Child): + pass + call_order = [] mixed = Child(call_order) autoTester.check (call_order) @@ -156,3 +159,11 @@ class Child(Mixin, Base): 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/modules/org/transcrypt/__builtin__.js b/transcrypt/modules/org/transcrypt/__builtin__.js index 5b479516..e3678120 100644 --- a/transcrypt/modules/org/transcrypt/__builtin__.js +++ b/transcrypt/modules/org/transcrypt/__builtin__.js @@ -70,7 +70,7 @@ function create_next_super(cls) { return function(methodName) { while(index < mro.length) { let base = mro[index++]; - if (methodName in base) + if (methodName in base.__class_attribs__) return base; } throw new Exception ('Superclass method not found'); // !!! Improve! diff --git a/transcrypt/modules/org/transcrypt/__core__.js b/transcrypt/modules/org/transcrypt/__core__.js index f98235dd..4a73b1cc 100644 --- a/transcrypt/modules/org/transcrypt/__core__.js +++ b/transcrypt/modules/org/transcrypt/__core__.js @@ -197,6 +197,7 @@ function _to_python_descriptor(instance, descript) { export var py_metatype = { __name__: 'type', __bases__: [], + __class_attribs__: {__init__: true}, // Overridable class creation worker __new__: function (meta, name, bases, attribs) { @@ -233,6 +234,7 @@ export var py_metatype = { 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() { @@ -271,6 +273,7 @@ export var object = { __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)