Modules
A Pluvial program can be split across multiple .plu files. The module system is entirely
compile-time name resolution — there are no new bytecode operations. Each file is a module;
names are private unless exported.
Exporting names
Section titled “Exporting names”Prefix a declaration with export to make it visible to other files. Anything not exported
is module-private:
export def add(int a, int b) int { return a + b }export struct Point { int x int y }export auto VERSION = "1.0" # exported constant (literal initializer)
def helper() int { return 42 } # not exported — module-privateImporting — two forms
Section titled “Importing — two forms”Form 1: qualified access
Section titled “Form 1: qualified access”import "path" brings a module in under its name; you then reach its members with
Module.name:
import "math" # then math.add(1, 2)import "./utils/strings" as s # rename with `as`, then s.repeat("ab", 3)Module.name works for functions, structs, and in type positions.
Form 2: direct binding
Section titled “Form 2: direct binding”from "path" import ... binds names directly into the importing file’s scope:
from "math" import add, mul # add(1, 2) / mul(3, 4)from "math" import add as plus # plus(1, 2)from "shapes" import Point, origin # structs and functions togetherBoth forms can coexist in one file: after import "math", writing add(1, 2) uses a
from-binding while math.add(1, 2) uses qualified access.
Path resolution
Section titled “Path resolution”- The module key is the absolute path (via
realpath), so./math.plu,math.plu, and./../foo/math.pluresolve to the same module if they point to the same file. A module’s top-level code runs once, Python-style. - The module name is the last path component without
.plu(./utils/strings→strings), andascan rename it (form 1). - Relative paths are resolved against the importing file’s directory; absolute paths
(starting with
/) are used as-is. - Modules execute in topological order — dependencies run their top-level code before the modules that import them.
- Circular imports are detected (
a → b → a) and reported as an error.
Visibility and errors
Section titled “Visibility and errors”A Module.name referring to a private (non-exported) name reports 'X' is not exported from module 'Y'; an unknown name reports module 'Y' has no export 'X'.
Collisions between an import and a local definition are detected in source order: defining
add and then from "math" import add (or the reverse) is a compile error.
The std/ namespace
Section titled “The std/ namespace”The std/ prefix is reserved for the standard library:
import "std/strings"std/<name> is resolved by trying, in order: the PLUVIAL_STD_PATH environment variable
(if set), the system install path, and ./stdlib (relative to the working directory). Each
candidate has <name>.plu appended and is resolved with realpath; the first hit is
canonical. If none resolve, the import fails with a “standard library module ‘std/X’ is not
yet available” error.
Standard-library shorthand
Section titled “Standard-library shorthand”Standard-library imports have a dot-style shorthand equivalent to the quoted form:
| Quoted form | Shorthand |
|---|---|
import "std/math" | import std.math |
import "std/math" as m | import std.math as m |
from "std/io" import read_file | from std.io import read_file |
| — | import std.foo.bar → resolves to std/foo/bar.plu |
Rules:
- The leading identifier must be
std—import foo.bar(unquoted, non-std) is a parse error. - Multi-segment names rewrite simply:
std.foo.bar→"std/foo/bar". - Relative paths still require quotes (
import "./utils"). asworks the same way in both syntaxes.
See the Standard library overview for the available modules.