3.24. Modules
Modules provide infrastructure for code reuse, as well as mechanism to expose C++ functionality to Daslang. A module is a collection of types, constants, and functions. Modules can be native to Daslang, as well as built-in.
For an overview of how module, require, and options fit into the overall file layout,
see Program Structure.
To request a module, use the require keyword:
require math
require daslib/ast public
require daslib/ast_boost
The public modifier indicates that the included module is visible to everything that includes the current module.
Module names may contain / and . symbols.
The project is responsible for resolving module names into file names (see Project).
3.24.1. File-path requires
A require is treated as a literal file path when both conditions hold: it
starts with one of the three recognized prefixes (./, ../, %/) and
ends in .das or .das_project. Anything else continues to resolve as a
module name through the normal path. % expands to get_das_root().
require ./helpers.das // relative to the current file
require ../shared/utility.das // relative with .. segments
require %/daslib/random.das as rng // % expands to get_das_root()
Path resolution happens entirely on the filesystem — no module_get callback
is consulted.
The resolved file’s own module X declaration must match the file stem;
a mismatch is a hard compile error (WrongModuleName). When the file has no
module declaration, the stem is used as the module name. The same file
required through several paths therefore dedupes to a single Module instance
— require ./foo.das and require %/path/to/foo.das both produce the same
module foo.
File-path requires are available:
In default mode (no
.das_project).During
.das_projectbootstrap — the project file itself can userequire ./helpers.dasto pull in shared code before its ownmodule_getcallback is wired up. This solves a chicken-and-egg problem when splitting project logic across files.
In project mode, regular .das source files have their requires routed
through module_get; projects that want the same file-path convenience
must implement it inside their own resolver.
3.24.2. Optional requires
A require may be guarded by a module name written as ?guard directly
after the require keyword (gen2 syntax only — the guard form is not accepted
under the v1 options gen2 = false parser):
require ?pugixml pugixml/PUGIXML_boost // only if module `pugixml` is available
require ?pugixml pugixml/PUGIXML_boost public
The require loads its target only when the guard module is available (linked
into this build / registered). When the guard is absent, the require is
skipped silently — no dependency is added and no error is raised, even if the
target itself does not exist. When the guard is present, the require behaves
exactly like an ordinary require, so a missing target still produces the usual
missing prerequisite error (the guard never masks a genuine resolution
failure).
The guard module is named separately from the target on purpose: the condition
to load (pugixml — the C++ module that makes the path resolvable) is distinct
from what is loaded (pugixml/PUGIXML_boost, whose own module name is
PUGIXML_boost).
A plain-name guard is strict: the require proceeds only when the guard module
is actually registered (a linked C++ module) — never on the target’s resolvability
(module source directories exist in every checkout, regardless of what the build
linked). For targets inside a pure-das package (one with no C++ module to guard
on), use a path guard — a guard containing / — whose availability is the
guard’s own file resolving, i.e. the package is mounted. When the guard is
unavailable, the require is skipped silently. This enables the contributor pattern
in llvm/daslib/llvm_user_modules.das: a require ?<impl-path> <registration>
line pulls a das package’s registration glue exactly when that package is mounted.
typeinfo builtin_module_exists additionally sees shared das modules
(module X shared) promoted by the running script - but a tool that compiles the
same program in a nested context (lint, the language server, a test harness)
promotes nothing, and there the trait answers false for every das target. Guard the
use of a guarded das target with typeinfo module_exists(target) instead: it asks
whether the target is visible from the compiling module, which is exactly what the
guarded require decided, so the answer is the same on both rails. Two edges of that
rule: the name must be the module’s own name (an alias from require X as Y answers
false, like any name the library does not know), and inside a generic function the
compiling module is the one INSTANTIATING the generic, not the one that declared it - a
guard in a generic body reads the caller’s view.
Pair it with typeinfo builtin_module_exists to guard
code that uses the optional target’s symbols — static_if drops the untaken
branch before name resolution, so the symbols are referenced only when present:
require ?pugixml pugixml/PUGIXML_boost
static_if (typeinfo builtin_module_exists(pugixml)) {
// resolved only when pugixml is available
parse_xml("<root/>") $(doc, ok) { /* ... */ }
}
3.24.3. Module groups
A require may name a group in square brackets instead of a module (gen2
syntax only):
require [sql_provider]
require ?sqlite [sql_provider] public
A group is a name that modules register themselves under. A module’s .das_module
descriptor calls register_module_group("sql_provider", "sqlite/sqlite_provider", "sqlite")
- the third argument, optional, is the member’s guard, what require ?sqlite
sqlite/sqlite_provider would carry, so a member whose file requires a C++ module the
build may lack joins only where that module is - and a C++ module registers with
registerModuleGroupMember. require [group] is one ordinary require per
registered member, sorted by member path: a member resolves and fails exactly as the same
require spelled by hand would, public applies to every member, a guard on the group
drops the whole group the way it drops a single require, and a member’s own guard drops
that member. A group nothing registered under adds nothing.
The group turns the dependency around. A module that wants every installed provider would otherwise have to name each one with its own guard and gain a line per provider; with a group each provider names the group it joins, and the requirer names only the group. The module cache records the requires a parse took, so a member joining a group later re-parses the modules that require the group.
The requirer reaches its members without naming them through daslib/module_group:
require daslib/module_group
require [sql_provider]
def register_present_providers {
call_module_group("sql_provider", "register_provider")
}
call_module_group("group", "entry", args...) expands at compile time to one
member::entry(args...) call per registered member, in the group’s sorted order; a member
without the entry is a compile error naming the call, and an empty group expands to
nothing. A group is therefore two names: the group a member joins, and the entry every
member defines.
3.24.4. Native modules
A native module is a separate Daslang file, with an optional module name:
module custom // specifies module name
def public foo { // defines function in module
...
}
If not specified, the module name defaults to that of the file name.
Modules can be private or public:
module Foo private
module Foo public
The default publicity of functions, structures, and enumerations is that of the module (i.e. if the module is public and a function’s publicity is not specified, that function is public).
Module can be made visible to all modules in the project via the !inscope modifier:
module Foo !inscope
3.24.5. Builtin modules
Built-in modules are the way to expose C++ functionality to Daslang (see Builtin modules).
3.24.7. Module function visibility
When calling a function, the name of the module can be specified explicitly or implicitly:
let s1 = sin(0.0) // implicit, assumed math::sin
let s2 = math::sin(0.0) // explicit, always math::sin
If the function does not exist in that module, a compilation error will occur. If the function is private or not directly visible, a compilation error will occur. If multiple functions match an implicit function call, a compilation error will occur.
Module names _ and __ are reserved. Both refer to the module currently being compiled:
_ searches everything visible there (its own symbols plus everything it requires),
while __ searches that module’s own symbols only, ignoring anything imported.
This is particularly important for generic functions, which are always instanced as private functions in the module that calls them. Inside an instanced generic, the module currently being compiled is therefore the caller’s module — neither prefix pins a lookup to the module where the generic was written:
module b
[generic]
def from_b_get_fun_4() {
return _::fun_4() // `fun_4' as seen by the module which instanced this generic
}
[generic]
def from_b_get_fun_5() {
return __::fun_5() // `fun_5' declared directly in the instancing module
}
Specifying an empty prefix is the same as specifying no prefix.
Without the _ or __ module prefixes, overwritten functions would not be visible from generics.
That is why the := and delete operators are always replaced with _::clone or _::finalize calls.
See also
Program structure for module declaration and require statements,
Constants and enumerations for module-scoped constants,
Options for per-module options.