Modules and Projects¶
ll-lang supports both single-file programs and multi-file projects. Every .lll file starts with a module header.
Module header¶
module Examples.Basics
The header must be the first non-comment line. The path is one or more uppercase-starting segments joined by dots.
In project mode the path must match the file location: a file at src/Foo/Bar.lll in project myapp must declare module Myapp.Foo.Bar.
Imports¶
module Examples.Modules
import Std.List
import Std.Maybe
firstDoubled(xs List[Int]) Maybe[Int] =
xs -> head -> map (\x. x * 2)
Each import declares a dependency on another module. Imports appear immediately after the module header, before any declarations.
In project mode, only explicitly imported modules contribute cross-module names during front-end checking.
The implicit prelude (~50 stdlib functions) is still always in scope without any import.
Exports¶
export { greet }
greet(name Str) Str = "hello " ++ name
Canonical surface: one module-level export list (export { ... }) placed after imports.
Current compatibility behavior:
- if export { ... } exists, imports only see listed names;
- if export list is absent, imports see all top-level names (compatibility fallback);
- legacy export decl still parses for compatibility, but does not by itself restrict visibility.
Project mode (lll.toml)¶
For multi-file programs, create a project manifest lll.toml at the project root:
[project]
name = "myapp"
version = "1.0.0" # optional
entry = "src/Main.lll" # optional, default src/Main.lll
[deps]
# Future: external ll-lang packages (Go-style module paths)
# "github.com/alice/json" = "v1.2.0"
[platform]
# Opt-in to platform-specific modules (Phase 8 PR4)
# use = ["Platform.IO", "Platform.Math"]
Directory layout¶
myapp/
├── lll.toml ← project manifest
├── src/
│ ├── Main.lll ← module Myapp.Main
│ └── Lib.lll ← module Myapp.Lib
└── bin/ ← generated: myapp.fs + myapp.fsproj
Module path convention¶
The compiler derives the expected module path from the file location:
| File | Expected header |
|---|---|
src/Main.lll |
module Myapp.Main |
src/Foo/Bar.lll |
module Myapp.Foo.Bar |
If the declared path doesn't match the expected path you get E020 ModulePathMismatch.
Import ordering¶
Files are compiled in topological order (dependencies first). Import cycles produce E024 ModuleCycle.
Build commands¶
# Scaffold a new project
lllc new myapp
# Build the project (reads lll.toml in current directory or any parent)
cd myapp && lllc build
# Build a project in a specific directory
lllc build ./myapp
# Single-file (no lll.toml needed — unchanged from before)
lllc build hello.lll
Output: bin/myapp.fs + bin/myapp.fsproj (ready for dotnet build).
Two-file example¶
src/Greet.lll:
module Hello.Greet
export { greet }
greet(name Str) Str = "Hello, " ++ name ++ "!"
src/Main.lll:
module Hello.Main
import Hello.Greet
main() = greet "World"
lll.toml:
[project]
name = "hello"
lllc build # → bin/hello.fs (both modules concatenated)
Error codes¶
| Code | Name | Meaning |
|---|---|---|
| E020 | ModulePathMismatch | module header does not match the file's location in src/ |
| E024 | ModuleCycle | Import graph contains a cycle |
| E025 | NoProjectForImport | Non-Std.* import used in single-file mode (no lll.toml) |
Known limitations¶
- No dep resolution yet. The
[deps]section is parsed and schema-frozen but packages are not fetched. Writing a dep that isn't vendored locally producesE022 UnresolvedDep(only when actually imported). [platform]target selection is live,Platform.*module APIs are still partial.
The manifest's[platform] use = [...]andPlatform.*.SDKaliases are wired into build/CLI flow, but manyimport Platform.*module surfaces are still being implemented incrementally.- Cross-module checking is import-scoped, but still file-local in inference. The compiler now only imports names from declared
importdependencies (no global sibling leakage), yet each file is still inferred independently and backend builds remain the final authority for some cross-file mismatches. - Visibility is export-list first. Add
export { ... }to enforce a stable module API; modules without export-list stay all-visible for compatibility.
Practical advice¶
- For learning/scripts: use a single
.lllfile with nolll.toml. The implicit prelude (listMap,strLen,printfn,readFile, …) is always available. - For
Maybe/Resultin single-file mode: declare them locally —Maybe A = Some A | None. - For multi-file projects: use
lllc new <name>to get the right directory structure, then add.lllfiles tosrc/.