Note: This website is archived. For up-to-date information about D projects and development, please visit wiki.dlang.org.

Modules Library

This library forms the core of the MiniD module system. All of the semantics of imports and such are handled by the functions and data structures in here. Even the built-in import statement just calls modules.load. This makes the module system moldable and customizable to your specific needs.

The modules library is always available. It's kind of part of the base library, but its functions are all accessible through the modules namespace.

Members

  1. modules.path
  2. modules.loaded
  3. modules.customLoaders
  4. modules.loaders
  5. modules.load(name: string)
  6. modules.reload(name: string)
  7. modules.initModule(topLevel: function, name: string)
  8. modules.runMain(mod: namespace, vararg)
  9. modules.compile(source: string, name: string = ..)

modules.path

This is just a variable that holds a string. This string contains the paths that are used when searching for modules. The paths are specified using forward slashes to separate path components regardless of the underlying OS, and semicolons to separate paths.

By default, this variable holds the string ".", which just means "the current directory". If you changed it to something like ".;imports/current", when you tried to load a module "foo.bar", it would look for "./foo/bar.md" and "imports/current/foo/bar.md" in that order.

modules.loaded

This is a table that holds all currently-loaded modules. The keys are the module names as strings, and the values are the corresponding modules' namespaces. This is the table that modules.load will check in first before trying to look for a loader.

modules.customLoaders

This is a table which you are free to use. It maps from module names (strings) to functions or namespaces. This table is used by the customLoad step in modules.loaders; see it for more information.

modules.loaders

This is an important variable. This holds the array of module loaders, which are functions which take the name of a module that's being loaded, and return one of three things: nothing or null, to indicate that the next loader should be tried; a namespace, which is assumed to be the module's namespace; or a function, which is assumed to be the module's loader, which corresponds to the top-level module function of a MiniD script.

By default, the three following loaders are in this array, in the following order:

  • customLoad: This looks in the modules.customLoaders table for a loader function or namespace. If one exists, it just returns that; otherwise, returns null. You can use this behavior to set up custom loaders for your own modules: just put the loader in the modules.customLoaders table, and when it's imported, it'll have the loader function or namespace used for it.
  • checkTaken: This traverses the global namespace hierarchy to ensure that there are no naming conflicts with existing globals before loading the module. If there is a conflict (an existing global somewhere in the name chain that has a type other than namespace), an error will be thrown. If there are no conflicts, this returns nothing to indicate that loading should proceed to the next step.
  • loadFiles: This looks for files to load and loads them. As explained in modules.path, the paths in that variable will be tried one by one until a file is found or they are all exhausted. This looks for both script files (".md") and compiled modules (".mdm"). If it finds just a script file, it will compile it and return the resulting top-level function. If it finds just a compiled module, it will load it and return the top-level function. If it finds both in the same path, it will load whichever is newer. If it gets through all the paths and finds no files, it returns nothing.

If shared library support is enabled (note: not implemented in the current release), another loader is added to the end of the array, loadSharedLib. This will look for a shared library in the same way that loadFiles looks for source and binary files. If it finds the shared library, it will load it and return its initialization function (which is analogous to the top-level function of a module).

modules.load(name: string)

Loads a module of the given name and, if successful, returns that module's namespace. If the module is already loaded (i.e. it has an entry in the modules.loaded table), just returns the preexisting namespace.

This is the function that the built-in import statement calls. So in fact, "import foo.bar" and "modules.load("foo.bar")" do exactly the same thing, at least from the module-loading point of view. Import statements also give you some syntactic advantages with selective and renamed imports.

The process of loading a module goes something like this:

  1. It looks in modules.loaded to see if the module of the given name has already been imported. If it has (i.e. there is a namespace in that table), it returns whatever namespace is stored there.
  2. It iterates through the modules.loaders array, calling each successive function with the module's name. If one of the functions returns a namespace, it sees if that namespace is in the modules.loaded table and if not, adds it; then it returns that namespace. If one of the functions returns a function, it calls modules.initModule with the function and the name of the module as parameters and returns the result of that function.
  3. If it gets through the entire array without getting a function or namespace from any loaders, an error is thrown saying that the module could not be loaded.

modules.reload(name: string)

Very similar to modules.load, this function replaces step 1 of modules.load's process with a check to see if the module has already been loaded; if it has, it continues on with the process. If it hasn't been loaded, throws an error.

modules.initModule(topLevel: function, name: string)

Initialize a module with a top-level function and a name. The name is used to create the namespace for the module in the global namespace hierarchy if it doesn't already exist. If the module namespace does already exist, it is cleared before the initialization happens. Once the namespace has been created, the top-level function is called with the module namespace as the 'this' parameter and the module name as the first parameter. If the top-level function completes successfully, the module's namespace will be added to the modules.loaded table.

modules.runMain(mod: namespace, vararg)

This will look in the given namespace for a global named main. If one exists, and that global is a function, that function will be called with the module namespace as 'this' and any variadic arguments to runMain as the arguments. Otherwise, this function does nothing.

modules.compile(source: string, name: string = ..)

Compiles a module's source code into a function, which is returned, but does not call the function or import the module. source is the full source code of the module. name is the name to give the module, which would be something like "foo.bar.baz" for a normal module. It defaults to "<compiled by modules.compile>".

If you have the source for a module and want to import it, you can write something like modules.initModule(modules.compile(source, name), name).