Imports and File Structure
Importing
Section titled “Importing”An import names either a single .sau file or a folder module (a directory with an init.sau — see Folder Modules), and the path is relative to the importing file’s directory, then the project’s src_dirs. The module comes first, so an editor knows what it can offer before you type a name.
-- one name, several, or one under another namefrom "entities/Player" import Playerfrom "utils/Math" import clamp, lerpfrom "data/PlayerRepository" import PlayerRepository as PlayerRepo
-- every exported namefrom "entities/Player" import *Or import the module as a whole. Everything it exports comes in, as with from shine import *, and is also reachable through its name:
-- `shine` exports Window, Graphics, Keyboard, Mouse and Timerimport shine -- all five, and shine.Window, shine.Graphics, …import shine as sh -- all five, and sh.Window, sh.Graphics, …import "uikit" -- everything uikit exports, and uikit.WindowGroup, …
local a: Window = Window("Demo")local b: shine.Window = shine.Window("Demo")An import brings in only what that module exports.
The qualified name is there for when two modules export the same one. A namespace is named after the last part of its path. A path whose last part is not a name, like "my-lib", needs as. shine.Window works anywhere a name or a type does, and a name the module does not export is reported as one: "`shine` has no export named `Windw`".
The path may be written with or without quotes. Unquoted, . separates folders — the two lines below mean exactly the same thing:
from "some/folder/module" import *from some.folder.module import *Apps and Libraries
Section titled “Apps and Libraries”A project is one of two shapes, declared by kind: in saule.config:
kind |
Has entry: |
saule run |
Purpose |
|---|---|---|---|
"app" (default) |
yes | runs it | a program |
"library" |
no | refuses, and says why | imported by other projects |
Scaffold either with saule init:
saule init myapp # an app, with src/main.sausaule init mylib --lib # a library, with src/init.sauA library’s src/init.sau is its public surface — whatever that file exports
is what importers see. Running one is a category error and reports as such
rather than failing on a missing entry file.
Importing from a Dependency
Section titled “Importing from a Dependency”A project listed in dependencies: is reachable by its name:. Naming the
dependency on its own imports the package itself:
from "json" import Json -- the `json` packagefrom "json/lexer" import Parser -- a specific module inside itA package exposes itself through an init.sau in one of its src_dirs —
the same folder module rule that applies anywhere
else, so there is one convention to learn rather than a special case for
dependencies. A package without one can still have its modules imported by
path, but its name alone won’t resolve.
json/├── saule.config name: "json"└── src/ └── init.sau ← what `import ... from "json"` getsFolder Modules (init.sau)
Section titled “Folder Modules (init.sau)”A folder becomes a single importable module by giving it an init.sau. Importing the folder imports that file, so whatever it exports is the folder’s public surface.
Re-exports
Section titled “Re-exports”export from passes names on: they are imported here and exported again, so a folder of files can be consumed as one unit.
-- some/folder/module/init.sau-- Paths are relative to this file.export from view import *export from button import Button, Toggle as SwitchConsumers then import the folder itself and get everything it passes on:
from some.folder.module import View, Buttonimport some.folder.module as ui
local view: View = View("Name")local s: ui.Switch = ui.Switch()Only what is written with export from is passed on. A plain from … import keeps its names private to the file, in an init.sau too, so a file’s imports stay an implementation detail. Any file can re-export, and a re-export of a re-export works. A namespace cannot be re-exported; name what to pass on instead.
Exporting
Section titled “Exporting”Add export before a class, interface, enum, function, or variable to make it accessible from other files:
export class Player -- ...end
export fn clamp(value: integer, min: integer, max: integer) -> integer if value < min then return min end if value > max then return max end
return valueend
export maxPlayers: integer = 8An export name: T = value is a module variable (see Variables) — a single value shared by every file that imports it, not a copy per importer.
A file without export is private to itself — even sibling files in the same folder can’t see its declarations. The only way to share code across files is to export it and import it explicitly.
Utility Modules
Section titled “Utility Modules”Not everything needs a class. Export standalone functions from a utility file and import them by name — a file is not a namespace, so the functions arrive as themselves rather than under the file’s name:
-- utils/Math.sau
export fn clamp(value: integer, min: integer, max: integer) -> integer if value < min then return min end if value > max then return max end return valueend
export fn lerp(a: float, b: float, t: float) -> float return a + (b - a) * tendfrom "utils/Math" import clamp, lerp
local clamped: integer = clamp(150, 0, 100) -- 100local smooth: float = lerp(0.0, 1.0, 0.5) -- 0.5Visibility Rules
Section titled “Visibility Rules”| Situation | Accessible from |
|---|---|
export class Foo |
anywhere that imports it |
class Foo without export |
only inside the same file |
local field or method |
only within the class |
static field or method |
via ClassName.x anywhere |
Circular Imports
Section titled “Circular Imports”Saule forbids circular imports at compile time:
ERROR: Circular import detected Player.sau → Inventory.sau → Player.sau
Hint: Extract shared types into a separate file