# Chapter 1: Getting started

This chapter gets you from an empty directory to a running native Loup program. You will install the toolchain, write a program with a `main()` function, run it with `loup run`, and then build it into a native executable with `loup build --target native`. Along the way you will meet the first two mistakes almost everyone makes when moving from scripts to native programs.

Native Loup is written in one of two modes. A file that starts with `@mode(host.native)` (extension `.lphc`) is compiled ahead of time to a platform binary. A file that starts with `@mode(host.dual)` (extension `.loup`) follows the same rules but can be delivered either way: run by the interpreter, compiled to WebAssembly, or compiled to a native binary. Chapter 2 explains when to choose which. Most examples in this book use `host.native`.

## Installing Loup

Official builds of Loup are published on the `code-craft-ai-company/loup-releases` GitHub page. The installer picks the archive for your machine, checks it against the release's signed `SHA256SUMS`, unpacks it into `~/.loup` (or `%LOCALAPPDATA%\Programs\Loup` on Windows), and puts its `bin` directory on your `PATH`. No administrator rights are needed.

```sh
# Linux and macOS
curl -fsSL https://github.com/code-craft-ai-company/loup-releases/releases/latest/download/loup-install.sh | sh
```

```powershell
# Windows (PowerShell)
irm https://github.com/code-craft-ai-company/loup-releases/releases/latest/download/loup-install.ps1 | iex
```

To pin a version, pass it to the script (`sh -s -- --version 1.25.1`) or set `LOUP_VERSION` before running the installer. Then check the installation:

```sh
loup --version
```

What you can do with native code depends on the platform you are on:

| Platform | `loup` is | Native binaries (`--target native`) |
|---|---|---|
| Linux x86-64 | the self-hosted toolchain (plus `loup-bootstrap`) | Supported; needs `clang` installed |
| macOS 11+ (Apple silicon, Intel) | the bootstrap compiler, `loup-bootstrap` | Preview: LLVM backend only; needs the Xcode Command Line Tools |
| Windows 10+ x86-64 | the bootstrap compiler | Not yet |
| Linux arm64 | the bootstrap compiler | Not yet |

Every platform can *run* native-mode programs with `loup run`, which executes them on Loup's interpreter. That is how every listing in this book was checked, so you can follow the whole book on any platform and build binaries where the toolchain supports it.

## Hello, world

Create a file named `hello.lphc`:

```loup
@mode(host.native)

func main(): void {
    println("hello, native world");
}
```

*Listing 1-1: A native program*

Run it:

```sh
loup run hello.lphc
```

```text
hello, native world
```

Two lines in this file are new if you are coming from Loup scripts.

`@mode(host.native)` declares the file's mode. It must be the first token in the file; only comments may come before it. A directive further down is `E_MODE_POSITION`, and a second directive is `E_MODE_DUPLICATE`. Every source file should declare its mode (ruling MODE-2), and the extension follows the mode (ruling MODE-3): `.lphc` for `host.native`, `.loup` for `host.dual`.

`func main(): void` is the entry point. In a script, the file's top-level statements run in order. In `host.native` and `host.dual` there is no top-level execution: the runtime calls `main()` once the module is initialized, whether the program is a native binary or is being run by `loup run` (Language Reference §1-2.4.5, rulings ENTRY-1 and MODE-5).

## Mistake: code at the top level

If you write a native file the way you would write a script, the compiler stops you:

```loup,does_not_compile
@mode(host.native)

println("hello, native world");
```

```text
main.lphc:3:1: error[E_TOPLEVEL_CONTROL_AOT]: E_TOPLEVEL_CONTROL_AOT: top-level control flow is not allowed in `host.native` — only declarations and constant initializers are permitted at module scope; the runtime invokes `main()` after module initialization (§1-2.4)
```

A native binary has no "run the file from top to bottom" step, so statements outside a function have nowhere to run. Only declarations (functions, classes, structs, enums, imports) and constant initializers may appear at module scope. Move the statement into `main()`, as in Listing 1-1.

Diagnostics from `loup run` have the shape `file:line:column: error[CODE]: message`, one per line, and the process exits with status 2. The spec's error code usually appears twice: once in the brackets and again at the start of the message.

## Mistake: an old mode name

Earlier editions of this book used `@mode(native)`. That name was retired in v1.17, but the toolchain does not reject it: a file with an unknown or retired mode name is quietly treated as a `host.scripted` script. A script does not call `main()` by itself, so the program prints nothing and exits with status 0:

```sh
$ loup run hello.lphc      # the file says @mode(native)
$ echo $?
0
```

If a program's `main()` never seems to run, check the mode line first. The current names are `host.native`, `host.dual`, `host.scripted`, `web.scripted`, `web.compiled` and `freestanding`.

## Arguments and exit status

`main` takes no parameters. Command-line arguments come from `runtime.args()`, and if `main` returns `int`, that value becomes the process exit status:

```loup
@mode(host.native)

func main(): int {
    let args = runtime.args();
    if (args.length() > 1) {
        printErr("usage: greet [NAME]");
        return 2;
    }
    var name = "world";
    if (args.length() == 1) {
        name = args[0];
    }
    println(i"hello, {$ name $}");
    return 0;
}
```

*Listing 1-2: Reading arguments and returning an exit status*

```sh
$ loup run greet.lphc
hello, world
$ loup run greet.lphc Ada
hello, Ada
$ loup run greet.lphc Ada Grace
usage: greet [NAME]
$ echo $?
2
```

`i"…{$ expr $}…"` is an interpolated string; a plain `"…"` string is never interpolated. `printErr` writes to standard error.

A C or Rust programmer's first instinct is to declare the arguments as a parameter:

```loup,does_not_compile
@mode(host.native)

func main(args: list[string]): int {
    println(args);
    return 0;
}
```

```text
main.lphc:3:11: error[E_MAIN_SIGNATURE]: E_MAIN_SIGNATURE: `main` takes no parameters — command-line arguments come from `runtime.args()` (§1-2.4.5, ruling MAIN-1)
```

Nothing passes `main` arguments, and only an `int` result is an exit status, so `main` must take no parameters and return `void` or `int` (ruling MAIN-1). The exit status rules are the same everywhere `main` is called (ruling EXIT-1):

- `return n` from an `int main` exits with `n`'s low byte (`263` exits 7, `-1` exits 255);
- a `void main` exits 0;
- an uncaught exception exits 1;
- a compile error exits 2;
- `exit(code)` ends the process at once with `code`.

## Building a native binary

`loup run` is for development. To produce an executable, build a project. `loup build` reads a package manifest, a TOML file named `<name>.lppkg`, so it always runs inside a project directory.

Create a project with the bootstrap compiler's `init` command:

```sh
loup-bootstrap init hello
cd hello
```

This writes `hello/hello.lppkg`, an empty `tests/` directory, and `src/main.loup`, a `host.dual` program:

```loup
@mode(host.dual)

func main(): void {
    println("hello, hello");
}
```

*Listing 1-3: The scaffold's `src/main.loup`*

A `host.dual` program can be built into a native binary as it is. For a `host.native` project, rename the file to `src/main.lphc`, change its first line to `@mode(host.native)`, and point the manifest at it:

```toml
name = "hello"
version = "0.1.0"
loup = "1.20.0"

[build]
entry = "src/main.lphc"
delivery = "native"
profile = "release"
```

*Listing 1-4: The `[build]` table of a native project*

`delivery` names what the build produces (`--target` on the command line is the same setting, and `target` is accepted in the manifest as its older spelling). The scaffold's `delivery = "cli"` builds WebAssembly; `"native"` builds a platform executable. Now build and run:

```sh
loup build --target native -o hello
./hello
```

```text
hello, hello
```

On Linux x86-64 this produces an ELF executable linked against the system C library; on macOS (preview) a Mach-O executable linked by Apple's `clang` and `ld64`. Nothing else needs to be installed on the machine that runs it: the Loup runtime (reference counting, strings, collections, exceptions) is compiled into the binary.

`loup run` and the native binary run the same program, but not on the same engine. `loup run` uses an interpreter, so a few runtime behaviours differ until the implementations converge. The book points these out where they come up; the first one you might notice is that an uncaught exception prints the exception and a stack trace under `loup run`, but a native binary prints a single line, such as `ValueError: bad input`, on standard error. Both exit with status 1.

## Build options you will use

`loup build --help` lists every option. These are the ones that matter for native programs:

| Option | Meaning |
|---|---|
| `--target native` | Build a platform executable (overrides the manifest's `delivery`) |
| `-o PATH` | Where to write the executable |
| `--release`, `--debug` | Shorthands for `--profile release` / `--profile debug` |
| `--backend llvm\|cranelift\|llbe` | Code generator; `llvm` is the default |
| `--lto thin\|full` | Link-time optimization (LLVM backend only) |
| `--mode MODE` | Project-wide mode for files that do not declare one |
| `--no-cache`, `--clean` | Force a full rebuild / delete the `.loup-cache` directory |

The LLVM backend writes LLVM IR and compiles it with `clang`, which is why Linux needs `clang` installed and macOS needs the Xcode Command Line Tools. Cranelift writes ELF objects only, so it is refused on macOS:

```text
error[E-BUILD-NATIVE]: --backend cranelift writes ELF objects, which a macOS host cannot link; use the default LLVM backend (`--backend llvm`)
```

Chapter 24 covers the backends, cross-compilation, library output, and distribution in detail.

## Checking a project without running it

`loup-bootstrap check` type-checks a whole project without building or running it, and prints diagnostics with source snippets:

```sh
loup-bootstrap check
```

It works on projects only: run it on a lone file and it stops with `E-CHECK-MANIFEST`. For single files, `loup run` reports the same compile errors (in the one-line format shown above) before it runs anything.

## Summary

- Native Loup files declare `@mode(host.native)` (`.lphc`) or `@mode(host.dual)` (`.loup`) as their first token.
- Execution starts in `main()`, which takes no parameters and returns `void` or `int`; arguments come from `runtime.args()`. Statements at the top level are `E_TOPLEVEL_CONTROL_AOT`.
- `loup run file.lphc` runs a native-mode program on the interpreter on every platform; the book's listings are checked this way.
- `loup build --target native -o app` builds an executable from a project with a `.lppkg` manifest. Linux x86-64 is supported (needs `clang`); macOS is a preview on the LLVM backend; Windows and Linux arm64 cannot build native binaries yet.
- A retired mode name such as `@mode(native)` is not an error: the file silently runs as a script and `main()` is never called.
- Chapter 2 explains what the two native modes allow and forbid, and how they compare to Loup's other modes.
