15. Modules and packages
15.1 Files and modules#
One .luc file is one module; its path under the package's source root is its name: src/image/color.luc is image.color. A file's name is an identifier. A .lucb file in the same package is a Base module (§16). Module cycles are errors. The source root is the one the package's manifest names (§15.5); a program run without a manifest has its entry module's directory as the root and app as its package name.
15.2 Imports and visibility#
import image.color
import data.serialization as serial
from image.geometry import Point, Sizeimport keeps a module qualified, with an optional alias; from ... import brings the named public declarations in. Imports come first and are used; a name is imported once and never declared beside its import. Declarations, fields and methods are private to their module unless pub: a pub type with no pub member can be named and passed but not read or called from another module. Another module neither names nor reaches a private field in a memberwise construction (§9.1), calls an init that is not pub (§10.1), nor displays a struct with a private field that does not declare Display (§10.5). A public signature mentions only public types. A module's name is read only to reach a member, shapes.origin, shapes.Point; the closed protocols (§13.3) are visible in every module without an import.
The names math, text, console and time always name the standard modules (§6.8): import math and from math import abs never reach a module of the package. So a package with a module of one of those names at its source root may not import that name, and doing so is an error; another package's module of that name is reached through its package, from luce_std import math as fmath, under a name of its own.
15.3 Top level#
A module contains imports, then struct, enum, class, interface, type, func, test, and let constants. A top-level let is a constant expression: literals, arithmetic, tuples, enum cases, and constructors of structs of those. There is no module initialisation and no global var; mutable state lives in an object the program constructs.
15.4 Entry point#
pub func main(arguments: list[str]) -> int!:
return 015.5 Packages#
A package is a directory with a package.prisma naming the package, its source root, and its dependencies exactly. There is no build script and no network during a build. The manifest is the same document for the Base modules the package contains.
#prisma 4.0
def package "demo" {
str source = "src"
}The package's name is its identity (§12.3), spelled as an identifier (luce-ui is luce_ui); source is the root the modules are named from, src unless written. The nearest definition above the entry module is the program's.
def dependency "name" { str path = "..." } names a dependency's local directory, relative to this manifest. Every package keeps its sources directly under its source root, with no directory repeating its name: a module is named by its path there, src/widgets/button.lucb is widgets.button. Inside the package that path is the import; another package names the module behind the package's identifier and may import it only when the package lists it in str[] public = [...]: from luce_ui import ui, import luce_ui.ui and from luce_ui.ui import Button all reach src/ui.lucb of luce-ui when it is public, and construction is Button("pause"). The from form is the usual one; it may name a directory of modules, from luce_geocore.core import parallel, and alias what it brings, from luce_crypto import native as crypto. import luce_ui is an error: a package is not a module, except inside the package when it has a module of its name, src/luce_ui.luc, its starter module. The standard packages are packages too, from luce_std import math. Base owns module resolution; Luce invokes the compiler LUCE_BASE names, else luce-base beside the luce executable, as a release lays them out, else luce-base on the path. See the package import contract for complete examples and source-bundle relocation. Builds do not fetch dependencies.