Source: import.js

/**
 * @namespace Sk
 *
 */

// this is stored into sys specially, rather than created by sys
Sk.sysmodules = new Sk.builtin.dict([]);
Sk.realsyspath = undefined;
Sk.externalLibraryCache = {};

Sk.loadExternalLibraryInternal_ = function (path, inject) {
    var scriptElement;
    var request, result;

    if (path == null) {
        return void(0);
    }

    if (Sk.externalLibraryCache[path]) {
        return Sk.externalLibraryCache[path];
    }

    request = new XMLHttpRequest();
    request.open("GET", path, false);
    request.send();

    if (request.status !== 200) {
        return void(0);
    }

    result = request.responseText;

    if (inject) {
        scriptElement = document.createElement("script");
        scriptElement.type = "text/javascript";
        scriptElement.text = result;
        document.getElementsByTagName("head")[0].appendChild(scriptElement);
    }

    return result;
};

Sk.loadExternalLibrary = function (name) {
    var i;
    var externalLibraryInfo, path,  module,
        dependencies, dep, ext, extMatch, co;

    // check if the library has already been loaded and cached
    if (Sk.externalLibraryCache[name]) {
        return Sk.externalLibraryCache[name];
    }

    externalLibraryInfo = Sk.externalLibraries && Sk.externalLibraries[name];

    // if no external library info can be found, bail out
    if (!externalLibraryInfo) {
        return void(0);
    }

    // if the external library info is just a string, assume it is the path
    // otherwise dig into the info to find the path
    path = typeof externalLibraryInfo === "string" ?
        externalLibraryInfo :
        externalLibraryInfo.path;

    if (typeof path !== "string") {
        throw new Sk.builtin.ImportError("Invalid path specified for " + name);
    }

    // attempt to determine the type of the library (js or py)
    // which is either specified explicitly in the library info
    // or inferred from the file extension
    ext = externalLibraryInfo.type;
    if (!ext) {
        extMatch = path.match(/\.(js|py)$/);
        ext = extMatch && extMatch[1];
    }

    if (!ext) {
        throw new Sk.builtin.ImportError("Invalid file extension specified for " + name);
    }

    module = Sk.loadExternalLibraryInternal_(path, false);

    if (!module) {
        throw new Sk.builtin.ImportError("Failed to load remote module '" + name + "'");
    }

    // if the library has any js dependencies, load them in now
    dependencies = externalLibraryInfo.dependencies;
    if (dependencies && dependencies.length) {
        for (i = 0; i < dependencies.length; i++) {
            dep = Sk.loadExternalLibraryInternal_(dependencies[i], true);
            if (!dep) {
                throw new Sk.builtin.ImportError("Failed to load dependencies required for " + name);
            }
        }
    }

    if (ext === "js") {
        co = { funcname: "$builtinmodule", code: module };
    } else {
        co = Sk.compile(module, path, "exec", true);
    }

    Sk.externalLibraryCache[name] = co;

    return co;
};

/**
 * @param {string} name to look for
 * @param {string} ext extension to use (.py or .js)
 * @param {boolean=} failok will throw if not true
 * @param {boolean=} canSuspend can we suspend?
 */
Sk.importSearchPathForName = function (name, ext, failok, canSuspend) {
    var fn;
    var j;
    var fns = [];
    var nameAsPath = name.replace(/\./g, "/");
    var L = Sk.realsyspath;
    var it, i;

    for (it = L.tp$iter(), i = it.tp$iternext(); i !== undefined; i = it.tp$iternext()) {
        fns.push(i.v + "/" + nameAsPath + ext);                 // module
        fns.push(i.v + "/" + nameAsPath + "/__init__" + ext);   // package
    }

    j = 0;

    return (function tryNextPath() {
        var handleRead = function handleRead(s) {
            var ns;
            if (s instanceof Sk.misceval.Suspension) {
                ns = new Sk.misceval.Suspension(undefined, s);
                ns.resume = function() {
                    try {
                        return handleRead(s.resume());
                    } catch (e) {
                        j++;
                        return tryNextPath();
                    }
                };
                return ns;
            } else {
                return {filename: fns[j], code: s};
            }
        };
        var s;

        while (j < fns.length) {
            // Ew, this is the only way to check for existence.
            // Even worse, it reports non-existence through exceptions, so we have to
            // write a custom resume() function to catch downstream exceptions and interpret
            // them as "file not found, move on".
            try {
                s = Sk.read(fns[j]);

                if (!canSuspend) {
                    s = Sk.misceval.retryOptionalSuspensionOrThrow(s);
                }

                return handleRead(s);
            } catch (e) {
                j++;
            }
        }

        if (failok) {
            return null;
        } else {
            throw new Sk.builtin.ImportError("No module named " + name);
        }
    })();
};

/**
 * Complete any initialization of Python classes which relies on internal
 * dependencies.
 *
 * This includes making Python classes subclassable and ensuring that the
 * {@link Sk.builtin.object} magic methods are wrapped inside Python functions.
 *
 * @return {undefined}
 */
Sk.doOneTimeInitialization = function () {
    var proto, name, i, x, func;
    var builtins = [];

    // can't fill these out when making the type because tuple/dict aren't
    // defined yet.
    Sk.builtin.type.basesStr_ = new Sk.builtin.str("__bases__");
    Sk.builtin.type.mroStr_ = new Sk.builtin.str("__mro__");

    // Register a Python class with an internal dictionary, which allows it to
    // be subclassed
    var setUpClass = function (child) {
        var parent = child.tp$base;
        var bases = [];
        var base;

        for (base = parent; base !== undefined; base = base.tp$base) {
            bases.push(base);
        }

        child["$d"] = new Sk.builtin.dict([]);
        child["$d"].mp$ass_subscript(Sk.builtin.type.basesStr_, new Sk.builtin.tuple(bases));
        child["$d"].mp$ass_subscript(Sk.builtin.type.mroStr_, new Sk.builtin.tuple([child]));
    };

    for (x in Sk.builtin) {
        func = Sk.builtin[x];
        if ((func.prototype instanceof Sk.builtin.object ||
             func === Sk.builtin.object) && !func.sk$abstract) {
            setUpClass(func);
        }
    }

    // Wrap the inner Javascript code of Sk.builtin.object's Python methods inside
    // Sk.builtin.func, as that class was undefined when these functions were declared
    proto = Sk.builtin.object.prototype;

    for (i = 0; i < Sk.builtin.object.pythonFunctions.length; i++) {
        name = Sk.builtin.object.pythonFunctions[i];

        if (proto[name] instanceof Sk.builtin.func) {
            // If functions have already been initialized, do not wrap again.
            break;
        }

        proto[name] = new Sk.builtin.func(proto[name]);
    }
};

/**
 * currently only pull once from Sk.syspath. User might want to change
 * from js or from py.
 */
Sk.importSetUpPath = function () {
    var i;
    var paths;
    if (!Sk.realsyspath) {
        paths = [
            new Sk.builtin.str("src/builtin"),
            new Sk.builtin.str("src/lib"),
            new Sk.builtin.str(".")
        ];
        for (i = 0; i < Sk.syspath.length; ++i) {
            paths.push(new Sk.builtin.str(Sk.syspath[i]));
        }
        Sk.realsyspath = new Sk.builtin.list(paths);

        Sk.doOneTimeInitialization();

    }
};

if (COMPILED) {
    var js_beautify = function (x) {
        return x;
    };
}

/**
 * @param {string} name name of module to import
 * @param {boolean=} dumpJS whether to output the generated js code
 * @param {string=} modname what to call the module after it's imported if
 * it's to be renamed (i.e. __main__)
 * @param {string=} suppliedPyBody use as the body of the text for the module
 * rather than Sk.read'ing it.
 * @param {boolean=} canSuspend whether we may return a Suspension object
 */
Sk.importModuleInternal_ = function (name, dumpJS, modname, suppliedPyBody, canSuspend) {
    //dumpJS = true;
    var parentModule;
    var modlocs;
    var namestr;
    var withLineNumbers;
    var finalcode;
    var result;
    var filename, codeAndPath, co, isPy, googClosure, external;
    var module;
    var prev;
    var parentModName;
    var modNameSplit;
    var toReturn;
    Sk.importSetUpPath();

    // if no module name override, supplied, use default name
    if (modname === undefined) {
        modname = name;
    }

    toReturn = null;
    modNameSplit = modname.split(".");

    // if leaf is already in sys.modules, early out
    try {
        prev = Sk.sysmodules.mp$subscript(modname);
        // if we're a dotted module, return the top level, otherwise ourselves
        if (modNameSplit.length > 1) {
            return Sk.sysmodules.mp$subscript(modNameSplit[0]);
        } else {
            return prev;
        }
    } catch (x) {
        // not in sys.modules, continue
    }

    if (modNameSplit.length > 1) {
        // if we're a module inside a package (i.e. a.b.c), then we'll need to return the
        // top-level package ('a'). recurse upwards on our parent, importing
        // all parent packages. so, here we're importing 'a.b', which will in
        // turn import 'a', and then return 'a' eventually.
        parentModName = modNameSplit.slice(0, modNameSplit.length - 1).join(".");
        toReturn = Sk.importModuleInternal_(parentModName, dumpJS, undefined, undefined, canSuspend);

        // If this suspends, we suspend. When that suspension is done, we can just
        // repeat this whole function call
        if (toReturn instanceof Sk.misceval.Suspension) {
            // no canSuspend check here; we'll only get a Suspension out of importModuleInternal_ if
            // canSuspend is true anyway
            return (function waitForPreviousLoad(susp) {
                if (susp instanceof Sk.misceval.Suspension) {
                    // They're still going
                    return new Sk.misceval.Suspension(waitForPreviousLoad, susp);
                } else {
                    // They're done!
                    // Re-call ourselves, and this time "toReturn = Sk.importModuleInternal_(...)"
                    // will hit the cache and complete immediately.
                    return Sk.importModuleInternal_(name, dumpJS, modname, suppliedPyBody, canSuspend);
                }
            })(toReturn);
        }
    }

    // otherwise:
    // - create module object
    // - add module object to sys.modules
    // - compile source to (function(){...});
    // - run module and set the module locals returned to the module __dict__
    module = new Sk.builtin.module();
    Sk.sysmodules.mp$ass_subscript(name, module);

    if (suppliedPyBody) {
        filename = name + ".py";
        co = Sk.compile(suppliedPyBody, filename, "exec", canSuspend);
    } else {
        // If an onBeforeImport method is supplied, call it and if
        // the result is false or a string, prevent the import.
        // This allows for a user to conditionally prevent the usage
        // of certain libraries.
        if (Sk.onBeforeImport && typeof Sk.onBeforeImport === "function") {
            result = Sk.onBeforeImport(name);
            if (result === false) {
                throw new Sk.builtin.ImportError("Importing " + name + " is not allowed");
            } else if (typeof result === "string") {
                throw new Sk.builtin.ImportError(result);
            }
        }

        // check first for an externally loaded library
        external = Sk.loadExternalLibrary(name);
        if (external) {
            co = external;
        } else {
            // Try loading as a builtin (i.e. already in JS) module, then try .py files
            codeAndPath = Sk.importSearchPathForName(name, ".js", true, canSuspend);

            co = (function compileReadCode(codeAndPath) {
                if (codeAndPath instanceof Sk.misceval.Suspension) {
                    return new Sk.misceval.Suspension(compileReadCode, codeAndPath);
                } else if (!codeAndPath) {
                    goog.asserts.assert(!isPy, "Sk.importReadFileFromPath did not throw when loading Python file failed");
                    isPy = true;
                    return compileReadCode(Sk.importSearchPathForName(name, ".py", false, canSuspend));
                } else {
                    return isPy ? Sk.compile(codeAndPath.code, codeAndPath.filename, "exec", canSuspend)
                        : { funcname: "$builtinmodule", code: codeAndPath.code };
                }
            })(codeAndPath);
        }
    }

    return (function importCompiledCode(co) {

        if (co instanceof Sk.misceval.Suspension) {
            return canSuspend ? new Sk.misceval.Suspension(importCompiledCode, co) : Sk.misceval.retryOptionalSuspensionOrThrow(co);
        }

        module.$js = co.code; // todo; only in DEBUG?
        finalcode = co.code;
        if (Sk.dateSet == null || !Sk.dateSet) {
            finalcode = "Sk.execStart = Sk.lastYield = new Date();\n" + co.code;
            Sk.dateSet = true;
        }

        // if (!COMPILED)
        // {
        if (dumpJS) {
            withLineNumbers = function (code) {
                var j;
                var pad;
                var width;
                var i;
                var beaut = js_beautify(code);
                var lines = beaut.split("\n");
                for (i = 1; i <= lines.length; ++i) {
                    width = ("" + i).length;
                    pad = "";
                    for (j = width; j < 5; ++j) {
                        pad += " ";
                    }
                    lines[i - 1] = "/* " + pad + i + " */ " + lines[i - 1];
                }
                return lines.join("\n");
            };
            finalcode = withLineNumbers(finalcode);
            Sk.debugout(finalcode);
        }
        // }

        namestr = "new Sk.builtin.str('" + modname + "')";
        finalcode += "\n" + co.funcname + "(" + namestr + ");";

        modlocs = goog.global["eval"](finalcode);

        return (function finishLoading(modlocs) {

            if (modlocs instanceof Sk.misceval.Suspension) {

                if (canSuspend) {
                    return new Sk.misceval.Suspension(finishLoading, modlocs);
                } else {
                    modlocs = Sk.misceval.retryOptionalSuspensionOrThrow(modlocs, "Module \""+modname+"\" suspended or blocked during load, and it was loaded somewhere that does not permit this");
                }
            }

            // pass in __name__ so the module can set it (so that the code can access
            // it), but also set it after we're done so that builtins don't have to
            // remember to do it.
            if (!modlocs["__name__"]) {
                modlocs["__name__"] = new Sk.builtin.str(modname);
            }

            module["$d"] = modlocs;
            
            // doc string is None, when not present
            if (!modlocs["__doc__"]) {
                modlocs["__doc__"] = Sk.builtin.none.none$;
            }

            // If an onAfterImport method is defined on the global Sk
            // then call it now after a library has been successfully imported
            // and compiled.
            if (Sk.onAfterImport && typeof Sk.onAfterImport === "function") {
                try {
                    Sk.onAfterImport(name);
                } catch (e) {
                }
            }

            if (toReturn) {
                // if we were a dotted name, then we want to return the top-most
                // package. we store ourselves into our parent as an attribute
                parentModule = Sk.sysmodules.mp$subscript(parentModName);
                parentModule.tp$setattr(modNameSplit[modNameSplit.length - 1], module);
                //print("import returning parent module, modname", modname, "__name__", toReturn.tp$getattr("__name__").v);
                return toReturn;
            }

            //print("name", name, "modname", modname, "returning leaf");
            // otherwise we return the actual module that we just imported
            return module;
        })(modlocs);
    })(co);
};

/**
 * @param {string} name the module name
 * @param {boolean=} dumpJS print out the js code after compilation for debugging
 * @param {boolean=} canSuspend can this function suspend and return a Suspension object?
 */
Sk.importModule = function (name, dumpJS, canSuspend) {
    return Sk.importModuleInternal_(name, dumpJS, undefined, undefined, canSuspend);
};

Sk.importMain = function (name, dumpJS, canSuspend) {
    Sk.dateSet = false;
    Sk.filesLoaded = false;
    //	Added to reset imports
    Sk.sysmodules = new Sk.builtin.dict([]);
    Sk.realsyspath = undefined;

    Sk.resetCompiler();

    return Sk.importModuleInternal_(name, dumpJS, "__main__", undefined, canSuspend);
};

/**
 * **Run Python Code in Skulpt**
 *
 * When you want to hand Skulpt a string corresponding to a Python program this is the function.
 *
 * @param name {string}  File name to use for messages related to this run
 * @param dumpJS {boolean} print out the compiled javascript
 * @param body {string} Python Code
 * @param canSuspend {boolean}  Use Suspensions for async execution
 *
 */
Sk.importMainWithBody = function (name, dumpJS, body, canSuspend) {
    Sk.dateSet = false;
    Sk.filesLoaded = false;
    //	Added to reset imports
    Sk.sysmodules = new Sk.builtin.dict([]);
    Sk.realsyspath = undefined;

    Sk.resetCompiler();

    return Sk.importModuleInternal_(name, dumpJS, "__main__", body, canSuspend);
};

Sk.builtin.__import__ = function (name, globals, locals, fromlist) {
    // Save the Sk.globals variable importModuleInternal_ may replace it when it compiles
    // a Python language module.  for some reason, __name__ gets overwritten.
    var saveSk = Sk.globals;
    var ret = Sk.importModuleInternal_(name, undefined, undefined, undefined, true);

    return (function finalizeImport(ret) {
        if (ret instanceof Sk.misceval.Suspension) {
            return new Sk.misceval.Suspension(finalizeImport, ret);
        }

        if (saveSk !== Sk.globals) {
            Sk.globals = saveSk;
        }
        if (!fromlist || fromlist.length === 0) {
            return ret;
        }
        // if there's a fromlist we want to return the actual module, not the
        // toplevel namespace
        ret = Sk.sysmodules.mp$subscript(name);
        goog.asserts.assert(ret);
        return ret;
    })(ret);
};

Sk.importStar = function (module, loc, global) {
    // from the global scope, globals and locals can be the same.  So the loop below
    // could accidentally overwrite __name__, erasing __main__.
    var i;
    var nn = global["__name__"];
    var props = Object["getOwnPropertyNames"](module["$d"]);
    for (i in props) {
        loc[props[i]] = module["$d"][props[i]];
    }
    if (global["__name__"] !== nn) {
        global["__name__"] = nn;
    }
};

goog.exportSymbol("Sk.importMain", Sk.importMain);
goog.exportSymbol("Sk.importMainWithBody", Sk.importMainWithBody);
goog.exportSymbol("Sk.builtin.__import__", Sk.builtin.__import__);
goog.exportSymbol("Sk.importStar", Sk.importStar);