How Nelson's Module System Works and How to Create a Custom Module
Nelson's module system uses a C++ ModulesManager to register directories under modules/, exposing functionality through built-ins like addmodule and modulepath, while each module uses loader.m, startup.m, and finish.m scripts to initialize and clean up resources.
Nelson organizes its functionality into discrete units called modules. Each module lives in its own directory under the modules/ folder and registers with the runtime through a small amount of boilerplate code. Understanding how Nelson's module system works is essential for extending the language with custom functionality or packaging existing code for redistribution.
Core Architecture of Nelson's Module System
ModulesManager and C++ Registration Layer
At the heart of Nelson's module system lies the ModulesManager class implemented in modules/modules_manager/src/cpp/ModulesManager.cpp. This singleton maintains a vector of registered modules, storing metadata including modulename, modulepath, isprotected, version, and library path.
When you register a module, the manager inserts a new entry via RegisterModule:
modulesMap.emplace_back(modulename, path, L"", protectedModule, version);
The manager provides APIs such as UnregisterModule, IsExistingModuleName, IsProtectedModuleName, and GetModules to query and manipulate the module registry at runtime.
Built-in Functions for Module Management
The C++ layer exposes functionality to the Nelson language through several built-in functions:
addmodule(modules/modules_manager/builtin/cpp/addmoduleBuiltin.cpp): Inserts a new entry intoModulesManagerby callingRegisterModule.ismodule(modules/modules_manager/builtin/cpp/ismoduleBuiltin.cpp): Returns a logical value indicating whether a short name refers to a loaded or protected module.modulepath(modules/modules_manager/builtin/cpp/modulepathBuiltin.cpp): Returns absolute paths to module subdirectories. Accepts options:'etc','bin','root','builtin','functions', or'tests'.
Module Lifecycle and Initialization
loader.m: Registration
Every module contains a loader.m file in its root directory (e.g., modules/xml/loader.m). This script executes automatically at startup and calls addmodule to register the module with the runtime:
% modules/my_module/loader.m
addmodule([nelsonroot() '/modules/' 'my_module'], 'my_module');
This invocation stores the module metadata in the ModulesManager vector, making the module discoverable by ismodule and modulepath.
startup.m: Initialization
After registration, Nelson executes etc/startup.m within the module directory. This script typically performs two critical tasks:
- Register the gateway: Calls
addgatewayto map function calls to the module's compiled library. - Add functions to path: Calls
addpathwith the-frozenflag to include the module'sfunctions/directory.
Example from modules/xml/etc/startup.m:
% modules/my_module/etc/startup.m
% Register the built-in library gateway
addgateway(modulepath('my_module','builtin'), 'my_module');
% Add functions directory to path (frozen prevents overwriting)
addpath(modulepath('my_module','functions'), '-frozen');
finish.m: Cleanup
When Nelson shuts down or the module unloads, it executes etc/finish.m to reverse the initialization steps:
% modules/my_module/etc/finish.m
% Remove functions from path
rmpath(modulepath('my_module','functions'));
% Remove the gateway
removegateway(modulepath('my_module','builtin'));
How to Create a Custom Module in Nelson
Creating a custom module involves creating a directory structure and three mandatory scripts. Below is a complete, runnable example.
Step 1: Create the Directory Structure
Create the following hierarchy under modules/my_module:
modules/
└─ my_module/
├─ loader.m
├─ etc/
│ ├─ startup.m
│ └─ finish.m
├─ functions/
│ └─ hello.m
└─ tests/
└─ test_hello.m
Step 2: Write loader.m
This file registers your module with the ModulesManager:
% modules/my_module/loader.m
addmodule([nelsonroot() '/modules/' 'my_module'], 'my_module');
Step 3: Configure startup.m and finish.m
startup.m initializes the module:
% modules/my_module/etc/startup.m
% Register gateway for built-in functions (if you have a .dll/.so)
addgateway(modulepath('my_module','builtin'), 'my_module');
% Add functions directory to Nelson path
addpath(modulepath('my_module','functions'), '-frozen');
finish.m cleans up:
% modules/my_module/etc/finish.m
rmpath(modulepath('my_module','functions'));
removegateway(modulepath('my_module','builtin'));
Step 4: Add Functions and Tests
Create a simple function in functions/hello.m:
% modules/my_module/functions/hello.m
function out = hello(name)
%HELLO Simple greeting example
% out = hello('Nelson') returns "Hello, Nelson!"
if nargin == 0
name = 'World';
end
out = sprintf('Hello, %s!', name);
end
Optional test in tests/test_hello.m:
% modules/my_module/tests/test_hello.m
function test_hello
assert_isequal(hello('Nel'), 'Hello, Nel!');
end
Step 5: Load and Verify
You can manually load your module in a Nelson session:
>> addmodule([nelsonroot() '/modules/my_module'], 'my_module');
>> hello('Nelson')
ans = "Hello, Nelson!"
Alternatively, restart Nelson. The Modules Manager automatically executes every loader.m found under modules/ during startup.
Using Module Management Built-ins
Nelson provides several built-in functions to query and manage modules at runtime:
Check if a module is loaded:
>> ismodule('my_module')
ans = true
Retrieve specific paths within a module:
>> modulepath('my_module','etc')
ans = "/path/to/nelson/modules/my_module/etc"
>> modulepath('my_module','functions')
ans = "/path/to/nelson/modules/my_module/functions"
List all loaded modules with metadata:
>> mods = getmodules()
mods =
struct with fields:
name: {'my_module' 'xml' ...}
path: {"/path/to/..." ...}
protected: [0 1 ...]
version: {'1.0.0' ...}
Summary
- Nelson's module system is managed by a C++
ModulesManagerclass that maintains a registry of all loaded modules, storing metadata such as name, path, version, and protection status. - Registration occurs through
loader.mscripts that call theaddmodulebuilt-in, which invokesRegisterModuleinmodules/modules_manager/src/cpp/ModulesManager.cpp. - Initialization happens via
etc/startup.m, which typically callsaddgatewayto link compiled libraries andaddpathto expose.mfunctions. - Cleanup is handled by
etc/finish.musingrmpathandremovegateway. - Query functions like
ismodule,modulepath, andgetmodulesprovide runtime introspection into the module registry. - Custom modules require a specific directory layout (
loader.m,etc/startup.m,etc/finish.m,functions/) and can be auto-loaded at startup or manually registered viaaddmodule.
Frequently Asked Questions
What is the difference between addmodule and addpath in Nelson?
addmodule registers a directory as a formal module within the C++ ModulesManager, creating a persistent entry with metadata (name, path, version, protection status) that survives for the session and can be queried via ismodule and getmodules. In contrast, addpath simply adds a directory to Nelson's function search path for .m files; it does not create a module entry, support versioning, or trigger startup.m/finish.m lifecycle scripts.
How do I protect a custom module from being unloaded?
Protected status is determined by the isprotected flag stored in the ModulesManager vector (see modules/modules_manager/src/cpp/ModulesManager.cpp). When calling addmodule, you cannot directly set this flag from Nelson script—it is typically reserved for core modules distributed with Nelson itself. To prevent accidental modification of your custom module's functions, use the -frozen flag when calling addpath in your startup.m file: addpath(modulepath('my_module','functions'), '-frozen'). This prevents user paths from shadowing your module's functions.
Can I distribute a Nelson module as a standalone package?
Yes. A Nelson module is self-contained within its directory structure (loader.m, etc/, functions/, etc.). You can distribute this directory as a zip file or Git repository. Users simply place it under their modules/ directory, and Nelson's ModulesManager will automatically execute loader.m at startup, registering the module. For convenience, Nelson provides a skeleton generator via just/get_module_skeleton.just, which clones the official template repository to help you package your module correctly with all required boilerplate files.
Where does Nelson store module version information?
Version metadata is read from each module's module.json file via the readVersionFromJson function inside modules/modules_manager/src/cpp/ModulesManager.cpp. When addmodule registers a module, the ModulesManager stores the version string alongside the module name, path, and protection status in its internal vector. You can inspect the version field for all loaded modules by calling getmodules(), which returns a struct array containing the version field for each registered module.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →