Modules
To help with organizing bigger projects Nujel includes a module system, this allows programmers to reuse short and simple names and also constrains interactions into a (hopefully) manageable area.
There are currently 2 main sources for modules:
- Built into the executable as part of the standard library
- Loaded from the filesystem
They mainly differ by using different types to specify their names, either keywords for built-ins or strings for external modules.
Warning
This subsystem is still considered unstable and may change drastically, so be careful when depending on specific behaviour.
1. Importing from built-in modules
Let's start with some simple example on how to import a function from a builtin stdlib module:
(import (red) :ansi) (println (red "Test")) ; This should be printed in red in most terminals (import (blue green) :ansi) ; You can also import multiple symbols at once (println (cat (blue "Te") (green "st"))) (import (yellow :as ansi-yellow) :ansi) ; Renaming is also possible (println (ansi-yellow "Test"))
Expected changes
The names of the builtin modules will very likely change, they will most likely get a single prefix so :ansi
becomes :core/ansi
2. Writing your own modules
You don't need to do anything special, every Nujel source file can be treated as a module. When it is imported for the first time all top-level code will be executed and all values marked for :export will be added to a map. The name is also determined by the path in the filesystem.
; test.nuj (defn test () :export ; The easiest way to export is to decorate your (defn) forms with the :export keyword, it will export the function with the name specified (println "Test has been called!")) (defn test-2 () :export-as test-zwei ; You can also use the :export-zwei decorator if you want different internal and external names (println "Test has been called!")) (def internal-value 123) (export exported-value internal-value) ; Exporting arbitrary values is best done with the (export) macro, it has pretty much the same form as (def)
3. Importing your own modules
To import modules from the filesystem, mainly ones you wrote yourself, you need to specify a relative path to the file, without the .nuj ending.
; test.nuj (defn test () :export (println "Test has been called!")) ; main.nuj (import (test) "./test") (test) ; => "Test has been called!"
4. Executing modules
One uncommon feature of the Nujel module system might be that there is no big distinction between executables and libraries, executable modules just have to export a (main) function which then might be called with the commandline arguments.
This enables the runtime to bundle multiple executable modules within the main executable, for example right now the REPL, Help screen and C Asset Code generator are all executable modules contained in the default stdlib.
Nujel defaults to executing the REPL module, but you can change that by for example specifying nujel -m :games/guess
which should start a very simple guess the number game.
Reasoning
I got inspired by Oberon here, and so far it has already become quite convenient to be able to bundle multiple "executables" with Nujel. It should become even more interesting once adding custom modules into a Nujel binary becomes easier and we enable for the default module to be changed easily. Then we could build specialized Nujel runtimes for specific programs with the end user not needing to know that it was built with Nujel.