# User Manual

This manual teaches `l` by building small native programs. It assumes that you already know a C-like language, so it
does not explain basic ideas such as functions, integer arithmetic, or lexical scope. Instead, it concentrates on the
parts of `l` that change how you design and read a program.

In particular, you will learn that:

- Function bodies and control-flow forms are expressions.
- Numeric types never promote implicitly, while a numeric literal takes its type from where it is written.
- Values and name-to-value bindings are immutable; assignment changes stored data, never a binding.
- A `let` only binds its evaluated expression, without allocating, and its type annotation is optional; annotated `Type`
  at module level, it names a type alias instead.
- Aggregate initializers allocate backing; constructor assignment updates existing backing.
- Tuples and slices are direct values, while arrays, structs, and enums are represented by backing addresses.
- Storage-accessing references are immutable by default; shallow `mut T` grants write capability at the written level.
- `volatile` independently makes permitted access through the written reference level use volatile LLVM operations.
- `static` declarations name external storage; definitions provide storage from compile-time data and relocations.
- Control-flow bodies are always blocks, and a completed control form is an ordinary operand.
- Loops can produce values through `else` and `break`.
- Enum variants are constructed through their enum, as `Event::Offline`, and switches are exhaustive.
- Function values do not capture their surroundings.
- An `impl` block places functions in a type's namespace, called as `T::f(...)`, with no receiver; `Self` names the
  target inside the block.
- Module-qualified paths are relative unless they start with `::`.
- `use name;` depends on the library `name` and reaches its interface as the namespace `name`.
- `extern` marks a declaration that links to a C symbol by its bare name.
- Raw memory is explicit but has no ownership, lifetime, bounds, or atomic guarantees.

The chapters use complete programs where a runnable example helps. Most examples return `0` when their checks succeed,
so `l run` can validate them without requiring an output library.

## Manual and specification

The manual demonstrates useful patterns and explains how the language's concepts fit together. It deliberately does not
catalog every token, grammar production, conversion, precedence level, target rule, or diagnostic phase.

The [Language Specification](./spec.md#the-l-programming-language-specification) is the normative, exhaustive
description. Follow a chapter's specification links when you need an exact rule or are investigating an edge case.

Continue with [Your First Program](#your-first-program).

## Your First Program

Create `monitor.l` with this complete program:

```l
// Return zero when the observed value matches the expected value.
pub fn main() i32 {
  let observed: i32 = 41;
  let expected: i32 = 42;
  expected - (observed + 1)
}
```

Check it, then run it:

```sh
l check monitor.l
l run monitor.l
```

`l check` validates the source without producing a binary. `l run` builds a host executable and starts it. The program
returns `0`, so a successful run has no output. To keep the executable, use:

```sh
l build -o monitor monitor.l
./monitor
```

### Read the body as an expression

An `l` function body contains one expression. A block lets that expression be a sequence:

```l
let observed: i32 = 41;
let expected: i32 = 42;
expected - (observed + 1)
```

Each semicolon evaluates and discards the expression before it. The final expression has no semicolon, so its value is
the block's value and therefore the result of `main`. Writing `return` is valid, but it is not needed here.

A trailing semicolon changes the block result to `void`. This is useful for functions that perform work without
returning a useful value, but it would not satisfy the `i32` result required by an exported root `main`.

### What the declaration says

```l
pub fn main() i32
```

This signature declares a function; the following body makes it a definition. `i32` is the fixed-width result required
for the entry point. `pub` exports the root symbol so the platform runtime can find `main`. The checker can validate a
file without `main`, but when an exported root `main` exists it must have the exact resolved type `fn () i32`.

Function definitions are private when `pub` is absent. `pub` controls both exported linkage and whether a definition can
be called through another module's API. Private functions remain callable within their own module. A bodyless
declaration names an external function and takes the same `pub`, which decides only whether other modules may call it:
its linkage is external either way, because another object supplies the entity.

### Source conventions

Source files are UTF-8, but identifiers use ASCII letters, digits, and underscores and are case-sensitive. `//` starts a
line comment. `///` is reserved for documentation comments, although generated documentation does not retain its text
yet. There are no block comments.

The manual uses `.l` for source files and two-space indentation, matching the compiler's canonical formatter output.

### Different from C

- The final expression returns from a function without `return`.
- A semicolon discards a value instead of merely ending every statement.
- `pub` controls exported linkage and cross-module function visibility; there is no header declaration in this program.
- An exported root `main` returns fixed-width `i32`, not the type selected by `@CType("int")` for a C `int`.

For exact source, token, and item rules, see [Source Text and Tokens](./spec.md#source-text-and-tokens) and
[Module Items](./spec.md#module-items).

Next, [Values and Types](#values-and-types) explains why the type distinction in the last bullet matters.

## Values and Types

`l` does not use C's integer promotions or usual arithmetic conversions. Values keep their exact types, and most
operators require both operands to have the same type. Choosing a type is therefore part of writing each expression, not
something left to an implicit promotion pass.

### Choose the type for the job

The numeric types fall into two practical groups, with a target-dependent macro for C interfaces:

| Use                                | Types                                                  | Why                              |
| ---------------------------------- | ------------------------------------------------------ | -------------------------------- |
| Data with a fixed representation   | `u8` through `u64`, `i8` through `i64`, `f32`, `f64`   | Width is stable across targets   |
| Sizes and address-sized arithmetic | `usize`, `isize`                                       | Width follows the target pointer |
| C declarations                     | `@CType("char")`, `@CType("int")`, and C pointer forms | Expansion follows the target ABI |

`@CType(...)` is an ordinary macro invocation used where a type is expected. Before checking, it expands to an ordinary
language type for the selected target. For example, `@CType("char")` becomes `u8`, `@CType("int")` becomes `isize` on
AVR and MSP430 or `i32` elsewhere, `@CType("char *")` becomes `mut *u8`, and `@CType("const char *")` becomes `*u8`.

`char` is a 32-bit character value. `bool` contains `true` or `false`, and `void` has the one value `null`. Strings are
not primitive values; [Slices and Text](#slices-and-text) develops their `[u8]` type.

The fifteen names are predefined identifiers rather than keywords: the compiler binds each of them in every module, so
`u8` resolves like an alias you could have written yourself. Nothing may be declared with one, so `fn u8()` and
`let bool: Type = i32;` are errors, and a name always means its type wherever it is written.

Types remain distinct when their representations happen to match. For example, `i32` and `u32` are both 32 bits, but
adding an `i32` value to a `u32` value is invalid because their signedness and types differ. An `@CType` result is not
distinct: when it expands to `i32`, it is exactly the ordinary `i32` type.

### Literals take their type from context

A numeric literal has no type of its own. The checker gives it the type its position expects, and the written value must
fit that type:

```l
let count: u8 = 255;
let offset: isize = -4;
let ratio: f64 = 6.022e23;
let mask: u16 = 0xFF_00;
```

Integers are written in decimal, `0x` hexadecimal, or `0b` binary, and one or more underscores may separate digits, so
`1__000` is valid. A floating-point literal needs a fraction or an exponent: `1.0` and `1e3` are floating-point, while
`1` is an integer and never becomes `f32` or `f64`. Nothing turns a number into a `char`; character literals contain one
Unicode scalar value, such as `'A'`, `'\n'`, or `'\u{1F980}'`.

Where nothing expects a numeric type, an integer is `i32` and a floating-point literal `f32`. A literal next to a typed
operand takes that operand's type, so if `small` is a `u8`, `small + 1` adds two `u8` values and `200 < small` compares
two. The rules that decide these types are listed under [Literal Typing](./spec.md#literal-typing).

### Convert deliberately

Apply the target type when two accepted numeric types need to meet:

```l
let small: u8 = 40;
let total: u16 = u16(small) + 2;
total
```

Conversions can narrow, wrap into another integer representation, or lose floating-point precision. `char` converts to
an integer, but an integer does not convert to `char`. `bool` and `void` do not participate in numeric conversions.

This reuses ordinary call syntax. The callee is resolved as a value first, so a parameter or local value shadows an
unqualified type of the same name. Only a type path can be a conversion target; name a pointer, qualified reference, or
other complex type with an alias before converting to it. A conversion takes exactly one argument.

This explicitness also applies to comparisons, shifts, and function arguments. If an API accepts `usize`, pass a `usize`
or convert to it instead of relying on the source type's width. Only literals adapt; a typed value never does.

### Aliases name a shape

A type alias improves vocabulary without creating a new nominal type. It is a module-level `let` whose annotation is the
keyword `Type`, saying that the name stands for a type rather than a value:

```l
let Reading: Type = i32;
let SampleCount: Type = usize;
```

`Reading` and `i32` are exactly the same type, and both are names: `i32` is predefined and `Reading` is yours, but the
grammar sees one shape. The alias helps a reader understand intent, but it does not prevent a different `i32` value from
being used as a reading. `Type` is accepted only in this position: it is not itself a type, so it cannot annotate a
binding inside a function body, and an alias cannot be declared there.

Here is a complete example:

```l
let Reading: Type = i32;

fn magnitude(value: Reading) Reading {
  if value < 0 { -value } else { value }
}

pub fn main() i32 {
  let raw: Reading = -42;
  magnitude(raw) - 42
}
```

The branches of `if` are both `Reading`, which resolves to `i32`. The final expression also remains `i32`, matching the
required entry-point result.

### Numeric operations are low-level

Integer addition, subtraction, multiplication, and negation wrap at the type's width. Division by zero, invalid shifts,
and some signed division cases have no defined result. Floating-point operations may produce infinities and NaNs. The
language does not insert runtime checks for these cases.

Each eager operator applies a fixed predefined associated function of its operand type. For example, `left + right`
applies `i32::add` when both operands are `i32`, and that function can also be called or passed as a function value
directly. This does not enable overloading: defining `add` on another type does not make `+` accept that type. `==` and
`!=` are primitive, while `&&` and `||` are shorthand for conditionals, so they keep short-circuiting.

The complete literal grammar and conversion behavior live in [Source Text and Tokens](./spec.md#source-text-and-tokens),
[Types](./spec.md#types), [Static Typing](./spec.md#static-typing),
[Predefined Functions](./spec.md#predefined-functions), and [Numeric Behavior](./spec.md#numeric-behavior).

Next, [Memory Model](#memory-model) separates immutable values from the storage that can hold their representation.

## Memory Model

Before using storage, separate the thing a program computes from the place that can hold its representation. In `l`,
values and bindings are immutable. Data in storage can change through a reference explicitly qualified `mut`.

### Six separate ideas

| Idea       | Meaning                                                                      | Example                                 |
| ---------- | ---------------------------------------------------------------------------- | --------------------------------------- |
| Value      | An immutable typed semantic entity, independent of registers or memory slots | `42`, an array reference                |
| Binding    | An immutable association from a name to a value                              | `let answer: i32 = 42`                  |
| Storage    | A region with an address, size, alignment, duration, and contents            | A struct's backing region               |
| Data       | The bytes currently stored in a region, not the source that initialized it   | The representation of `42`              |
| Reference  | An immutable value designating storage and providing access to it            | A struct reference or `*i32`            |
| Allocation | Creation of a storage region                                                 | Evaluating a runtime struct initializer |

The compiler might keep a value in a register, spill it, or eliminate its machine representation. Those choices do not
turn a binding into source-level storage. A load reads a stored value; a store replaces data. Neither changes an
existing value or rebinds a name.

### Evaluate first, bind second

```l
let answer: i32 = 42;
let same: i32 = answer;
```

Both names denote the same scalar value. Neither `let` allocates a slot, and neither name can be used as the destination
of a scalar store. The annotation is optional: `let same = answer;` binds the same `i32` value, since an unannotated
binding takes the type of its value.

For an array, struct, or enum, the value is instead a reference to backing storage:

```l
let Cell: Type = struct { value: i32 };

let cell: mut Cell = {value: 0};
let alias: Cell = cell;
let count: mut *i32 = cell.value;
let before: i32 = *count;
count = 42;
```

```text
cell ----+
         +----> Cell backing region
alias ---+      +----------------------+
count --------> | value: i32 data      |
                | first 0, then 42     |
                +----------------------+
before --------> 0 (unchanged value)
```

`{value: 0}` is the allocating expression. It constructs fresh backing and yields a `Cell` reference with the expected
outer `mut` capability. Only after that does `let cell` bind the result. `alias` designates the same region through an
immutable view, with no copy and no allocation. Because `cell` is mutable, `cell.value` projects a `mut *i32` slot
reference. `*count` loads its contents; `count = 42` stores through it. Assignment never rebinds `count`, `cell`, or
`alias`.

References alias when they designate the same or overlapping regions. A reference to a field overlaps its parent region,
so writes can be observed through another alias. A previously loaded scalar such as `before` does not change.

### Initialization versus updating

The same aggregate syntax has two runtime roles:

**Initializer = new storage. Constructor = existing storage. Assignment never rebinds a name.**

| Form                              | Effect                                                                                 |
| --------------------------------- | -------------------------------------------------------------------------------------- |
| `let cell: mut Cell = {value: 1}` | The initializer allocates and constructs fresh backing, then `let` binds its reference |
| `cell = {value: 2}`               | The constructor updates existing backing and yields `void`; `cell` keeps its identity  |
| `let other: Cell = cell`          | Binds an alias without copying                                                         |
| `cell = other`                    | Invalid aggregate assignment, not a copy or a rebinding                                |

An array or struct initializer requires an expected aggregate type. The annotation above supplies it; function
arguments, nested fields, return types, and contextual control-flow results can also supply it. The compiler does not
infer a struct shape from field syntax. `{}` is the `void` block expression rather than empty aggregate syntax, so an
empty struct initializer cannot be written. There is no separate scalar allocator syntax: use an existing aggregate
field or element when you need a writable local slot.

### Nested aggregates are separate regions

```l
let Inner: Type = struct { value: i32 };
let Outer: Type = struct { inner: mut Inner };

let outer: mut Outer = {inner: {value: 1}};
let old: Inner = outer.inner;
outer = {inner: {value: 2}};
```

```text
before: outer -> [inner reference] -> [value: 1]
                   old -----------------^

after:  outer -> [new reference]   -> [value: 2]
                   old -----------> [value: 1]
```

The outer region stays in place. Nested initializer syntax creates a new child and replaces the reference stored in the
outer field; it does not recursively overwrite the old child. In contrast, `outer.inner = {value: 3}` first loads the
current child reference, then updates that child's existing backing.

`Inner` and `*Inner` differ by one indirection:

```text
Inner   -> [Inner backing]
*Inner  -> [slot containing an Inner reference] -> [Inner backing]
```

If `slot: mut *Inner` already exists, `slot = {value: 4}` allocates fresh immutable `Inner` backing, stores its
reference in the mutable slot, and yields that `Inner` reference. It does not write an inline struct over the pointer
slot. If both levels must permit writes, use `slot: mut *mut Inner`; then `*slot = {value: 4}` loads the stored mutable
reference and updates the backing it designates.

### Mutability is a shallow capability

```l
let cell: mut Cell = {value: 1};
let view: Cell = cell;
cell.value = 2;
let observed: i32 = *view.value;
```

`observed` is `2`. The unqualified `view` cannot write, but it does not prevent the mutable alias `cell` from modifying
the region. There is no runtime flag or ABI difference. A mutable reference implicitly converts to its immutable form
only at the top level; the reverse conversion and changes at deeper levels are rejected.

The modifier is shallow. Given `Outer = struct { inner: mut Inner }` and `outer: Outer`, `outer.inner` loads exactly the
declared `mut Inner` reference, which permits writing the separately backed child even though the outer reference is
immutable. Declare the field `inner: Inner` to keep child access immutable. Direct fields, including raw pointers,
tuples, and slice descriptors, project `mut *T` from a mutable base and `*T` from an immutable base; loading either
yields exactly the declared `T`, without changing deeper capability.

`mut` is valid on raw pointers, fixed arrays, structs, enums, and slices. It is invalid on scalars, tuples, `void`, and
function-reference types: calling a function is not storing through it. Nested modifiers apply exactly where written, as
detailed in [Mutable Reference Types](./spec.md#mutable-reference-types).

### Storage duration is not lexical scope

Each runtime aggregate initializer allocates at its execution site. Repeated evaluations in a loop create distinct
regions, all lasting until the current function returns. Allocation is not hoisted into one reused entry-block slot, and
leaving a block or iteration does not reclaim it. Such loops grow the stack and can exhaust it. Construct reusable
backing outside a loop when fresh identity is not needed; remember that nested initializers still allocate fresh
children during constructor updates.

There is no ownership or lifetime analysis. Returning or retaining a reference to this stack storage does not extend its
duration: using it after the allocating function returns can access dangling storage.

### Static storage

Module-level `static` items are storage declarations or definitions, not `let` bindings:

```l
let Point: Type = struct { x: i32, y: i32 };

static supplied: *u32;
static counter: mut *u32 = 0;
pub static origin: Point = {x: 0, y: 0,};
```

`supplied` is a declaration: it introduces a name and type, and another linked object must provide the storage.
`counter` and `origin` are definitions: they also provide storage and its initial contents. Every definition declares an
entity, but a declaration need not define it. The same distinction applies to a function signature ending in `;` versus
one followed by a body.

`counter` has type `mut *u32`, while its designated slot contains a `u32`. Its static initializer `0` describes that
slot's initial contents, a `u32`, not a pointer value. `static counter: u32 = 0;` would be invalid because the
annotation must designate storage. A struct, enum, or fixed array already designates its backing, so `origin` does not
need an extra pointer level. A direct slice or function reference does not qualify as a static-storage annotation; a
pointer to a slot containing either value does.

The compiler emits bytes and relocations from static initializers. It does not execute runtime initializers,
constructors, or startup allocation. Nested aggregate syntax still describes separate backing objects with references
stored in their parent slots. Those emitted bytes are data; the source syntax describing them is not data.

Static definitions last for the module's runtime lifetime. Omitting an initializer never implies zero-filled storage.
`pub` controls visibility independently: `counter` is private and mutable, while `origin` is public and immutable
through its `l` type. An unqualified definition is emitted in constant storage where supported; a `mut` definition is
emitted in writable storage. An immutable external declaration only restricts access through its `l` type and does not
assert that the externally supplied storage is physically constant.

[Targets and C Interoperation](#targets-and-c-interoperation) develops linkage and the supported static initializer
forms.

### Migration note

There are no `var` or `const` declarations and no `const` language type qualifier; those two words are ordinary
identifiers. The removed word `readonly` is also an ordinary identifier, because unqualified storage references are now
immutable. Replace scalar storage declarations with a typed aggregate initializer and a projected slot, as in the `Cell`
example, and write `mut` at each reference level that must permit stores. A type alias is `let Name: Type = T;`; the
reserved `type` keyword no longer introduces one.

The normative decomposition is [Memory, Bindings, and Storage](./spec.md#memory-bindings-and-storage). Continue with
[Functions and Expressions](#functions-and-expressions), then [Storage and Loops](#storage-and-loops).

## Functions and Expressions

Functions in `l` look familiar, but their bodies are expression trees. Blocks, bindings, conditions, assignments, and
later even loops and switches can contribute values to a larger expression.

### Define the result once

Parameter types are always written. The result type follows the parameter list:

```l
fn maximum(left: i32, right: i32) i32 {
  if left > right { left } else { right }
}
```

The selected branch becomes the value of `if`, the block, and the function. Both yielding branches must have compatible
types. Only the selected branch runs.

Omitting the result type means `void`:

```l
fn perform_notification();

fn notify() {
  perform_notification();
}
```

A trailing semicolon supplies the `void` value `null` for this body.

### Bind intermediate values

`let` evaluates its expression once, then binds the resulting value directly without allocating storage:

```l
let distance: i32 = right - left;
let doubled: i32 = distance * 2;
doubled
```

An annotation is optional. Without one, the binding takes the type of its value, so `let doubled = distance * 2;` is
also an `i32`, and `let count = 0;` is an `i32` because nothing asks the literal for another type. Write the annotation
when the value alone does not say what you mean, such as `let count: u8 = 0;`. A name is not visible in its own
initializing expression, and an inner binding may shadow an outer one. The new name remains available through the rest
of its block.

When a sequence itself must be used as a value, put it in a block:

```l
let adjusted: i32 = {
  let delta: i32 = 2;
  value + delta
};
adjusted
```

`{}` is the empty block, which yields the `void` value `null`, and it is accepted wherever an expression is. It is never
aggregate syntax, so there is no empty struct initializer.

### Leave early when it clarifies the path

`return value` exits immediately. Because it never reaches the surrounding expression, it can appear in a branch whose
other path produces a value:

```l
fn clamp(value: i32, low: i32, high: i32) i32 {
  if value < low { return low };
  if value > high { return high };
  value
}
```

A bare `return` returns `null` from a `void` function. Source after a return still has to parse and type-check.

`return`, `break`, and `continue` have the internal bottom type `Never`: they have no normal completion and fit any
expected value position. Strict expressions propagate it. If `consume` returns `bool`, `consume(return 1)` is `Never`,
because the argument exits before the call can run. The same applies to eager operators, projections, tuples,
initializers, sequences, and binding initializers. Source after the jump is still checked.

Short-circuiting remains visible in the type: a `Never` left side makes `&&` or `||` `Never`, while a `Never` right side
still leaves the path that skips it. This is static propagation, not full control-flow or constant-value analysis.

If the callee itself is `Never`, the call is `Never` and no signature gives its arguments parameter types: each argument
is still checked on its own, so array or struct syntax as an argument is rejected. See
[Static Non-Yielding Classification](./spec.md#static-non-yielding-classification) and
[Calls, Constructors, and Conversions](./spec.md#calls-constructors-and-conversions).

### Compose a complete program

```l
fn clamp(value: i32, low: i32, high: i32) i32 {
  if value < low { return low };
  if value > high { return high };
  value
}

fn score(reading: i32) i32 {
  let bounded: i32 = clamp(reading, 0, 100);
  let centered: i32 = bounded - 50;
  if centered < 0 { -centered } else { centered }
}

pub fn main() i32 {
  score(92) - 42
}
```

Calls evaluate the callee and then arguments from left to right. They require the declared arity and compatible argument
types. A mutable storage reference may become immutable only at the top level, and a reference may gain top-level
`volatile`; numeric promotion, immutable-to-mutable conversion, volatility removal, and deeper qualifier changes are not
implicit. Parameters and results keep their declared types across the call; there are no named, default, or variadic
arguments.

An expected result type also reaches an explicit `return`, the final expression of a sequence, each yielding conditional
or switch arm, and a contextual `while`, `for`, or `loop` expression's `break` and `else` results. Aggregate
initializers in these positions can use that type. This is contextual checking, not inference of an aggregate shape.
Returning newly allocated aggregate backing is type-correct but leaves a dangling reference once the allocating function
returns.

### Keep precedence unsurprising

Postfix calls, conversions, and selections bind tightly, followed by prefix operations, arithmetic, comparisons, Boolean
operations, and assignment. A control head consumes an expression directly, so `if ready` and `if (ready)` differ only
in the ordinary expression grouping around `ready`. Every control body is a block, so a completed `if`, `while`, `loop`,
`for`, or `switch` is an ordinary operand and needs no parentheses:

```l
if ready { current } else { fallback } + 1
```

The same rule replaces `else if`: write the nested conditional inside the `else` block.

```l
if value < low { low } else { if value > high { high } else { value } }
```

The exact grammar and precedence are defined by [Expression Grammar](./spec.md#expression-grammar).

Next, [Storage and Loops](#storage-and-loops) adds mutation. This is where `l` departs most visibly from ordinary C
variable syntax.

## Storage and Loops

As [Memory Model](#memory-model) explains, `let` only binds an immutable value. To keep changing state, obtain a
reference to storage and load or store through it. The reference and its binding remain unchanged.

### A storage name is an address

These expressions perform different jobs:

| Form                                                     | Meaning                                                                    |
| -------------------------------------------------------- | -------------------------------------------------------------------------- |
| `let initial: i32 = 40`                                  | Binds a scalar, with no slot allocation                                    |
| `let cell: mut struct { value: i32 } = {value: initial}` | The initializer allocates backing; the binding holds its mutable reference |
| `let current: mut *i32 = cell.value`                     | Binds the projected mutable field-slot reference                           |
| `let view: *i32 = current`                               | Binds an immutable view of the same slot                                   |

Load from storage with prefix `*`:

```l
let initial: i32 = 40;
let cell: mut struct { value: i32 } = {value: initial};
let current: mut *i32 = cell.value;
let limit: i32 = 42;
let loaded: i32 = *current;
```

Every local binding has an initializing expression. There is no uninitialized local or implicit zero initialization, and
`let limit: i32 = 42` does not provide storage to load or write.

### Assign to the pointer, not the load

Assignment expects a writable pointer as its target:

```l
current = 41;
current += 1;
*current
```

The target is `current`, not `*current`. `current` identifies the storage; `*current` reads it. Assignment returns the
value it writes, so it can feed a larger expression. Compound assignment evaluates its target only once. Neither form
rebinds `current`; a previously loaded scalar remains the value that was read.

This model replaces several familiar C forms. There is no address-of operator; aggregate field and element projections
already provide slot references. There are no increment or decrement operators. Use `current += 1` and `*current`
explicitly.

### Loop over state

A `while` condition is checked before each iteration. The body value is discarded:

```l
let cell: mut struct { value: i32 } = {value: 0};
let current: mut *i32 = cell.value;
while *current < 10 {
  current += 1
};
*current
```

`continue` starts the next iteration. `break` exits the innermost loop. Both forms can occur where another path would
produce a value because they do not yield to the immediate expression. Like `return`, they are complete jump expressions
rather than primary expressions; use parentheses to place one directly inside an operator operand.

### Loop until an explicit result

`loop` has no condition or normal termination path:

```l
let answer: i32 = loop {
  if ready() { break 42 };
  continue
};
```

Every normally completed body iteration starts the next one, and the body value is discarded. Reachable `break` values
determine the expression's type. A `loop` that cannot break is `Never`, so `fn wait() i32 { loop {} }` is type-correct
even though it never returns.

### Let a loop produce a value

Adding `else` turns normal loop completion into a result. A valued `break` supplies the alternative result and skips the
`else`:

```l
fn converge(start: i32) i32 {
  let cell: mut struct { value: i32 } = {value: start};
  let current: mut *i32 = cell.value;
  let limit: i32 = 42;
  while *current < limit {
    current += 1;
    if *current == 42 { break *current }
  } else {
    -1
  }
}

pub fn main() i32 {
  converge(40) - 42
}
```

Here the successful `break` produces `42`. If the condition is false before that happens, `else` produces `-1`. All
yielding `break` values and the `else` value must have compatible types. Without `else`, `while` and `for` have type
`void`, and a bare `break` supplies `void`.

`for` uses the same `break`, `continue`, and `else` model. It becomes useful after the manual introduces arrays and
slices.

### Storage is deliberately low-level

The state above is allocated before the loop and reused. Moving an aggregate initializer inside the loop instead creates
fresh backing on every evaluation. All those regions last until the allocating function returns, so such loops grow the
stack and can exhaust it; iteration boundaries do not reclaim storage.

The language does not infer exclusive ownership of these addresses. A function can receive two writable references to
the same storage, and a reference can escape the function that allocated it without extending storage duration.
[Raw Pointers and Unsafe](#raw-pointers-and-unsafe) develops those responsibilities after the data model is in place.

See [Memory, Bindings, and Storage](./spec.md#memory-bindings-and-storage) and [Control Flow](./spec.md#control-flow)
for exact typing and evaluation rules.

Next, [Aggregate Data](#aggregate-data) explains why arrays and structs behave differently from the direct values used
in this chapter.

## Aggregate Data

All `l` values are immutable semantic entities. Their runtime representations fall into two categories, which determine
what a binding, parameter, return value, selection, and equality operation carries:

| Model          | Types                                                         | What moves through an expression |
| -------------- | ------------------------------------------------------------- | -------------------------------- |
| Direct         | Primitives, raw pointers, function references, tuples, slices | The complete value               |
| Address-valued | Fixed arrays, structs, enums                                  | The address of backing storage   |

An array, struct, or enum value is already a reference. It is not implicitly loaded as one large composite value.
Binding, passing, or returning it carries the backing address and does not copy its contents or transfer ownership.

### Tuples are direct values

A tuple groups at least two values without naming them:

```l
let Range: Type = (i32, i32);

fn width(range: Range) i32 {
  range.1 - range.0
}
```

Projection returns the selected value directly. `range.0` is not a pointer to an assignable tuple slot. Tuple types are
structural and positional, and tuple equality compares their elements recursively.

To keep a tuple in writable storage, use a mutable aggregate such as
`let holder: mut struct { range: Range } = {range: pair}`. Its `range` field projects a `mut *Range`; load the complete
tuple before projecting its elements. A direct tuple cannot itself be qualified `mut`, but its reference-valued elements
can carry their own qualifiers.

### Fixed arrays provide backing storage

An array type includes a positive literal length:

```l
let readings: [i32; 4] = [10, 20, 30, 40];
```

Here `readings` has the written type `[i32; 4]`, not an extra raw-pointer wrapper. The initializer allocates and
constructs immutable backing; `let` only binds its reference. Indexing a direct element projects an immutable `*i32`
slot, so reading the third value uses `*readings[2]`.

There are no bounds checks. The index may use any unsigned integer type, and a literal index is a `usize`, but it is
your responsibility to keep it within the fixed length.

An array initializer needs a known array type and returns a fresh backing reference. Assigning array syntax to an
existing writable array instead performs a constructor update in the same backing and returns `void`:

```l
let readings: mut [i32; 3] = [10, 20, 30];
readings = [12, 22, 32];
readings[1] = 42;
```

Each element takes the declared element type, so `let bytes: [u8; 2] = [1, 255];` needs no conversion. The array type
itself must still come from context: `let values = [1, 2];` is rejected, because an unannotated binding gives the
initializer no expected type and the compiler does not infer an aggregate shape.

Array equality compares backing addresses, not elements.

### Structs are structural addresses

Struct types are anonymous and structural. A transparent alias gives one a useful name:

```l
let Reading: Type = struct {
  channel: u8,
  value: i32,
};
```

Field names, order, types, and whether the struct is packed all participate in its identity. Fields must have unique
names. There are no nominal struct declarations.

Initialize every field exactly once. Source order in the initializer controls evaluation order, but it does not need to
match declaration order:

```l
let reading: mut Reading = {value: 40, channel: 1};
reading.value += 2;
let value: i32 = *reading.value;
```

A direct field selection yields `mut *T` from a mutable base and `*T` from an immutable base, including for a raw
pointer, tuple, function reference, or slice field. An aggregate field selection instead automatically loads the boxed
backing reference with exactly the declared field type. It adds no pointer level or deeper capability.

### Put the models together

```l
let Reading: Type = struct {
  channel: u8,
  value: i32,
};

let Range: Type = (i32, i32);

fn set_value(reading: mut Reading, value: i32) {
  reading.value = value;
}

fn width(range: Range) i32 {
  range.1 - range.0
}

pub fn main() i32 {
  let reading: mut Reading = {channel: 1, value: 40};
  let history: [i32; 3] = [10, 20, 42];
  let expected: i32 = *history[2];
  let accepted: Range = (0, expected);

  set_value(reading, expected);
  if *reading.value == expected && width(accepted) == 42 { 0 } else { 1 }
}
```

The tuple `accepted` moves as a complete pair. `reading` and `history` move as addresses. Calling `set_value` therefore
updates the original `reading` storage.

### Access follows declared types

`Reading` is immutable and `mut Reading` permits updates through that reference. `mut Reading` can implicitly become
`Reading` only at the top level, never the reverse. An API taking or returning `Reading` keeps access immutable; another
mutable alias may still change the same region.

Nested aggregates are separately backed, not inline. The distinction is visible during an outer update:

```l
let Inner: Type = struct { value: i32 };
let Outer: Type = struct { inner: mut Inner, tag: u8 };

let outer: mut Outer = {inner: {value: 1}, tag: 0};
let alias: Outer = outer;
let old: Inner = outer.inner;
outer = {inner: {value: 2}, tag: 1};
```

`outer == alias` remains true. `outer.inner != old` is now true, and `*old.value` is still `1`: nested initializer
syntax created a new child and replaced the stored child reference. In contrast, `outer.inner = {value: 3}` loads and
updates the current child without replacing it. `outer = alias` is invalid, while `let another: Outer = alias` only
creates an alias.

For `view: Outer`, the direct `view.tag` slot is immutable, but `view.inner` still loads exactly the declared
`mut Inner`, which permits child writes. Declare `inner: Inner` to keep that child immutable. A field declared `*Inner`
is different again: it stores an immutable raw pointer to an `Inner` reference slot, not the child's ordinary `Inner`
reference.

Recursive aliases are possible when an alias in the cycle directly names an address-valued aggregate or function
reference, allowing outer access qualifiers on eligible types. This makes a recursive struct type finite because a
nested aggregate member stores a backing address:

```l
let Node: Type = struct {
  value: i32,
  next: Node,
};
```

Cycles through only aliases or raw pointers are rejected. A slice's element type must not reach any recursive alias
cycle, even one that contains no slice itself. For example, `let Chain: Type = [Chain; 1];` is accepted, but `[Chain]`
and slicing a `Chain` value are not. See [Type Aliases](./spec.md#type-aliases) for the exact recursion rules.

Next, [Slices and Text](#slices-and-text) creates direct views over array and string storage.

## Slices and Text

A fixed array reference designates a known amount of backing storage. A slice is a direct, by-value descriptor that
views contiguous storage without copying it. The descriptor contains an element-storage address and a `usize` element
count.

### Create a view with a half-open range

Apply a postfix range to an array or another slice:

```l
let readings: [i32; 5] = [10, 20, 30, 40, 50];
let middle: [i32] = readings[1..3];
let prefix: [i32] = readings[..2];
let suffix: [i32] = readings[2..];
let whole: [i32] = readings[..];
```

The range is half-open, so it holds `end - start` elements: `readings[1..3]` views two of the five. An omitted start is
`0` and an omitted end is the source count, which makes `readings[..]` the whole view.

Read the current count through `.length`, which always returns `usize`:

```l
let count: usize = middle.length;
```

Slicing preserves the element type and the base's outer `mut` and `volatile` qualifiers. It allocates no element buffer
and extends no lifetime. Neither slicing nor indexing checks its bounds or range. An unqualified `[T]` descriptor denies
writes to the viewed element slots, `mut [T]` permits them, and `volatile [T]` makes reads volatile without permitting
writes; none of these outer properties changes separately stored child references after they are loaded.

A slice's element type cannot reach a recursive alias cycle, even through an aggregate that is legal on its own.
`let Chain: Type = [Chain; 1];` is valid, but `values[..]` for `values: Chain` is rejected: the resulting slice element
reaches the recursive `Chain` type. The same restriction applies to written slice types such as `[Chain]`, not only to
cycles that contain a slice. See [Type Aliases](./spec.md#type-aliases).

### Iterate over element values

`for` accepts an array or slice, evaluates it once, and visits elements in ascending index order:

```l
fn sum(values: [i32]) i32 {
  let cell: mut struct { value: i32 } = {value: 0};
  let total: mut *i32 = cell.value;
  for value in values {
    total += value
  };
  *total
}
```

The loop binding is the current element value. It does not need the explicit dereference used by `*values[index]`.

As with `while`, a `for` can return a value. Exhaustion runs `else`; `break value` skips it:

```l
fn first_even(values: [i32]) i32 {
  for value in values {
    if value % 2 == 0 { break value }
  } else {
    -1
  }
}
```

An empty slice reaches `else` immediately. `continue` advances to the next element.

### Strings are UTF-8 byte slices

A string literal has type `[u8]`, and its read-only backing storage lasts for the module's runtime lifetime:

```l
let label: [u8] = "monitor";
let first: u8 = *label[0];
let bytes: usize = label.length;
```

Indexing observes encoded bytes, not Unicode characters. There is no hidden trailing zero. Escapes are decoded before
the text is encoded as UTF-8. Its unqualified immutable type is retained when the slice is passed, returned, stored, or
sliced. A parameter accepting string literals must accept `[u8]`, not `mut [u8]`.

Triple quotes allow raw newlines:

```l
let report: [u8] = """sensor one
sensor two""";
```

A `c` prefix creates a different value for C APIs: `c"monitor"` has type `*u8` and includes one trailing zero byte. This
matches `@CType("const char *")` because that macro expands to `*u8`.
[Targets and C Interoperation](#targets-and-c-interoperation) uses that form.

### Complete example

```l
fn sum(values: [i32]) i32 {
  let cell: mut struct { value: i32 } = {value: 0};
  let total: mut *i32 = cell.value;
  for value in values { total += value };
  *total
}

fn first_even(values: [i32]) i32 {
  for value in values {
    if value % 2 == 0 { break value }
  } else {
    -1
  }
}

pub fn main() i32 {
  let readings: [i32; 4] = [1, 3, 6, 8];
  let window: [i32] = readings[1..4];
  let label: [u8] = "monitor";

  if
    window.length == 3 &&
    sum(window) == 17 &&
    first_even(window) == 6 &&
    label.length == 7
  {
    0
  } else {
    1
  }
}
```

The exact count formulas, accepted index types, and invalid-range behavior are in [Slice Types](./spec.md#slice-types)
and [Raw Pointer and Memory Behavior](./spec.md#raw-pointer-and-memory-behavior).

Next, [Enums and Switches](#enums-and-switches) uses address-valued data to model alternatives.

## Enums and Switches

An `l` enum is a structural discriminated union. Each variant is either a unit case or carries one payload. Like arrays
and structs, an enum value is the address of backing storage.

### Describe the alternatives

```l
let Event: Type = enum {
  Reading: i32,
  Fault: u8,
  Offline,
};
```

Variant order, names, payload types, and IDs all participate in structural identity. IDs normally use zero-based source
positions. When an external format requires specific IDs, write a unique nonnegative ID for every variant:

```l
let Result: Type = enum {
  Ok: i32 = 10,
  Error = 20,
};
```

Do not mix implicit and explicit IDs in one enum.

### Constructors name their enum

A variant is written through its enum, so a constructor carries its own type:

```l
let event: mut Event = Event::Reading(40);
event = Event::Fault(2);
event = Event::Offline;
```

`Event::Reading(40)` allocates fresh enum backing and yields its reference, which `let` binds. `event = Event::Fault(2)`
and `event = Event::Offline` instead run constructors against the existing writable backing, retain its identity, and
yield `void`. `event = another_event` is not valid aggregate assignment.

The prefix is an ordinary type path, so a nested enum is reached as `model::Event::Offline` and, from the root,
`::model::Event::Offline`. A structural enum without an alias cannot be constructed, because there is no path that names
it. Since variants and associated functions share the enum's one namespace, `impl Event { fn Offline() ... }` is an
error.

A payload variant takes exactly one value or nested aggregate initializer. An aggregate payload is boxed: nested syntax
creates separate child backing and stores its reference. Updating an enum with a fresh nested initializer replaces the
payload reference, not the old child's data.

### Switch exhaustively

`switch` evaluates the enum address once and chooses an arm:

```l
fn event_score(event: Event) i32 {
  switch event {
    case Reading(value):
      value
    case Fault(code):
      -i32(code)
    case Offline:
      -1
  }
}
```

Without `default`, every variant must appear exactly once. This makes adding a variant visible wherever the enum is
handled. A `default` can replace omitted cases, but it is invalid when all variants are already named.

A payload binding belongs only to its arm and loads the exact declared payload type. A direct payload binds as a value;
an aggregate payload binds as its stored backing reference. An immutable `Event` protects enum storage, but a payload
declared `mut Child` still loads as mutable; leave the payload type unqualified when child access must stay immutable.
Unit variants cannot have payload bindings.

Every arm with a static value type must have a compatible type. As with `if`, an arm statically classified as `Never`,
such as a direct `return`, `break`, or `continue`, does not force the other arms' type. A strict expression containing a
`Never` operand is also `Never`; a short-circuit right operand is the important exception. See
[Static Non-Yielding Classification](./spec.md#static-non-yielding-classification).

A scrutinee that is statically `Never`, such as `switch (return 0) { ... }`, is rejected: no enum value ever selects an
arm. See [Switch Expressions](./spec.md#switch-expressions).

### Complete example

```l
let Event: Type = enum {
  Reading: i32,
  Fault: u8,
  Offline,
};

fn event_score(event: Event) i32 {
  switch event {
    case Reading(value):
      value
    case Fault(code):
      -i32(code)
    case Offline:
      -1
  }
}

pub fn main() i32 {
  let event: mut Event = Event::Reading(42);
  if event_score(event) != 42 { return 1 };

  event = Event::Offline;
  if event_score(event) != -1 { return 2 };

  event = Event::Fault(2);
  if event_score(event) != -2 { return 3 };

  0
}
```

Enum equality compares backing addresses rather than variants and payloads. Use `switch` when you need semantic
comparison or payload access.

For exact constructor, coverage, layout, and invalid-discriminator rules, see [Enum Types](./spec.md#enum-types) and
[Switch Expressions](./spec.md#switch-expressions).

Next, [Function References](#function-references) applies the same expression-oriented model to behavior.

## Function References

A function name is a first-class callable value. Its type records parameter and result types:

```l
fn (left: i32, right: i32) i32
```

Parameter names inside a function-reference type are descriptive. Only their order and types and the result type
determine identity.

### Pass and return behavior

```l
let Strategy: Type = fn (left: i32, right: i32) i32;

fn maximum(left: i32, right: i32) i32 {
  if left > right { left } else { right }
}

fn minimum(left: i32, right: i32) i32 {
  if left < right { left } else { right }
}

fn choose(want_maximum: bool) Strategy {
  if want_maximum { maximum } else { minimum }
}

fn apply(strategy: Strategy, left: i32, right: i32) i32 {
  strategy(left, right)
}
```

The selected function reference moves as a direct, pointer-sized value. It can be bound, compared for identity, passed,
returned, and called. A call is a postfix expression, so a returned function can be called directly as
`choose(true)(20, 22)`.

### Anonymous functions do not capture

An anonymous function writes its complete signature:

```l
let average = fn(left: i32, right: i32) i32 {
  (left + right) / 2
};
average(20, 22)
```

The binding takes the anonymous function's written type; an annotation, such as a compatible function-type alias, is
optional. Anonymous functions are not closures: their bodies can use their own parameters and module-level functions or
statics, but not parameters or bindings from an enclosing function.

Choose an ordinary function with an explicit parameter when behavior needs runtime context. Do not expect an anonymous
function to retain local state.

### Storage adds one pointer level

A function reference already represents its first reference level. It is not a raw pointer. To keep one in a storage
slot, initialize an aggregate field and project that slot:

```l
let holder: struct { operation: Strategy } = {operation: maximum};
let slot: *Strategy = holder.operation;
let selected: Strategy = *slot;
selected(20, 22)
```

The type of `slot` is `*Strategy`, equivalent to `*fn (left: i32, right: i32) i32`. Dereference the slot before calling
the stored function.

`mut Strategy` is invalid: calling a function is not writing through a storage reference. An unqualified `*Strategy`
prevents replacement of the stored function reference while still allowing it to be loaded and called; use
`mut *Strategy` for a replaceable slot. Parameter and return types inside `Strategy` retain their own reference
qualifiers; changing those qualifiers is not an implicit conversion between function types.

### Complete example

```l
let Strategy: Type = fn (left: i32, right: i32) i32;

fn maximum(left: i32, right: i32) i32 {
  if left > right { left } else { right }
}

fn minimum(left: i32, right: i32) i32 {
  if left < right { left } else { right }
}

fn choose(want_maximum: bool) Strategy {
  if want_maximum { maximum } else { minimum }
}

pub fn main() i32 {
  let average: Strategy = fn(left: i32, right: i32) i32 { (left + right) / 2 };
  let selected: Strategy = choose(true);
  let holder: struct { operation: Strategy } = {operation: selected};
  let slot: *Strategy = holder.operation;

  if average(20, 22) != 21 { return 1 };
  if (*slot)(20, 42) != 42 { return 2 };
  if *slot != maximum { return 3 };
  0
}
```

Function references do not convert to raw pointers or integers. See
[Function Reference Types](./spec.md#function-reference-types) for identity and invalid-call behavior.

Next, [Associated Functions](#associated-functions) keeps a type's operations next to its definition.

## Associated Functions

A function that belongs with a type can be placed in that type's namespace. An `impl` block does exactly that and
nothing more: the functions inside are ordinary functions, and the block only decides which path names them.

### Put functions in a type's namespace

```l
let Point: Type = struct {
  x: i32,
  y: i32,
};

impl Point {
  pub fn set(point: mut Point, x: i32, y: i32) {
    point = {x: x, y: y};
  }

  pub fn sum(point: Point) i32 { *point.x + *point.y }
}

pub fn main() i32 {
  let point: mut Point = {x: 0, y: 0};
  Point::set(point, 20, 22);
  Point::sum(point) - 42
}
```

`Point::set` and `Point::sum` are called through the type. There is no receiver: an associated function has no `self`,
`point.sum()` is not a call, and the value it works on is an ordinary parameter. Compared to a free `fn sum(point)`, the
only difference is the name, which cannot collide with a module function, static, alias, or namespace called `sum`.

The block takes no `pub`. Each function inside does, with the usual meaning: `pub fn` is exported and callable from any
module, while a function without `pub` is callable only from the module that contains the block. A bodyless `fn f();`
declares an associated function supplied by another object.

### Any type owns a namespace

The target can be a primitive as well as an alias:

```l
impl u8 {
  pub fn double(value: u8) u8 { value * 2 }
}

fn apply(operation: fn (value: u8) u8, value: u8) u8 { operation(value) }

pub fn main() i32 {
  let double: fn (value: u8) u8 = u8::double;
  if apply(double, 21) != 42 || u8::double(1) != 2 { return 1 };
  0
}
```

`u8::double` is an ordinary path: `u8` is a predefined name, not a keyword, so it is a path segment like `Point` in
`Point::new`. Like every associated function, it is an ordinary function value: bind it, pass it, compare it, or store
it in a static. The target of an `impl` block may be any type expression, but a call always spells the type as a name,
so give `[u8]`, `(i32, i32)`, or `*u8` an alias first: `let Bytes: Type = [u8]; impl Bytes { ... } Bytes::first(b)`.
`void` and `mut`- or `volatile`-qualified targets are rejected.

Primitive numeric, integral, and Boolean types also own a fixed set of predefined functions. They can be used directly,
including as function values:

```l
let add: fn (left: i32, right: i32) i32 = i32::add;
let total: i32 = add(20, 22);
let ready: bool = bool::not(false);
```

Their names are reserved only on targets that provide them, so `impl i32 { fn add(...) ... }` is rejected while the
user-defined `u8::double` above remains valid. Operators always apply the predefined functions selected by their
existing typing rules; an `impl` block cannot overload them. See [Static Typing](./spec.md#static-typing) and
[Predefined Functions](./spec.md#predefined-functions) for the complete operator and function tables.

### `Self` names the target

Inside the block, `Self` stands for the target, so a function can be written without repeating the type it belongs to:

```l
let Point: Type = struct {
  x: i32,
  y: i32,
};

impl Point {
  pub fn set(point: mut Self, x: i32, y: i32) mut Self {
    point = {x: x, y: y};
    point
  }

  pub fn origin(point: mut Self) mut Self { Self::set(point, 0, 0) }

  pub fn size() usize { @sizeOf(Self) }
}

pub fn main() i32 {
  let point: mut Point = {x: 1, y: 2};
  Point::origin(point);
  *point.x + *point.y + i32(Point::size()) - 8
}
```

`Self` is an alias and nothing more: it is replaced by the target before type checking, so `Self` and `Point` are one
type, `mut Self` is `mut Point`, and `Self::set` is `Point::set`. It is accepted wherever a type is written inside the
block, including anonymous-function signatures and macro arguments. `Self::f` needs the target to be a predefined name
or an alias path, exactly as a call does, so under `impl [u8]` the type `Self` is fine but `Self::first` is an error;
give the slice an alias to use both. Outside an `impl` block there is nothing for `Self` to name, and the target of a
block is outside it, so `impl Self` is an error too.

### Aliases are transparent here too

The language has no nominal types, so the namespace belongs to the structural type, not to a name. Two aliases of one
type share it, blocks for one type merge wherever they are written, and a second function of the same name on the same
type is an error even when a different alias spells it:

```l
let Point: Type = struct { x: i32, y: i32 };
let Coordinate: Type = Point;

impl Point { pub fn sum(point: Point) i32 { *point.x + *point.y } }
impl Coordinate { pub fn zero() i32 { 0 } }

namespace geometry {
  // The same structural type, so `Point::sum` here is the root's `Point::sum`.
  let Point: Type = struct { x: i32, y: i32 };
  pub fn total(point: Point) i32 { Point::sum(point) + Point::zero() }
}
```

Resolving `Point` in `geometry::total` follows the ordinary type-path rules, so a child module reaches the namespace
through an alias it can name; once the type is known, `sum` and `zero` are found on it from any module.
`Coordinate::sum` and `::Point::zero` name the same functions. `impl Coordinate { pub fn sum(...) }` would be a
duplicate.

Linking follows the same idea. An associated function's symbol is derived from the resolved type alone, so `impl u8`
defines `1h__parse` and `impl Point` defines `8O1xl1ylE__new` in every package, and `--name` does not apply. Two
packages that both define `Point::new` therefore clash at link time, which the compiler cannot detect.

`l bundle --header` keeps public associated functions as declarations inside their `impl` block, and `l doc` lists them
under their type. See [Associated Functions](./spec.md#associated-functions) and
[Associated Function Symbols](./spec.md#associated-function-symbols) for the exact rules and the encoding.

Next, [Raw Pointers and Unsafe](#raw-pointers-and-unsafe) deals with addresses that do permit explicit reinterpretation.

## Raw Pointers and Unsafe

Storage projections introduced raw pointers as part of ordinary code. This chapter makes their type and conversion rules
explicit and, more importantly, defines what the language does not protect.

### Immutable and mutable access

`*T` permits reading but not writing a slot containing `T`. `mut *T` explicitly permits writing that slot. Both are
immutable reference values; `mut` describes access through the reference, not rebinding:

```l
fn read(value: *i32) i32 {
  *value
}

fn increment(value: mut *i32) i32 {
  value += 1
}
```

A mutable reference converts implicitly to the corresponding immutable reference only at the outer level. For example,
`*mut *i32` is an immutable outer slot containing a mutable `*i32`; loading it yields exactly `mut *i32`. `mut **i32`
instead permits replacing an outer slot whose stored pointer is immutable. Neither type implicitly converts to the other
because that would change capability at a deeper level.

`*void` and `mut *void` are opaque addresses. They can be passed, compared, and converted, but not dereferenced or
assigned through because `void` has no stored representation.

### Swap through addresses

Names bound to slot references can be passed directly because they already have pointer type:

```l
fn swap(left: mut *i32, right: mut *i32) {
  let saved: i32 = *left;
  left = *right;
  right = saved;
}
```

There is no address-of operator. An aggregate field or element, a static name, a parameter, a function result, a loaded
reference, or an explicit integer conversion supplies an address. `let` never allocates a slot on its own.

### Use `unsafe` for address construction and retyping

Pointer-to-integer conversion is accepted directly only for `usize` and `isize`:

```l
let address: usize = usize(pointer);
```

Constructing a pointer from either pointer-width integer, retyping a raw pointer, or adding `mut` to an immutable raw
pointer requires an `unsafe` block:

```l
let IntPointer: Type = *i32;
let BytePointer: Type = *u8;
let WritableBytes: Type = mut *u8;

let restored: IntPointer = unsafe { IntPointer(address) };
let bytes: BytePointer = unsafe { BytePointer(pointer) };
let writable: WritableBytes = unsafe { WritableBytes(bytes) };
```

`unsafe` is lexical and adds no runtime check. It states that the program is taking responsibility for a conversion; it
does not prove that an address is aligned, live, correctly typed, or valid to access. It also does not make unsupported
conversions legal.

Adding `mut` by explicit unsafe raw-pointer retyping changes only the pointer type. A later store is defined only when
the underlying storage is genuinely writable; string backing and constant static regions remain physically read-only
after a conversion. There is no safe capability-adding conversion, and converting an immutable aggregate or slice
directly to its mutable type is invalid even inside `unsafe`.

Dereference and assignment through a pointer do not themselves require an `unsafe` block. Their syntax is ordinary, but
their behavior is defined only when the address is valid.

### Complete example

```l
let IntPointer: Type = *i32;

fn read(value: *i32) i32 {
  *value
}

fn swap(left: mut *i32, right: mut *i32) {
  let saved: i32 = *left;
  left = *right;
  right = saved;
}

pub fn main() i32 {
  let pair: mut struct { left: i32, right: i32 } = {left: 20, right: 22};
  let left: mut *i32 = pair.left;
  let right: mut *i32 = pair.right;
  swap(left, right);

  if read(left) != 22 || read(right) != 20 { return 1 };

  let address: usize = usize(left);
  let restored: IntPointer = unsafe { IntPointer(address) };
  if restored != left { return 2 };
  0
}
```

### Remember aggregate pointer depth

An address-valued aggregate `A` already represents `address -> aggregate storage`. A raw `*A` is therefore another
level: `address -> A address -> aggregate storage`. Passing a struct normally uses `A`, not `*A`.

For `Point = struct { value: i32 }`, the following levels differ:

| Type             | Access                                                   |
| ---------------- | -------------------------------------------------------- |
| `*Point`         | Immutable slot containing an immutable `Point` reference |
| `mut *Point`     | Mutable slot containing an immutable `Point` reference   |
| `*mut Point`     | Immutable slot containing a mutable `Point` reference    |
| `mut *mut Point` | Mutable slot containing a mutable `Point` reference      |

If `slot: mut *Point`, `slot = {value: 1}` materializes fresh immutable `Point` backing and stores its reference in the
slot, yielding `Point`. With `slot: *mut Point`, `*slot = {value: 1}` instead updates the currently referenced mutable
backing and yields `void`. Use `mut *mut Point` when both operations are required.

Function references also occupy an address-sized representation but remain a separate type family. They do not convert
to raw pointers or integers.

### Use volatile references for memory-mapped I/O

`volatile T` is a shallow modifier for memory references. It is valid on raw pointers, fixed arrays, structs, enums, and
slices. Every load through that reference level is emitted as a volatile LLVM operation; a store additionally requires
`mut`:

```l
let Register: Type = mut volatile *u32;
let register: Register = unsafe { Register(0x40021018) };
let enabled: u32 = *register;
register = enabled | (1 << 4);
```

The dereference loads the register with a volatile LLVM load, and the assignment writes it with a volatile LLVM store.
An outer volatile array or struct similarly makes direct element or field accesses volatile. Loading a separately stored
aggregate reference is itself volatile, but the resulting child keeps its declared type; mark the child reference
volatile separately when accesses through it must also be volatile.

`volatile R` is immutable volatile access: reads are volatile, and stores through that level are rejected.
`mut volatile R` permits volatile writes. Mutability and volatility are independent; mutable access may be discarded and
volatile access may be added at the top level, but neither property moves into nested types implicitly. Neither volatile
loads nor volatile stores are atomic or a synchronization primitive.

### What remains your responsibility

The language performs no ownership, aliasing, provenance, lifetime, range, or bounds analysis. It allows null and
dangling pointers, overlapping writable access, and pointers or slices that outlive stack storage. Address-valued
arrays, structs, and enums returned from a function can also refer to expired stack storage.

There is no pointer arithmetic; use array or slice indexing. Unqualified accesses are ordinary non-volatile operations;
use `volatile` references for memory-mapped I/O. No access form is atomic or provides a synchronization primitive.

Read [Raw Pointer and Memory Behavior](./spec.md#raw-pointer-and-memory-behavior) before relying on edge-case memory
behavior.

Next, [Packages and Modules](#packages-and-modules) organizes these functions and types across source files.

## Packages and modules

A source file contains module items: functions, type aliases (module-level `let` bindings annotated `Type`), statics,
`impl` blocks, and namespaces. As a program grows, it moves into a directory, and the directory's layout says which
files share a module and which form a namespace.

Everything reached from one entry point is a package: the distribution unit, and the outermost thing the language
organizes. It has no manifest file, so the entry point alone defines it. The entry point is a single file, which is
already a package, or a directory. A package with a public root `main` is an executable package; without one it is a
library package, and an executable package serves as both.

### Split a program into files

Create this layout:

```text
monitor/
  main.l
  stats.l
  model/
    reading.l
```

`model/reading.l` defines the shared shape:

```l
let Reading: Type = struct {
  value: i32,
};
```

`stats.l` reaches into the sibling namespace through a qualified path:

```l
fn adjust(reading: mut model::Reading, amount: i32) {
  reading.value += amount;
}
```

`main.l` drives the program:

```l
pub fn main() i32 {
  let reading: mut model::Reading = {value: 40};
  adjust(reading, 2);
  *reading.value - 42
}
```

Run the entry directory:

```sh
l check monitor
l run monitor
```

Nothing in the sources names another file. The `.l` files directly under `monitor` are one module, the root, so `main.l`
calls `adjust` from `stats.l` without a qualifier and without `pub`. The `model` subdirectory is a namespace named after
it, so `Reading` is reached as `model::Reading`. A subdirectory nested deeper is a namespace nested deeper:
`model/units/length.l` would place its items at `model::units`.

A subdirectory forms a namespace only when it holds `.l` files somewhere below it, and its name must be an identifier.
Directories holding documentation, build output, or anything else without `.l` files are ignored. The merged module is
exactly what you could have written by hand in one file:

```text
<contents of main.l>
<contents of stats.l>

namespace model {
  let Reading: Type = struct { value: i32 };
}
```

Inspect it with `l bundle monitor`. Files are merged in name order, then namespaces in name order; because items are
visible throughout their module regardless of order, the order only affects the printed bundle.

### Qualified paths are relative unless they start at the root

A path is resolved relative to the module that contains it. `model::Reading` written in the root module names `Reading`
inside the root's `model` namespace, which is why `stats.l` and `main.l` above can use it. The same path written inside
a `stats` namespace would instead look for `stats::model::Reading`.

A leading `::` makes a path absolute, so it starts at the root module wherever it is written:

```l
namespace stats {
  pub fn adjust(reading: mut ::model::Reading, amount: i32) {
    reading.value += amount;
  }
}
```

`::model::Reading` reaches the root's `model` namespace from inside `stats`. The prefix also works with a single
segment: in the root module, `::adjust` names the root's own `adjust` function even where a parameter or local binding
called `adjust` would shadow the bare name.

An unqualified function or named type searches only the current module and does not fall back to a parent. A child
namespace cannot access items from a parent module, not even through an absolute path, so `::adjust` written inside
`stats` is an error. This keeps lookup local but means nested code must be designed around root-level namespace
boundaries rather than parent access.

Paths never import a name: write the relative or absolute path directly, and leave a space after a `:` that precedes an
absolute path: `let reading: ::model::Reading` lexes as intended, while `reading:::model::Reading` does not.

### Declare a namespace inline

A namespace can also be declared inline with the `namespace` keyword:

```l
namespace monitor {
  fn read() i32 { 40 }
  pub fn adjusted() i32 { read() + 2 }
}

pub fn main() i32 {
  monitor::adjusted() - 42
}
```

Each namespace is declared once. A second `namespace monitor { ... }` is a duplicate item, and so is an inline
`namespace model { ... }` next to a `model` directory. Splitting a module across several `.l` files of one directory is
not a declaration and stays allowed: items are visible throughout their module regardless of source order or which file
contains them.

### Export a module API with `pub`

A private function can be referenced only in its own module. Mark each function called through another module `pub`, as
with `monitor::adjusted` above; `adjust` needs no `pub`, because `stats.l` and `main.l` supply the same root module.
`pub` also controls exported linker visibility, although public module-qualified symbol names are not a stable foreign
interface. A bodyless declaration takes the same `pub` for the same reason, even though it is externally linked either
way: without it, only its own module may call the function another object supplies.

Statics use `pub` for cross-module visibility on both declarations and definitions:

```l
namespace state {
  pub static count: mut *u32 = 0;
  pub static external_limit: *u32;
  static private_count: mut *u32 = 0;
}
```

`state::count` is a mutable reference to defined scalar storage; `state::external_limit` is an immutable reference to
externally supplied storage. That declaration does not promise that the external region is physically constant.
`private_count` is inaccessible outside `state`. Visibility is independent of write capability and of whether the item
provides a definition. A private static definition has internal linkage; even a source-private declaration without an
initializer has external linkage, because another object must supply it.

### Name the package

A package name is not written in source. It is supplied when compiling, and the same sources can be compiled under any
name:

```sh
l compile --emit ll --name acme/monitor monitor
```

A name is one or more `/` separated segments, such as `acme/monitor` or `acme/monitor/stats`; a one-segment name is how
a library is named, as [Use a library](#use-a-library) shows. A segment starts with an ASCII letter and continues with
ASCII letters, digits, and interior hyphens. Each segment becomes one namespace around the package's root module, so
`acme/monitor` places that root module under `acme` and then `monitor`.

The namespace surrounds the root module rather than entering it, so nothing about writing the code changes:
`::model::Reading` still starts at the package's own root. What changes is the compiled artifact, where `stats::adjust`
becomes `acme__monitor__stats__adjust`. A bodyless function or a static without an initializer is named the same way,
since it declares what a definition in its place defines. An `extern` declaration is the exception: it names a C symbol,
so it always links to its bare item name, whatever package and namespace hold it; see
[Targets and C Interoperation](#targets-and-c-interoperation). Omitting `--name` compiles without a namespace, which is
what the examples above do.

`main` is namespaced along with every other definition, so a named executable package no longer offers the `main` symbol
the system runtime calls. Build programs without a name:

```sh
l build -o monitor/monitor monitor
```

The compiler reads each `.l` file under its canonical path and rejects one that resolves outside the entry directory, so
a symlink cannot pull sources in from elsewhere. An entry that holds no source, such as a broken symlink, is ignored,
and a symlinked directory that leads back into a directory being read is rejected. See
[Packages and Modules](./spec.md#packages-and-modules) for the layout rules.

### Use a library

A package can use another package, called a library, by its name. The repository's `libs` directory holds one such
library per platform, `linux`, `macos`, and `windows`, each a directory of sources whose `println` writes a line to
standard output:

```l
use macos;

pub fn main() i32 {
  macos::println("Hello, world!");
  0
}
```

`use macos;` says that the program needs the `macos` library, and declares the namespace `macos` holding what the
library exports. Every `use` comes before the items of its file or inline namespace. The namespace belongs to the module
the file joins, so a second `use macos;` in another file of that module is a duplicate item.

The compiler looks for the library in each directory given by `-L` (`--library-path`), in order, as the source file
`macos.l` or else the source directory `macos`, and skips a directory that does not exist:

```sh
l run -L libs hello.l
```

What the namespace holds is the library's interface, the projection `l bundle --header` prints: its aliases and `use`s,
its public declarations, and its public definitions turned into declarations. Private items stay out of reach, and
`macos::write` is an unknown function even though the library declares one. The library's own `use`s are read the same
way, and a library that ends up using itself is rejected. Inside the interface, a path starting with `::` starts at the
library's root, as it does when the library is compiled on its own.

`l check` checks the interface the program sees, not the library's function bodies; those are checked when the library
itself is compiled, as `build` and `run` do.

A library name is a one-segment package name, so it holds letters and digits but no `_`. The library is compiled as the
package of that name, so `macos::println` links as `macos__println`. The interface's declarations request exactly those
symbols, since a declaration is named like a definition in its place. Only an `extern` declaration keeps a bare C name,
as the library's own `extern fn write` does for the `write` it calls. Since a library's symbols begin with its name, a
program compiled as the package of the same name, or holding a root namespace of that name without `--name`, defines
symbols that conflict with the library's. [Build at the right boundary](#build-at-the-right-boundary) shows how the
library's code joins the program. See [Libraries](./spec.md#libraries) for the exact rules.

## Targets and C Interoperation

`l` emits native code through LLVM and uses the selected target's data layout and C calling convention. Fixed-width data
stays fixed, pointer-sized types follow address width, and `@CType` translates supported C declarations for that target.

### Separate portable data from platform interfaces

Use `u32` or `i64` when a format requires an exact width. Use `usize` or `isize` for values tied to address width. At a
C boundary, write a type as `@CType("C declaration")` so the declaration follows the selected target ABI. `CType` is a
case-sensitive predefined macro name, and its type result works anywhere a type is accepted.

The macro expands before checking to an ordinary language type rather than a distinct ABI type. Convert explicitly when
the internal value has a different type from that target-specific result:

```l
extern fn putchar(character: @CType("int")) @CType("int");
let CInt: Type = @CType("int");

fn emit(character: char) {
  putchar(CInt(character));
}
```

A declaration ending in `;` names a function supplied during linking. `extern` says that a C object supplies it, so the
declaration links to the bare name `putchar`. No ABI adapter is generated; the source declaration must actually match
the linked symbol.

Because an `extern` declaration links to its bare name (see [Name the package](#name-the-package)), C declarations can
be grouped in a namespace and still link under `--name`:

```l
namespace posix {
  pub extern fn write(descriptor: @CType("int"), buffer: @CType("const char *"), count: @CType("size_t")) @CType("long");
}
```

Declarations of one symbol anywhere in a package name one external entity, so they must all be functions of the same
type or statics of the same type. A declaration also may not share its symbol with a definition, such as a root
definition of a package compiled without `--name`, whose symbol is its bare name too.

### Use C strings only at C boundaries

An ordinary `"status"` is an immutable `[u8]` slice with a byte count and no terminator. `c"status"` is a
module-lifetime immutable `*u8` with one trailing zero byte. C `char` also maps to `u8`, so this matches a parameter
written as `@CType("const char *")`. Use the C string form only when the external API expects it.

Embedded zero bytes are allowed and may cause a C function to see a shorter string than the source text.

### Ask the target about layout

`@sizeOf(T)` expands at compile time to a `usize` containing the selected target's size:

```l
@sizeOf(usize)
@sizeOf(@CType("int"))
@sizeOf((u8, u32))
@sizeOf([u8; 16])
```

The result can differ with pointer width, C data model, alignment, and aggregate layout. Object emission fails if the
size does not fit the target `usize`.

Ordinary structs use target padding. A packed struct removes struct padding and may create unaligned fields:

```l
let Header: Type = packed struct {
  tag: u8,
  value: u32,
};
```

Packed access supports unaligned fields. Qualify the reference with `volatile` when the packed region requires volatile
access; neither packed nor volatile access is atomic.

### Complete host example

```l
extern fn putchar(character: @CType("int")) @CType("int");
let CInt: Type = @CType("int");

let Header: Type = packed struct {
  tag: u8,
  value: u32,
};

fn emit(character: char) {
  putchar(CInt(character));
}

pub fn main() i32 {
  emit('O');
  emit('K');
  emit('\n');
  if @sizeOf(Header) == 5 { 0 } else { 1 }
}
```

On a supported Linux or macOS host, `l run` links `putchar` from the system C runtime and prints `OK` followed by a
newline.

### Static storage and symbols

Module-level `static` separates an external declaration from a definition that provides storage:

```l
extern static external_count: *i32;
pub extern static external_limit: *i32;

static count: mut *i32 = 0;
pub static limit: *i32 = 100;
```

Every definition is also a declaration, just as a function body makes a function declaration a definition. A form
without an initializer requires an external symbol; it never defines implicitly zeroed storage. As with functions,
`extern` makes that symbol the bare item name a C object defines. Here `count` has exactly the written type `mut *i32`,
and the linked region contains an `i32`, not another pointer. `let count: i32 = 0` would only bind a scalar and allocate
nothing.

A static must expose either a raw pointer to a valid non-`void` slot or an address-valued array, struct, or enum,
optionally qualified `mut` or `volatile`. Direct scalars, tuples, slices, functions, and `*void` are not eligible static
types. Pointer slots can store any valid non-`void` value, including tuples, slices, raw pointers, function references,
and aggregate references:

```l
let Point: Type = struct { x: i32, y: i32 };

static origin: Point = {x: 0, y: 0};
static selected: *Point = origin;
static message: *[u8] = "ready";
static callback: *fn (value: i32) i32 = identity;
static pair: *(i32, bool) = (-1, true);

fn identity(value: i32) i32 { value }
```

`origin` directly names `Point` backing. `selected` names a separate pointer slot containing a reference to that
backing. To load the text descriptor use `*message`; to call the stored function use `(*callback)(42)`. Forward
references such as `identity` are allowed. A top-level aggregate definition must describe its own backing contents, so
`static other: Point = origin;` is invalid; a reference slot such as `selected` may alias `origin`.

Static initializers admit compatible literals, strings and C strings, negated numeric literals, tuples, array and struct
syntax, qualified enum constructors, and named static or function references as relocations. They do not admit calls,
loads, field selection, indexing, slicing, control flow, conversions, or arbitrary computation. Macros expand before
validation, and an anonymous function is rejected there before it could become a function reference; the exact whitelist
and typing exceptions are in [Static Initializers](./spec.md#static-initializers).

Nested aggregate syntax creates separate private globals and stores relocations to them. Mutability applies at each
written level: an immutable parent region can hold a child declared `mut`, whose separate backing is emitted writable.
Definitions use ordinary ABI padding and alignment, including enum pointer payloads. No runtime constructor, startup
function, or stack allocation initializes a static.

Definitions without `pub` have internal linkage; public definitions are exported. Declaration-only forms have external
linkage even without `pub`, but `pub` is still required for source access from another module. It is independent of
`mut`. A definition without outer `mut`, including an unqualified or `volatile` definition, emits constant storage in
the target's supported read-only or relocation-bearing section; a `mut` definition emits writable storage. The compiler
does not verify the type, layout, or writability of an externally supplied symbol.

### Know the ABI boundary

`@CType` accepts C character, Boolean, integer, size, pointer-difference, floating-point, and `void` scalar spellings.
It normalizes whitespace and accepts aliases such as `"short int"`, `"signed"`, and `"unsigned long int"`. It also
accepts restricted pointer declarators with `const` and `volatile`: `@CType("char *")` becomes `mut *u8`, while
`@CType("const char *")` becomes `*u8`, `@CType("volatile char *")` becomes `mut volatile *u8`, and
`@CType("const volatile char *")` becomes `volatile *u8`. Pointer chains are supported: C `const char **` becomes
`mut **u8`, while `char * const *` becomes `*mut *u8` and `char * volatile *` becomes `mut volatile *mut *u8`. Every
non-`const` pointee level gains `mut`; every `const` pointee level is unqualified and immutable. A `const` or `volatile`
qualifier on the final C pointer has no type-level effect because `l` bindings are already immutable, and a qualifier
without any pointer, such as `"const int"`, is rejected. These mappings preserve the corresponding volatile access and
do not change the ABI; the C qualifiers remain C syntax inside the quoted declarator.

C `char` and `unsigned char` expand to `u8`, while `signed char` expands to `i8`. C `_Bool`, `size_t`, `ptrdiff_t`,
`float`, and `void` expand to `bool`, `usize`, `isize`, `f32`, and `void`. The specification leaves the other spellings
implementation-defined per target; this compiler maps them as follows:

| C spelling                                     | AVR, MSP430       | Windows (not Cygwin), UEFI | Other targets          |
| ---------------------------------------------- | ----------------- | -------------------------- | ---------------------- |
| `short`, `signed short`, and their `int` forms | `i16`             | `i16`                      | `i16`                  |
| `unsigned short`, `unsigned short int`         | `u16`             | `u16`                      | `u16`                  |
| `int`, `signed`, `signed int`                  | `isize`           | `i32`                      | `i32`                  |
| `unsigned`, `unsigned int`                     | `usize`           | `u32`                      | `u32`                  |
| `long`, `signed long`, and their `int` forms   | `i32`             | `i32`                      | pointer-width signed   |
| `unsigned long`, `unsigned long int`           | `u32`             | `u32`                      | pointer-width unsigned |
| `long long` and its signed and `int` forms     | `i64`             | `i64`                      | `i64`                  |
| `unsigned long long`, `unsigned long long int` | `u64`             | `u64`                      | `u64`                  |
| `double`                                       | `f32` on AVR only | `f64`                      | `f64`                  |

"Pointer-width" means `i32` or `i64` for `long` and `u32` or `u64` for `unsigned long`, never `isize` or `usize`.

Parameters and results of type `i8`, `u8`, `i16`, `u16`, and `bool` receive the extension attributes the target's C ABI
requires, whether written directly or produced by `@CType`. On 64-bit RISC-V, LoongArch, and MIPS targets `i32`, `u32`,
and `char` are sign-extended as well, and on 64-bit PowerPC, SystemZ, and SPARC targets they are extended by their
signedness. Because `@CType("char")` is always `u8`, only values 0 through 127 agree with a signed plain C `char` across
a call. Variadic functions and by-value C aggregates do not have a general direct mapping, and neither does a function
with another calling convention, such as the Win32 API's `stdcall` on 32-bit x86 Windows. Tuples and slices use an `l`
language ABI, not a promised C struct ABI. Use a C-compatible wrapper when an interface contains unsupported forms.

Select a target when checking or compiling target-dependent code:

```sh
l check --target aarch64-unknown-linux-gnu monitor.l
l compile --target aarch64-unknown-linux-gnu --emit obj monitor.l monitor.o
```

Object emission can reject pointer-width integer literals, enum IDs, string lengths, or aggregates larger than the
address space that earlier syntax and type checking accepted, including in code a jump makes unreachable. The complete
layout and mapping rules are in [Machine and ABI Model](./spec.md#machine-and-abi-model) and
[Compile-Time Macros](./spec.md#compile-time-macros).

Next, [Compiler Workflow](#compiler-workflow) shows where checking, bundling, code generation, linking, and inspection
fit.

## Compiler Workflow

The shortest development loop is still the most useful:

```sh
l check monitor.l
l run monitor.l
```

Use the other commands when you need to inspect a phase, keep an artifact, cross-compile, or publish a program.

### Check before producing artifacts

`check` reads the entry file or directory, reduces it to MIR, checks it into LIR, and then reports lints over the parsed
source. Successful checking produces no file. The one lint, `identifier-underscore-limit`, warns about an identifier
holding more than one underscore; each finding names its source location. Lints are compiler guidance rather than
language rules, and their warnings do not change a successful exit status.

A source entry is a file or a directory. Naming `-` instead reads one source file from standard input, which is also
what `check` and `bundle` read when no entry is named.

Specify `--target` when target-dependent forms such as `@CType("int")` or `@sizeOf` affect checking:

```sh
l check --target x86_64-unknown-linux-gnu monitor.l
```

`check` does not perform the emitter's representability checks for pointer-width integer literals, enum IDs, string
lengths, or aggregate sizes. LLVM IR, bitcode, assembly, or object emission can still reject a checked source when those
do not fit the selected target, or when declarations conflict under [Linkage](./spec.md#linkage).

### Inspect the program at the useful level

When the module layout is the question, inspect the bundled source:

```sh
l bundle monitor
```

The output merges the entry directory's files, prints each subdirectory as a `namespace` block, and leaves every macro
as written, `use` lines included. It is canonical source, not a preservation of comments or original formatting.
`l bundle --header` projects the public interface of a library-shaped module, which is what a `use` of it sees. It
retains public statics as `pub static` declarations with their initializers removed and public function definitions as
bodyless `pub` declarations, inside `impl` blocks as well. Public declarations, aliases, and `use` lines remain, while
private functions, statics, and declarations are omitted.

This projection keeps namespace nesting and qualified paths; it does not record `--name` or rebase the interface when
placed elsewhere. A declaration without `extern` requests the symbol a definition in its place would have, so the header
reaches the definitions it was projected from only in the same place of a package of the same name. `use` arranges
exactly that: see [Use a library](#use-a-library). Saved as `api/api.l` under a consumer's entry directory instead, a
root `pub fn answer() i32;` sits at `api::answer` and requests `api__answer`, which the original object does not
provide. Check both name resolution and external symbol compatibility before reusing a generated header by hand; neither
is automatically adapted. See [Linkage](./spec.md#linkage) for how symbols are formed.

To inspect the frontend before macro expansion, emit the lexer token stream or the parser's high-level intermediate
representation (HIR):

```sh
l compile --emit ts monitor.l monitor.ts
l compile --emit hir monitor monitor.hir
```

The token stream includes whitespace and comments and prints one token and its source position per line; it describes
one file, so it refuses a directory. The HIR is the parser's pretty-printed tree of the whole package after those tokens
are discarded, with every macro and shorthand still in place. See
[Source Text and Tokens](./spec.md#source-text-and-tokens) for the lossless token model.

When implicit compiler decisions are the question, inspect the mid-level or low-level intermediate representation:

```sh
l compile --emit mir monitor.l monitor.mir.l
l compile --emit lir monitor.l monitor.lir.l
```

MIR is the reducer's output: every `@` macro and `Self` is expanded, and every shorthand is written out, including
result types, enum IDs, `else` blocks, jump operands, block results, short-circuit operators as conditionals, and
anonymous functions lifted to private `anonymousFunctionN` functions. LIR is the checker's output, the same constructs
annotated with their resolved types, names, and operations; its projection shows the constructs only, except that every
`let` is printed with its inferred type. Both print as parseable `l` source without source provenance, so they are for
inspection rather than serialization back into the compiler.
[Language Representations](./spec.md#language-representations) defines each level.

### Build at the right boundary

`build` compiles and links a host program in one step:

```sh
l build -o monitor monitor.l
```

`--shared` builds a shared library instead, and `--static` a static library; the two flags exclude each other, and
without either `build` produces an executable. A shared library needs position-independent objects, so `--shared`
compiles them that way on its own:

```sh
l build --shared -o libmonitor.so monitor.l
l build --static -o libmonitor.a monitor.l
```

`compile` stops before linking and can emit textual LLVM IR, LLVM bitcode, assembly, or an object:

```sh
l compile --emit ll monitor.l monitor.ll
l compile --emit bc monitor.l monitor.bc
l compile --emit asm monitor.l monitor.s
l compile --emit obj monitor.l monitor.o
```

`compile` and `build` optimize with LLVM's standard pipeline at level `2` unless `-O` selects another level, spelled as
Clang spells it: `0` disables optimization, `1` keeps compilation fast, `3` adds aggressive code-growing
transformations, `s` favors size, and `z` wants the smallest code even at the cost of speed. `run` and `install` always
use the default level. Emitted LLVM IR and bitcode show the optimized module, so inspect the compiler's own lowering at
`-O 0`:

```sh
l compile --emit ll -O 0 monitor.l monitor.ll
l build -O z -o monitor monitor.l
```

Optimization keeps every volatile access but may remove other work whose result the program never uses. A busy-wait loop
that only counts therefore has to count through a `volatile` reference; see
[Use volatile references for memory-mapped I/O](#use-volatile-references-for-memory-mapped-io). No level turns code into
calls to C library functions, such as a zeroing loop into `memset`, because a bare-metal image has no C library to
provide them. Operations without a native instruction still need runtime routines: floating `%` calls `fmod`, and 64-bit
division on a 32-bit target calls the compiler runtime.

Objects follow the target's default relocation model, which is position-dependent on most targets; some, such as macOS
and 64-bit Windows, always use position-independent code. Pass `--pic` to compile position-independent code when the
object is destined for a shared library:

```sh
l compile --emit obj --pic monitor.l monitor.o
```

Use `l link` when objects and libraries already exist:

```sh
l link -o monitor monitor.o support.a
```

`--entry SYMBOL` starts an executable at another symbol instead of through the C runtime, and `--script FILE` hands the
linker a GNU-style linker script; `build` accepts `--script` as well.

`compile` and `build` accept the package name the sources are compiled as, which namespaces every module-level symbol
the artifact defines. `--name` accepts one or more slash-separated name segments, without a revision suffix:

```sh
l compile --emit obj --name acme/monitor monitor.l monitor.o
```

Without `--name` the artifact carries no package namespace, which is what a program needs, because a namespaced `main`
is no longer the symbol the system runtime calls.

A program that uses libraries needs their code as well as its own. `check`, `compile`, `build`, `run`, and `install` all
take `-L DIR`, repeatable, to find each library's source. `build`, `run`, and `install` compile every library the
program uses, directly or through another library, as the package the library is named after, and link its object with
the program's own:

```sh
l build -L libs -o hello hello.l
```

`compile` produces only the program's own code. `--deps FILE` writes the libraries it has to be linked with, one name
per line, every library before the libraries it uses itself. Only output generated from checked code can list them, so
`--deps` refuses `--emit ts`, `hir`, and `mir`. Compiling each listed library under its own name and linking the objects
gives what `build` does in one step:

```sh
l compile -L libs --deps hello.deps hello.l hello.o
l compile --name macos libs/macos macos.o
l link -o hello hello.o macos.o
```

LLVM IR, bitcode, assembly, and object emission support targets with an LLVM code generator and a C scalar model
understood by `l`: ordinary 32-bit or 64-bit pointer targets, plus 16-bit AVR and MSP430. Lanai, SPIR, and SPIR-V
architectures are not supported.

`l link` always links for the host target. Use `l build --target TRIPLE` to compile and link for another supported
target. The linker supports x86, x86-64, AArch64, ARM, RISC-V 64, LoongArch 64, little-endian PowerPC 64, and s390x
Linux targets with supported GNU or musl environments, x86-64 and AArch64 macOS targets, and bare-metal ELF targets
whose triple names the `none` system, such as `thumbv7m-none-eabi`. Cross-linking a Linux or macOS program also requires
that target's runtime and development files.

### Build bare-metal firmware

A bare-metal target has no C runtime to start the program, so `build` links its image with `ld.lld` alone. A linker
script places the code and data in the device's memory and names the entry point:

```sh
l build --target thumbv7m-none-eabi --script linker.ld -o blink.elf main.l
```

The program and its script own everything a runtime would otherwise do: the vector table, the initial stack, copying
`.data` into RAM, and clearing `.bss`. The repository's `examples/stm32` program does all of it for a Cortex-M3
microcontroller. Linker scripts apply to every ELF executable or shared library; Mach-O linking rejects them.

Device programmers and boot loaders usually take a raw binary or an Intel HEX file rather than ELF. `objcopy` converts
an existing ELF image, and `build --format` converts the executable it links:

```sh
l objcopy --format bin -o blink.bin blink.elf
l build --target thumbv7m-none-eabi --script linker.ld --format ihex -o blink.hex main.l
```

A raw binary holds the loadable memory from the lowest load address on, while Intel HEX records carry their addresses.
Conversion requires `llvm-objcopy` on `PATH` and refuses input that is not ELF, such as a macOS executable.

`inspect` reports the size of every section of an ELF, Mach-O, or COFF binary:

```sh
l inspect blink.elf
```

Each section has a class: `text` for code and constants, `data` for writable memory whose initial contents the image
stores, `bss` for writable memory that starts zeroed, or `-` for a section no loaded program occupies, such as a symbol
table. The totals sum the classes as Berkeley `size` does, then add the Flash an image needs, `text + data`, and the
RAM, `data + bss`.

### WebAssembly object output

The emitter can produce a nonempty relocatable WebAssembly object for `wasm32-unknown-unknown`, including code that uses
the language's aggregate types:

```sh
l compile --target wasm32-unknown-unknown --emit obj app.l app.o
```

This is partial end-to-end WebAssembly support. The bundled linker supports only Linux, macOS, and bare-metal ELF, so
`l build --target wasm32-unknown-unknown` cannot complete a WebAssembly module. Use an external `wasm-ld` workflow and
supply any host imports or runtime support the program needs. The compiler does not currently provide `wasm-ld`
integration, host imports, runtime facilities, or JavaScript loading glue. WebAssembly object output is distinct from
the planned JavaScript source backend; it does not translate `l` programs into JavaScript.

### Generate and install

Generate API documentation from module items with:

```sh
l doc --output api monitor.l
```

The command can produce HTML or structured JSON. It currently documents canonical signatures and source organization,
including static visibility, exposed type, and declaration/definition status. Private functions and private statics are
included only with `--document-private-items`; `///` comment text is reserved but not retained yet.

Install a host program into the language environment with:

```sh
l install monitor.l
l where
```

`where` prints the selected environment root, and `install` places the executable in its `bin` directory.

### Locate and fetch packages

Packages live under the environment root in a fixed layout. Installed lookup and shorthand fetching accept exactly
`author/package`, optionally followed by `@tag`, rather than the two-or-more-segment names accepted by compiler
`--name`. Each name segment starts with an ASCII letter and continues with ASCII letters, digits, and interior hyphens.
A tag matches `[A-Za-z0-9][A-Za-z0-9._+-]*`. The specification selects these published files:

```text
$ROOT/packages/<author>/<package>/<tag>/src/                  entry point
$ROOT/packages/<author>/<package>/<tag>/<target>/<package>.a  linkable file
$ROOT/packages/<author>/<package>/<tag>/<target>/<package>.l  header
```

`where` reports those paths and fails when the file is not installed:

```sh
l where acme/monitor@v1.0.0
l where --lib acme/monitor@v1.0.0
l where --header acme/monitor@v1.0.0
```

A package name without `@` is looked up under the literal `latest` directory, not the highest installed version. Naming
a package without `--lib` or `--header` locates its entry point, the `src` directory. A linkable file and a header
describe one target each, so both are looked up under the host triple unless `--target` names another. A target
directory is named by the triple without the version its operating system carries, so `arm64-apple-darwin24.6.0` and
`arm64-apple-darwin` name one directory. Add `--print` to print the located file instead of its path; for the entry
point it prints the merged source, as `l bundle` does.

`fetch` retrieves a package from a git remote using a one-commit-deep checkout of the selected revision, not its
history:

```sh
l fetch acme/monitor
l fetch acme/monitor@v1.0.0
l fetch https://example.com/acme/monitor.git --dest vendor/monitor
```

An `author/package` source names a GitHub repository and is installed under the layout above. A git URL names only a
repository, so `--dest` has to say where it goes. `--dest` writes the package into that directory without any structure
of its own. Appending `@` and a tag, branch, or commit to either form selects a revision; without one the remote's
default branch is taken. Shorthand revisions must fit the tag grammar above. An untagged shorthand is installed under
`latest`, while explicitly writing `@latest` requests that git revision. These are explicit lookups and fetches, not
automatic version or dependency resolution. Fetching needs `git` on `PATH`.

### Diagnose the toolchain

The commands above lean on tools and files the compiler does not carry itself: `git` for fetching, the host's LLD driver
and C runtime for linking, the archive tool for static libraries, and `llvm-objcopy` for image conversion. `doctor`
checks each of them and says how to get whatever is missing:

```sh
l doctor
```

Every dependency prints one line with its status, and the command exits unsuccessfully when any of them is missing.

`interpret`, `lsp`, `package`, and `upload` name planned tools. They take no arguments yet, report that they are not
implemented, and exit unsuccessfully.

Use `l COMMAND --help` for the current options rather than treating this chapter as an exhaustive command reference.

### A C programmer's final checklist

| Expectation from C                   | `l` behavior                                                             |
| ------------------------------------ | ------------------------------------------------------------------------ |
| Integer operands promote             | Operand types must match; convert with `T(value)`                        |
| Literal suffixes such as `1u`        | Literals have no suffix and take their type from context                 |
| Variables name stored values         | `let` only binds values; aggregate projections expose storage slots      |
| Assignment can rebind a name         | Assignment stores data through a writable reference, never rebinds       |
| An immutable parent freezes children | Boxed children load their exact declared type, including a written `mut` |
| `&value` takes an address            | There is no address-of operator                                          |
| Arrays decay implicitly              | Fixed arrays are backing addresses; slices are explicit descriptors      |
| Ranges are usually half-open         | Slice ranges are half-open too: `values[1..3]` views two elements        |
| Structs copy by value                | Struct values carry backing addresses                                    |
| Function pointers are raw pointers   | Function references are a distinct callable type                         |
| Closures capture locals              | Anonymous functions do not capture                                       |
| Functions live only at file scope    | `impl T { ... }` places functions in a type's namespace, called `T::f()` |
| Scoping walks outward to parents     | Paths are relative to the current module; a leading `::` starts at root  |
| Enum variants are bare names         | A variant is constructed through its enum, as `Event::Offline`           |
| `else if` chains conditionals        | Bodies are blocks; write `else { if expression { ... } }`                |
| Raw pointers support arithmetic      | Use array or slice indexing; pointer arithmetic is absent                |
| `volatile` can model MMIO            | `volatile T` reads only; `mut volatile T` also permits volatile writes   |
| Headers declare foreign symbols      | `extern` declarations name C symbols by their bare names                 |
| `#include` pulls in a library's API  | `use name;` reads the library's interface as the namespace `name`        |

You now have the conceptual model needed to read and write `l`. Keep the
[Language Specification](./spec.md#the-l-programming-language-specification) nearby for exhaustive syntax, typing,
evaluation, ABI, diagnostics, and unsupported forms.