|
| 1 | +__pragma__ ('stripcomments') |
| 2 | + |
| 3 | +/* Nested module-object creator, part of the nesting may already exist and have attributes |
| 4 | +
|
| 5 | +A Transcrypt applicaton consists of a main module and additional modules. |
| 6 | +Transcrypt modules constitute a unique, unambiguous tree by their dotted names, no matter which of the alternative module root paths they come from. |
| 7 | +The main module is represented by a main function with the name of the application. |
| 8 | +The locals of this function constitute the outer namespace of the Transcrypt application. |
| 9 | +References to all local variables of this function are also assigned to attributes of local variable __all__, using the variable names as an attribute names. |
| 10 | +The main function returns this local variable __all__ (that inside the function is also known by the name __world__) |
| 11 | +Normally this function result is assigned to window.<application name>. |
| 12 | +The function may than be exitted (unless its main line starts an ongoing activity), but the application namespace stays alive by the reference that window has to it. |
| 13 | +In case of the ongoing activity including the script is enough to start it, in other cases it has to be started explicitly by calling window.<application name>.<entrypoint function>. |
| 14 | +There may be multiple such entrypoint functions. |
| 15 | +
|
| 16 | +Additional modules are represented by objects rather than functions, nested into __world__ (so into __all__ of the main function). |
| 17 | +This nesting can be directly or indirectly, according to the dotted paths of the additional modules. |
| 18 | +One of the methods of the module object is the __init__ function, that's executed once at module initialisation time. |
| 19 | +
|
| 20 | +The additional modules also have an __all__ variable, an attribute rather than a local variable. |
| 21 | +However this __all__ object is passed to the __init__ function, so becomes a local variable there. |
| 22 | +Variables in additional modules first become locals to the __init__ function but references to all of them are assigned to __all__ under their same names. |
| 23 | +This resembles the cause of affairs in the main function. |
| 24 | +However __world__ only referes to the __all__ of the main module, not of any additional modules. |
| 25 | +Importing a module boils down to adding all members of its __all__ to the local namespace, directly or via dotted access, depending on the way of import. |
| 26 | +
|
| 27 | +In each local namespace of the module function (main function for main module, __init__ for additional modules) there's a variable __name__ holding the name of the module. |
| 28 | +Classes are created inside the static scope of a particular module, and at that (class creation) time their variable __module__ gets assigned a reference to __name__. |
| 29 | +This assignement is generated explicitly by the compiler, as the class creation function __new__ of the metaclass isn't in the static scope containing __name__. |
| 30 | +
|
| 31 | +In case of |
| 32 | + import a |
| 33 | + import a.b |
| 34 | +a will have been created at the moment that a.b is imported, |
| 35 | +so all a.b. is allowed to do is an extra attribute in a, namely a reference to b, |
| 36 | +not recreate a, since that would destroy attributes previously present in a |
| 37 | +
|
| 38 | +In case of |
| 39 | + import a.b |
| 40 | + import a |
| 41 | +a will have to be created at the moment that a.b is imported |
| 42 | +
|
| 43 | +In general in a chain |
| 44 | + import a.b.c.d.e |
| 45 | +a, a.b, a.b.c and a.b.c.d have to exist before e is created, since a.b.c.d should hold a reference to e. |
| 46 | +Since this applies recursively, if e.g. c is already created, we can be sure a and a.b. will also be already created. |
| 47 | +
|
| 48 | +So to be able to create e, we'll have to walk the chain a.b.c.d, starting with a. |
| 49 | +As soon as we encounter a module in the chain that isn't already there, we'll have to create the remainder (tail) of the chain. |
| 50 | +
|
| 51 | +e.g. |
| 52 | + import a.b.c.d.e |
| 53 | + import a.b.c |
| 54 | +
|
| 55 | +will generate |
| 56 | + var modules = {}; |
| 57 | + __nest__ (a, 'b.c.d.e', __init__ (__world__.a.b.c.d.e)); |
| 58 | + __nest__ (a, 'b.c', __init__ (__world__.a.b.c)); |
| 59 | + |
| 60 | +The task of the __nest__ function is to start at the head object and then walk to the chain of objects behind it (tail), |
| 61 | +creating the ones that do not exist already, and insert the necessary module reference attributes into them. |
| 62 | +*/ |
| 63 | + |
| 64 | +export function __nest__ (headObject, tailNames, value) { |
| 65 | + var current = headObject; |
| 66 | + // In some cases this will be <main function>.__all__, |
| 67 | + // which is the main module and is also known under the synonym <main function.__world__. |
| 68 | + // N.B. <main function> is the entry point of a Transcrypt application, |
| 69 | + // Carrying the same name as the application except the file name extension. |
| 70 | + |
| 71 | + if (tailNames != '') { // Split on empty string doesn't give empty list |
| 72 | + // Find the last already created object in tailNames |
| 73 | + var tailChain = tailNames.split ('.'); |
| 74 | + var firstNewIndex = tailChain.length; |
| 75 | + for (var index = 0; index < tailChain.length; index++) { |
| 76 | + if (!current.hasOwnProperty (tailChain [index])) { |
| 77 | + firstNewIndex = index; |
| 78 | + break; |
| 79 | + } |
| 80 | + current = current [tailChain [index]]; |
| 81 | + } |
| 82 | + |
| 83 | + // Create the rest of the objects, if any |
| 84 | + for (var index = firstNewIndex; index < tailChain.length; index++) { |
| 85 | + current [tailChain [index]] = {}; |
| 86 | + current = current [tailChain [index]]; |
| 87 | + } |
| 88 | + } |
| 89 | + |
| 90 | + // Insert its new properties, it may have been created earlier and have other attributes |
| 91 | + for (let attrib of Object.getOwnPropertyNames (value)) { |
| 92 | + Object.defineProperty (current, attrib, { |
| 93 | + get () {return value [attrib];}, |
| 94 | + enumerable: true, |
| 95 | + configurable: true |
| 96 | + }); |
| 97 | + } |
| 98 | +}; |
| 99 | + |
| 100 | +// Initialize module if not yet done and return its globals |
| 101 | +export function __init__ (module) { |
| 102 | + if (!module.__inited__) { |
| 103 | + module.__all__.__init__ (module.__all__); |
| 104 | + module.__inited__ = true; |
| 105 | + } |
| 106 | + return module.__all__; |
| 107 | +}; |
| 108 | + |
| 109 | +// Since we want to assign functions, a = b.f should make b.f produce a bound function |
| 110 | +// So __get__ should be called by a property rather then a function |
| 111 | +// Factory __get__ creates one of three curried functions for func |
| 112 | +// Which one is produced depends on what's to the left of the dot of the corresponding JavaScript property |
| 113 | +export function __get__ (aThis, func, quotedFuncName) { // Param aThis is thing before the dot, if it's there |
| 114 | + if (aThis) { |
| 115 | + if (aThis.hasOwnProperty ('__class__') || typeof aThis == 'string' || aThis instanceof String) { // Object before the dot |
| 116 | + if (quotedFuncName) { // Memoize call since fcall is on, by installing bound function in instance |
| 117 | + Object.defineProperty (aThis, quotedFuncName, { // Will override the non-own property, next time it will be called directly |
| 118 | + value: function () { // So next time just call curry function that calls function |
| 119 | + var args = [] .slice.apply (arguments); |
| 120 | + return func.apply (null, [aThis] .concat (args)); |
| 121 | + }, |
| 122 | + writable: true, |
| 123 | + enumerable: true, |
| 124 | + configurable: true |
| 125 | + }); |
| 126 | + } |
| 127 | + return function () { // Return bound function, code duplication for efficiency if no memoizing |
| 128 | + var args = [] .slice.apply (arguments); // So multilayer search prototype, apply __get__, call curry func that calls func |
| 129 | + |
| 130 | + // Note that if aThis has a proxy, self of func should be the proxy, since another __getattr__ or __setattr__ may be done on self |
| 131 | + return func.apply (null, [aThis.__proxy__ ? aThis.__proxy__ : aThis] .concat (args)); // Prepend aThis or its proxy before other actual params |
| 132 | + |
| 133 | + |
| 134 | + }; |
| 135 | + } |
| 136 | + else { // Class before the dot |
| 137 | + return func; // Return static method |
| 138 | + } |
| 139 | + } |
| 140 | + else { // Nothing before the dot |
| 141 | + return func; // Return free function |
| 142 | + } |
| 143 | +}; |
| 144 | + |
| 145 | +export function __getcm__ (aThis, func, quotedFuncName) { |
| 146 | + if (aThis.hasOwnProperty ('__class__')) { |
| 147 | + return function () { |
| 148 | + var args = [] .slice.apply (arguments); |
| 149 | + return func.apply (null, [aThis.__class__] .concat (args)); |
| 150 | + }; |
| 151 | + } |
| 152 | + else { |
| 153 | + return function () { |
| 154 | + var args = [] .slice.apply (arguments); |
| 155 | + return func.apply (null, [aThis] .concat (args)); |
| 156 | + }; |
| 157 | + } |
| 158 | +}; |
| 159 | + |
| 160 | +export function __getsm__ (aThis, func, quotedFuncName) { |
| 161 | + return func; |
| 162 | +}; |
| 163 | + |
| 164 | +// Mother of all metaclasses |
| 165 | +export var py_metatype = { |
| 166 | + __name__: 'type', |
| 167 | + __bases__: [], |
| 168 | + |
| 169 | + // Overridable class creation worker |
| 170 | + __new__: function (meta, name, bases, attribs) { |
| 171 | + // Create the class cls, a functor, which the class creator function will return |
| 172 | + var cls = function () { // If cls is called with arg0, arg1, etc, it calls its __new__ method with [arg0, arg1, etc] |
| 173 | + 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 |
| 174 | + return cls.__new__ (args); // Each Python class directly or indirectly derives from object, which has the __new__ method |
| 175 | + }; // If there are no bases in the Python source, the compiler generates [object] for this parameter |
| 176 | + |
| 177 | + // Copy all methods, including __new__, properties and static attributes from base classes to new cls object |
| 178 | + // The new class object will simply be the prototype of its instances |
| 179 | + // JavaScript prototypical single inheritance will do here, since any object has only one class |
| 180 | + // This has nothing to do with Python multiple inheritance, that is implemented explictly in the copy loop below |
| 181 | + for (var index = bases.length - 1; index >= 0; index--) { // Reversed order, since class vars of first base should win |
| 182 | + var base = bases [index]; |
| 183 | + for (var attrib in base) { |
| 184 | + var descrip = Object.getOwnPropertyDescriptor (base, attrib); |
| 185 | + if (descrip == null) { // Another library modified Function.prototype |
| 186 | + continue; |
| 187 | + } |
| 188 | + Object.defineProperty (cls, attrib, descrip); |
| 189 | + } |
| 190 | + for (let symbol of Object.getOwnPropertySymbols (base)) { |
| 191 | + let descrip = Object.getOwnPropertyDescriptor (base, symbol); |
| 192 | + Object.defineProperty (cls, symbol, descrip); |
| 193 | + } |
| 194 | + } |
| 195 | + |
| 196 | + // Add class specific attributes to the created cls object |
| 197 | + cls.__metaclass__ = meta; |
| 198 | + cls.__name__ = name.startsWith ('py_') ? name.slice (3) : name; |
| 199 | + cls.__bases__ = bases; |
| 200 | + |
| 201 | + // Add own methods, properties and own static attributes to the created cls object |
| 202 | + for (var attrib in attribs) { |
| 203 | + var descrip = Object.getOwnPropertyDescriptor (attribs, attrib); |
| 204 | + Object.defineProperty (cls, attrib, descrip); |
| 205 | + } |
| 206 | + for (let symbol of Object.getOwnPropertySymbols (attribs)) { |
| 207 | + let descrip = Object.getOwnPropertyDescriptor (attribs, symbol); |
| 208 | + Object.defineProperty (cls, symbol, descrip); |
| 209 | + } |
| 210 | + |
| 211 | + // Return created cls object |
| 212 | + return cls; |
| 213 | + } |
| 214 | +}; |
| 215 | +py_metatype.__metaclass__ = py_metatype; |
| 216 | + |
| 217 | +// Mother of all classes |
| 218 | +export var object = { |
| 219 | + __init__: function (self) {}, |
| 220 | + |
| 221 | + __metaclass__: py_metatype, // By default, all classes have metaclass type, since they derive from object |
| 222 | + __name__: 'object', |
| 223 | + __bases__: [], |
| 224 | + |
| 225 | + // Object creator function, is inherited by all classes (so could be global) |
| 226 | + __new__: function (args) { // Args are just the constructor args |
| 227 | + // In JavaScript the Python class is the prototype of the Python object |
| 228 | + // In this way methods and static attributes will be available both with a class and an object before the dot |
| 229 | + // The descriptor produced by __get__ will return the right method flavor |
| 230 | + var instance = Object.create (this, {__class__: {value: this, enumerable: true}}); |
| 231 | + |
| 232 | + if ('__getattr__' in this || '__setattr__' in this) { |
| 233 | + instance.__proxy__ = new Proxy (instance, { |
| 234 | + get: function (target, name) { |
| 235 | + let result = target [name]; |
| 236 | + if (result == undefined) { // Target doesn't have attribute named name |
| 237 | + return target.__getattr__ (name); |
| 238 | + } |
| 239 | + else { |
| 240 | + return result; // Will be bound to the target.__proxy__ to allow call chaining (as in issue #587) |
| 241 | + } |
| 242 | + }, |
| 243 | + set: function (target, name, value) { |
| 244 | + try { |
| 245 | + target.__setattr__ (name, value); |
| 246 | + } |
| 247 | + catch (exception) { // Target doesn't have a __setattr__ method |
| 248 | + target [name] = value; |
| 249 | + } |
| 250 | + return true; |
| 251 | + } |
| 252 | + }) |
| 253 | + instance == instance.__proxy__; |
| 254 | + } |
| 255 | + |
| 256 | + // Call constructor |
| 257 | + this.__init__.apply (null, [instance] .concat (args)); |
| 258 | + |
| 259 | + // Return constructed instance |
| 260 | + return instance; |
| 261 | + } |
| 262 | +}; |
| 263 | + |
| 264 | +// Class creator facade function, calls class creation worker |
| 265 | +export function __class__ (name, bases, attribs, meta) { // Parameter meta is optional |
| 266 | + if (meta === undefined) { |
| 267 | + meta = bases [0] .__metaclass__; |
| 268 | + } |
| 269 | + |
| 270 | + return meta.__new__ (meta, name, bases, attribs); |
| 271 | +}; |
| 272 | + |
| 273 | +// Define __pragma__ to preserve '<all>' and '</all>', since it's never generated as a function, must be done early, so here |
| 274 | +export function __pragma__ () {}; |
0 commit comments