# Prime Core v0.1 — Specification

Status: draft · 2026-11-01
Scope: the buildable core of Prime (see `site/index.html`, https://primelang.dev, for the full language vision).

Prime Core is the first implemented subset of Prime. It is made for business logic that
applications let their users or their own teams add without rebuilding: reports first.
`.prime` files are compiled to bytecode and run inside **PrimeVM**, a sandboxed virtual
machine written in Rust that an application embeds and connects to its data (§11, §18).

Everything in Prime Core is valid Prime. Features of the full language that are not part of
the core (§17) keep their keywords reserved, so later versions extend the core without
breaking existing scripts.

---

## 1. Design goals

1. **Safe by construction.** A script can only compute and call the host functions it is
   given. No file, network, process, reflection or `eval` access exists in the language.
2. **Customisable without a rebuild.** Reports are plain source files; adding or overriding
   a report needs no rebuild of the application that runs them.
3. **Fast enough for a year of sensor data.** Data is streamed through cursors; the VM is
   register-based (design inherited from KANJI).
4. **Machine-writable.** A strict context-free grammar and machine-readable errors (§14) so
   our own local model can generate and self-correct scripts (full Prime §5).
5. **Deterministic.** The same script, inputs and host data always produce the same output.

---

## 2. Source files and modules

- Source files use the extension `.prime` and are UTF-8 encoded.
- Each file starts with exactly one module declaration, followed by imports, followed by
  class declarations.

```prime
module weather.reports.daily_rainfall;

import std.math;
import std.time;
import demo.weather;
```

- `std.*` modules are part of PrimeVM (§10). Other imported modules are **host modules**
  supplied by the embedding application (§11). Importing an unknown module is a compile error.
- A module may import other modules from the same project (e.g. a shared
  `weather.reports.common`). Cyclic imports are a compile error.

---

## 3. Lexical structure

### 3.1 Whitespace and comments
Spaces, tabs, CR and LF separate tokens. Comments: `// to end of line` and `/* block */`
(block comments do not nest).

### 3.2 Identifiers
`[A-Za-z_][A-Za-z0-9_]*`. Identifiers are case-sensitive. Non-ASCII letters are not allowed
in identifiers (they are allowed in string literals).

### 3.3 Keywords (core)
```
module import pub priv static class void var const
int float string boolean date datetime duration list map
if else while for break continue return
true false null new
```

**Contextual keywords** — `select from where order by asc desc top` are keywords only inside
a `select` expression (§7); everywhere else they are ordinary identifiers, so a field may be
called `from` (`ctx.period.from`). After `.` any word, including a keyword, is a valid member
name (`xs.map`).

### 3.4 Reserved for later versions (using them is a compile error)
```
prob confidence tensor infer classifying as qstate
async await thread wait nowait transaction
```

### 3.5 Literals
| Kind | Examples | Notes |
|---|---|---|
| int | `0`, `42`, `1_000_000`, `0x1F` | 64-bit signed |
| float | `0.5`, `3.0e-4`, `1_234.75` | 64-bit IEEE 754; a `.` or exponent is required |
| string | `"Пробег"`, `"a\tb\n"`, `"\u{1F697}"` | escapes: `\" \\ \n \r \t \u{…}` |
| boolean | `true`, `false` | |
| null | `null` | only for optional types (§4.3) |
| date | `d"2026-10-31"` | ISO date |
| datetime | `dt"2026-10-31T06:37:00"` | local time of the data (see §11.4) |
| duration | `5m`, `90s`, `2h`, `1d`, `250ms` | int followed by unit |

### 3.6 Operators and punctuation
```
+ - * / %   == != < <= > >=   && || !   ?:   =  += -= *= /= %=   ++ --
( ) [ ] { } , ; . : @ ?
```

---

## 4. Types

### 4.1 Primitive types
| Type | Values | Default |
|---|---|---|
| `int` | 64-bit signed integer; overflow is a runtime error | `0` |
| `float` | 64-bit float | `0.0` |
| `string` | immutable UTF-8 text | `""` |
| `boolean` | `true` / `false` | `false` |
| `date` | calendar date | — (must be initialised) |
| `datetime` | date + time of day, second precision | — |
| `duration` | signed span, millisecond precision | `0s` |

No implicit conversions except `int → float` in arithmetic with a `float` operand and in
assignment to a `float` variable. All other conversions are explicit (`float(x)`, `int(x)`
truncates toward zero, `string(x)`).

### 4.2 Collections
- `list<T>` — ordered, growable. Literal: `[1, 2, 3]`; empty: `list<int> xs = [];`
- `map<K, V>` — K must be `int`, `string` or `date`. Literal: `{"a": 1, "b": 2}`.
  Iteration order is insertion order.

### 4.3 Optional types
`T?` may hold `null`. Host functions that may have no value return `T?` (e.g. a sensor
without a reading). A `T?` must be checked before use as `T`:

```prime
float? fuel = p.sensor("_fuelLevel");
if (fuel != null) {
    total += fuel;            // fuel is narrowed to float inside this block
}
float level = fuel ?: 0.0;    // default operator
```

### 4.4 Classes
Classes are records with fields and methods. No inheritance and no interfaces in v0.1.

```prime
pub class Stop {
    pub datetime start;
    pub duration length;
    pub string   address;

    pub boolean isLong() {
        return length > 30m;
    }
}

Stop s = new Stop(start: dt"2026-10-31T10:29:00", length: 10m, address: "");
```

- `new C(field: value, …)` initialises fields by name; omitted fields take their defaults
  (an omitted field without a default is a compile error).
- Objects are reference values; the VM's garbage handling is reference counting with cycle
  collection at the end of a run (§13.3).
- `static` methods belong to the class; instance methods receive `this` implicitly.
- `pub` members are visible to other modules; `priv` (the default) only inside the module.

### 4.5 Local type inference
`var` declares a local variable whose type is inferred from its initialiser:
`var days = period.days();`. Fields, parameters and return types are always explicit.

### 4.6 Constants
`const float KM_PER_MILE = 1.609344;` — module- or class-level, compile-time evaluated.

---

## 5. Statements

```prime
float total = 0.0;                  // declaration (initialiser required)
total += km;                        // assignment: = += -= *= /= %=
count++;  count--;                  // same as count += 1, count -= 1 (also ++count)
if (km > 500.0) { … } else if (…) { … } else { … }
while (i < n) { … }
for (int i = 0; i < n; i++) { … }             // C-style loop
for (date day : period.days()) { … }          // for-each over list or cursor
break; continue; return expr;
out.row(day, metric("rainfall", mm, Unit.MM)); // expression statement (calls only)
```

`++` and `--` are statements, not operators: they apply to a variable, field or element
and cannot appear inside an expression (`print(i++)` is a compile error), so an expression
never changes a variable as a side effect.

Blocks `{ … }` introduce a scope. Shadowing a variable of an enclosing scope in the same
method is a compile error (keeps generated code unambiguous).

---

## 6. Expressions

Precedence, highest first:

| Level | Operators | Assoc. |
|---|---|---|
| 1 | `.` member, `()` call, `[]` index | left |
| 2 | unary `-` `!` | right |
| 3 | `*` `/` `%` | left |
| 4 | `+` `-` | left |
| 5 | `<` `<=` `>` `>=` | none |
| 6 | `==` `!=` | none |
| 7 | `&&` | left (short-circuit) |
| 8 | `\|\|` | left (short-circuit) |
| 9 | `?:` (default for optionals), `c ? a : b` | right |

- `+` on two strings concatenates; `string + non-string` is a compile error (use `string(x)`
  or `Str.format`).
- Integer division truncates; division or modulo by zero is a runtime error.
- `date ± duration` and `datetime ± duration` give the same type; `datetime - datetime`
  gives a `duration`; comparisons work within each temporal type.

---

## 7. Queries (from KANJI)

Reports mostly walk through one subject's data for a period. Prime Core keeps KANJI's
built-in query statement, compiled to cursor instructions (§13.5):

```prime
for (Reading r : select from readings(station, period)
                 where r.valid && r.wind > 3.0
                 order by r.time asc
                 top 100000) {
    …
}
```

- The `from` operand must be a host **data source** (§11.2) — a call that returns a
  `cursor<T>`. Plain lists use ordinary loops.
- `where`, `order by` and `top` are optional. The compiler hands the parts it can express to
  the host (filter on fields, sort, limit), so the database does the work; anything else is
  evaluated in the VM. The result is identical either way.
- Rows are streamed in batches (default 1 000); a year of readings never sits in memory.
- A `select` without `for` is a compile error; collect explicitly if a list is needed
  (`list<Reading> xs = []; for (… : select …) { xs.add(r); }`).

---

## 8. Annotations

```
annotation = "@" Identifier [ "(" arg { "," arg } ")" ] ;
arg        = Identifier "=" literal ;
```

Core annotations:

| Annotation | On | Meaning |
|---|---|---|
| `@Report(id = "…", title = "…", mobile = true\|false, customer = "…")` | class | declares a report (§12); `mobile` and `customer` are passed to the host, which decides where the report is offered |
| `@Param(name = "…", type = "…", default = "…")` | class (repeatable) | extra report parameter |
| `@TestSpecification` | class | test class (§15) |
| `@Test(timeout = 500)` | method | test method, timeout in ms |
| `@Fixture(source = "readings", file = "…json")` | test method (repeatable) | host data for the test |

Unknown annotations are a compile error.

---

## 9. Program entry

A report is a class annotated with `@Report` that has exactly one method

```prime
pub static void run(ReportContext ctx)
```

`ReportContext` (§12.1) gives access to the report's subject, period, parameters and output. Library
modules (no `@Report`) contain shared classes and static helpers.

A program that is not a report is a class `Main` with `pub static void main()`; `prime run`
and the playground run the project's one `Main.main`. It writes its results with `print`
(§10). A `main` that returns a value instead has the value shown after its output.

---

## 10. Standard library (built into PrimeVM)

No module of the standard library performs I/O or reads the clock.

- `std.core` — `print(value)`, available in every file without an import. It appends the
  value's text (strings as they are, other values as `string(x)` shows them, lists and
  maps in brackets) as one line of the run's *output*: a buffer returned to whoever ran
  the code, never a terminal, file or network. `prime run` and the playground show it,
  `prime test` shows it under each test, and for a report it is a debugging log — never
  rows of the report. `print` returns `void`. Output written before a runtime error is
  kept.

- `std.math` — `Math.abs, min, max, round(x, digits), floor, ceil, sqrt, pow, clamp`;
  constants `Math.PI`, `Math.E`. `round` (and `{0:0.00}` in `Str.format`) rounds half away
  from zero after pre-rounding to 15 significant digits, so `round(1.005, 2)` is `1.01` —
  what business code usually expects (and what PHP's `round()` gives).
- `std.text` — `Str.len, substring, contains, startsWith, split, join, trim, toUpper,
  toLower, replace, pad`; `Str.format("{0} mm in {1}", mm, d)` with `{n:0.00}` number formats.
- `std.time` — `Date.of(y, m, d)`, `d.addDays(n)`, `d.dayOfWeek()`, `d.startOfWeek()`,
  `d.startOfMonth()`, `dt.date()`, `dt.time()`, `Duration.hours(x)`, `dur.totalHours()`,
  `Time.format(value, "dd.MM.yyyy")`.
- `std.collections` — methods on `list` (`add, size, get, set, remove, sort, sortBy,
  contains, first, last`) and `map` (`get` → `V?`, `put, has, keys, values, size`).
- `std.assert` — `Assert.equals, isTrue, isFalse, isNull, near(expected, actual, eps)`;
  only usable inside test classes.

---

## 11. Host modules

PrimeVM itself knows no domain. The embedding application registers host modules: types,
functions and data sources with fixed signatures. Host modules are declared in **JSON**
(the built-in `std.*` modules use the same format), so an application can describe what it
provides without changing PrimeVM:

```json
{ "module": "demo.weather",
  "classes": [ { "name": "Station", "fields": [ { "name": "id", "type": "int" } ] } ],
  "functions": [ { "name": "readings", "source": true, "returns": "cursor<Reading>",
                   "params": [ { "name": "station", "type": "Station" }, { "name": "period", "type": "Period" } ] } ] }
```

Type texts use Prime syntax plus the pseudo-types `any`, `number` (int|float), `quantity`
(int|float|duration) and the return type `num_result` (int if all numeric arguments are int,
else float). Methods may be `static`, `test_only`, and a last parameter may be `variadic`.
Host fields are read-only for scripts; host classes cannot be created with `new`.

A host function marked `"source": true` returns `cursor<T>` and is a **data source**: it can
only be used in `select … from` (§7), so the host can push filters, order and limits down
to its database. Other host functions are called like static functions.

Examples, tests and this specification use `demo.weather` (`hosts/demo.weather.json`), a
fictional weather-station network: `Station`, `Reading` (time, position, wind, direction,
temperature, `sensor(key)`), the data sources `readings`, `rainEvents` and `calibrations`,
and functions such as `rainfall(station, day)` and `sunshine(station, day)`.

### 11.1 Report profile
A host that offers reports declares these classes and functions in one of its modules; the
fixture host of `prime test` (§15) implements them for tests:

```prime
pub class Period   { pub date from; pub date to; pub static Period of(date from, date to);
                     pub list<date> days(); pub list<date> weeks(); pub list<date> months(); }
pub class ReportContext {              // see §12.1
    pub Station station;               // the subject: named and typed by the host
    pub Period period; pub datetime runAt; pub ReportOutput out;
    pub string param(string name); pub string t(string key);
}
pub class ReportOutput { row(any label, Metric... m); rowOf(any label, list<Metric> m); total(Metric... m); }
pub class Metric { }
pub class Unit { … }                   // constants: the host's closed set of unit codes
Metric metric(string key, any value, Unit unit);
Metric text(string name, string? value, string color);
pub class Reports { pub static test_only ReportResult run(string id, int station, date from, date to); }
pub class ReportResult { float total(string key); int rowCount(); string label(int row);
                         float value(int row, string key); string? text(int row, string key); }
```

A report runs for one **subject** — a station, a customer, a machine — chosen by the host:
the second parameter of `Reports.run` names it (`station`) and `ReportContext` has a field
of that name whose class has an `int id`. Everything else in the profile is fixed.

### 11.2 Time
All `date`/`datetime` values are local times of the data. `now()` does not exist; the host
passes the run time in `ctx.runAt` so runs are reproducible.

---

## 12. Report output

### 12.1 `ReportContext`
```prime
pub class ReportContext {
    pub Station station;                  // the subject (§11.1)
    pub Period  period;
    pub string  param(string name);       // values of @Param parameters
    pub string  t(string key);            // a text in the run's language
    pub datetime runAt;
    pub ReportOutput out;
}
```

### 12.2 Rows, totals, metrics
```prime
ctx.out.row(day, metric("rainfall", mm, Unit.MM));         // one row per day/item
ctx.out.row("North pier", metric("gusts", 3, Unit.PCS));   // label may be text
ctx.out.total(metric("rainfall", total, Unit.MM));         // totals section
ctx.out.rowOf(day, metrics);                               // a row with a list of metrics
text(task.task, ctx.t("status_done"), "green")             // literal name and text value
```

- `metric(key, value, unit)` — `key` is a translation key, never display text; the host
  turns keys into display names in the user's language.
- `text(name, value, color)` is a metric whose name and value are shown as given (a place
  name, a status from `ctx.t(key)`), with an optional color (`""` for none).
- `ctx.t(key)` translates a text key into the run's language.
- `Unit` is a closed set of codes declared by the host, which renders each code in the
  user's language.
- `int`, `float` and `duration` values stay typed in the run's output; how they are shown
  (decimals, locale, wire format for clients) is up to the host.

---

## 13. Compilation and PrimeVM

### 13.1 Pipeline
`.prime` → lexer → parser (AST) → name resolution and type checking → register allocation
→ bytecode (`.primevm`) → PrimeVM. The compiler and the VM are the same Rust library; the host
can compile at startup and cache `.primevm` files keyed by source hash.

### 13.2 Machine model (inherited from KANJI)
- **Register-based**, like KANJI's operand-style instructions (`MOV dst, src`,
  `ADD dst, a, b`), not a pure stack machine: fewer instructions per statement.
- One **frame** per call: up to 256 registers, the function's constant pool, return address.
  (KANJI's separate result/param/condition stacks become ordinary registers.)
- **Dispatch**: a `match` over `enum Op` (KANJI's `HANDLERS[]` table).
- **Values**: a tagged enum — `Null, Int, Float, Bool, Str, Date (days), DateTime (seconds),
  Duration (ms), List, Map (insertion-ordered), Obj (script class), Host (class + opaque
  handle)`. No raw memory offsets or pointers (unlike KANJI), so scripts cannot read or
  corrupt VM memory. Host values are handles the host resolves; the VM never looks inside.
- **Frames** are explicit (no Rust recursion): deep script recursion hits the call-depth
  limit, never the native stack.

### 13.3 Memory
Reference counting; cycles are collected when a run ends (runs are short). The VM counts
allocated bytes against the memory limit (§16).

### 13.4 Instruction set (v0.1)
Operands: `r` register (frame-relative, ≤ 255 per function), `k` constant, `f` function,
`b` built-in, `h` host import, `@` absolute jump target. `prime disasm` prints this form.

| Group | Instructions | KANJI origin |
|---|---|---|
| Load/move | `LOADK r k`, `LOADNULL r`, `LOADBOOL r b`, `MOV r r` | MOV |
| Arithmetic | `ADD SUB MUL DIV REM r r r`, `NEG r r` — checked; int overflow is `E0402` | ADD SUB MUL DIV MOD NEG |
| Compound | compiled to the same ops with `dst = lhs` | MOVA SUBA MULA … |
| Compare | `EQ NE LT LE GT GE r r r` (result boolean) | CMP |
| Logic | `NOT r r`; `&&`, `\|\|`, `?:` and `c ? a : b` compile to jumps | AND OR |
| Jumps | `JMP @`, `JMPT r @`, `JMPF r @`, `JMPNN r @` (jump if not null) | JMP JE JNE … |
| Conversions | `I2F r r` (inserted where an int meets a float slot), `CONV r r type` | — |
| Text | `CONCAT r r r` | — |
| Calls | `CALL r f base n` (script), `CALLB r b base n` (std library), `CALLH r h base n` (host function, method, field or constant), `RET r`, `RETV` | CALL RET |
| Objects | `NEW r class`, `GETF r r field`, `SETF r field r`, `GETS`/`SETS` (statics) | NEW, field offsets |
| Collections | `NEWLIST r`, `NEWMAP r`, `GETI r r r`, `SETI r r r` | — |
| Output | `PRINT r` (`print`, §10: one line of the run's output) | — |
| Cursors | `QOPEN r h base n order`, `QNEXT r r @` (next row or jump when done), `QCLOSE r` | SQL_SELECT, SQL_MNXT, SQL_JEOF |

Arguments of a call occupy consecutive registers starting at `base` (receiver first for
methods). Report output (`ctx.out.row`, `ctx.out.total`) is an ordinary host method call.

### 13.5 Query compilation
`for (Reading r : select from readings(s, per) where W order by O top N) { B }` becomes:

```
QOPEN   rc, h:readings, args…, order "time:desc"    ; order pushed down to the host
loop:
QNEXT   rp, rc, done          ; next row into rp, or jump to done
<W → JMPF … loop>             ; filter runs in the VM (v0.1)
<B>
<count ≥ N → JMP done>        ; top runs in the VM
JMP     loop
done:
QCLOSE  rc
```

`order by` is limited to fields of the row variable (`E0317` otherwise). Cursors still open
when a function returns or fails are closed by the VM.

### 13.6 `.primevm` file format (compiled bytecode)
```
magic "PRIMEVM" | format version u8 (2) | flags u16 (bit 0: encrypted, reserved)
source fingerprint (32 bytes) | file names | constant pool | class table
| function table (code, register count, line table) | host imports | built-ins | reports
```
Integers are LEB128 varints. Host imports (module, class, name, kind) are checked against the
host at load time (`Host::link`); built-ins are resolved by name, so an older VM refuses a
module that needs a newer std library instead of misbehaving.
Optional encryption (AES-256-GCM, key provided by the host) will protect proprietary report
code shipped to customers or devices — the successor of `KANJICryptographer`.

---

## 14. Errors (machine-readable, for people and our model)

Compile and runtime errors are JSON objects:

```json
{
  "phase": "type",
  "code": "E0305",
  "file": "reports/daily_rainfall.prime",
  "line": 14, "column": 21,
  "message": "cannot add 'string' and 'float'",
  "hint": "convert with string(mm) or use Str.format"
}
```

`phase` is one of `lex, parse, resolve, type, test, runtime, limit`. Error codes are stable
across versions so a model can learn to fix them (full Prime §5.3).

---

## 15. Tests and admission

```prime
@TestSpecification
pub class DailyRainfallTests {

    @Test(timeout = 500)
    @Fixture(source = "rainfall", file = "fixtures/rainfall_2026-10-29.json")
    pub void oneRowPerDayAndRoundedTotal() {
        ReportResult r = Reports.run("daily_rainfall", station: 1, from: d"2026-10-29", to: d"2026-10-31");
        Assert.equals(3, r.rowCount());
        Assert.equals("2026-10-29", r.label(0));
        Assert.near(12.35, r.value(0, "rainfall"), 0.001);
        Assert.near(112.35, r.total("rainfall_total"), 0.001);
    }
}
```

### 15.1 Running tests
- Each `@Test` method runs on a fresh object of its class in a fresh VM (statics reset),
  with the sandbox limits of §16 and its `timeout` (default 2 000 ms, `E0504`).
- `Reports.run(id, station, from, to)` runs the report inside the same VM for the subject
  with that id (§11.1; the test host creates it with default field values) and returns a
  `ReportResult`: `rowCount()`, `label(row)`, `value(row, key)`, `total(key)` (numbers;
  durations in hours). Failures inside the report point at the report's source line.
- Host data comes only from the test's `@Fixture` files; a host function or data source
  without a fixture fails with `E0405`. There is no database or network access in tests.
- `prime test --host … <folder>` runs them; `--json` prints one result per line.

### 15.2 Fixture files
`file` is relative to the folder of the test's source file and may not leave it (`..`,
absolute paths: `E0207`). The test host converts JSON by the host declarations (§11), so a
wrong field name or type is an `E0602` naming the row and field.

- **Data source** (`readings`): an array of rows, or `{"rows": [...]}`. Every declared
  field is required (optional ones may be omitted); unknown keys are errors.
  `date` is `"YYYY-MM-DD"`, `datetime` `"YYYY-MM-DDTHH:MM:SS"`, `duration` milliseconds or
  `"1h30m"`. A row-class method reads the row key of the same name — directly when it has
  no parameters, otherwise from an object keyed by its first argument:
  `"sensor": {"pm10": 41.5}` serves `r.sensor("pm10")` (a missing key is `null`
  for an optional result). Rows outside a `Period` argument (by the row's first `datetime`
  field) are skipped; `order by` is applied by the host.
- **Host function** (`rainfall`): `{"calls": [{"args": [...], "result": ...}], "default": ...}`.
  Arguments match by value; a record matches by its `id` (`7` or `{"id": 7}`); `"*"`
  matches anything. Unmatched calls use `default`, or fail.

```json
{ "calls": [ { "args": [1, "2026-10-29"], "result": 12.345 },
             { "args": [1, "*"],          "result": 0.0 } ] }
```

The report profile itself (§11.1: `ReportContext`, `Period.days/weeks/months`,
`ReportOutput`, `metric`, `text`, `Unit`, `@Param` defaults through `ctx.param`) is built
into the test host.

### 15.3 Admission rule
The host loads a report only if the project compiles, the report's folder has at least one
test, and **all tests in that folder pass**. A failing new version never replaces a working
one. `prime test` prints the verdict per report and exits with 1 if any report is not
admitted.

`prime run --report <id> --subject <id> --from <date> [--to <date>] --fixture source=file`
runs a report against fixtures and prints its output as JSON — the basis for comparing a
report with another implementation of it.

---

## 16. Sandbox limits (per run, set by the host)

| Limit | Default | When exceeded |
|---|---|---|
| Instructions ("fuel") | 200 000 000 | `E0501` |
| Memory (live heap bytes, checked every 1 024 instructions) | 128 MB | `E0502` |
| Wall time | 120 s (host-enforced) | run cancelled |
| Call depth | 256 | `E0503` |
| Output of `print` | 10 000 lines or 1 MB | `E0502` |
| Test timeout | `@Test(timeout)`, default 2 000 ms | `E0504` |
| Expression nesting (compile time) | 64 levels | compile error `E0108` |
| Rows per cursor batch | 1 000 | — (streaming) |

There is no way for a script to change its limits.

---

## 17. Not in Prime Core v0.1 (keywords reserved)

`prob<T>` and `confidence`, `tensor<…>`, `infer()` / `classifying` / `as`, `qstate` and the
quantum API, `async` / `thread` / `wait` / `nowait` (KANJI threads), transactions and data
modification, inheritance and interfaces, exceptions (`try`).

---

## 18. Embedding

### 18.1 Rust crates
```
primelang/
├── crates/prime-syntax   lexer, parser, AST, spans, error JSON
├── crates/prime-check    resolution, type checking
├── crates/prime-vm       compiler to .primevm, VM, std library, limits
├── crates/prime-test     fixture host, test runner, admission rule
├── crates/prime-wasm     wasm32 build of compiler + VM with the host ABI below
└── crates/prime-cli      prime check | parse | build | run | test | disasm
```

### 18.2 Wasm host ABI (for embedding applications, e.g. Go through wazero)
`crates/prime-wasm`, built for `wasm32-wasip1` (WASI supplies the clock for timeouts).
Requests and responses are JSON; values cross as tagged JSON (`{"i": 5}`, `{"f": 1.5}`,
`{"d": days}`, `{"t": seconds}`, `{"ms": n}`, `{"h": handle, "c": "Class"}`, strings, lists).

Exports: `prime_alloc(len) → ptr`, `prime_free(ptr, len)`,
`prime_load(ptr, len) → (ptr << 32) | len` (compile `{"sources", "hosts", "fixtures",
"test"}`; returns reports, imports, `main` (the project's one `module.Main.main`, or
`null`), test results and admission),
`prime_run(ptr, len) → (ptr << 32) | len` (run `{"report", "context": handle, "fuel",
"memory", "timeout_ms"}`, or `{"function": "module.Class.method", …}` to call a function
without arguments, which answers `{"ok": true, "value": result as text or null}`). Both
answer `"output"`, the lines written with `print`, also when they fail. The host must
give WASI its real monotonic clock, or `timeout_ms` does not measure time.

Imports (module `prime_host`, implemented by the embedding application): `call(import, args_ptr, args_len) → i64`,
`cursor_open(import, args_ptr, args_len, order_ptr, order_len) → i64`,
`cursor_next(cursor) → i64` (a row, or `null` at the end), `cursor_close(cursor)`,
`take(ptr)`. A call returns the length of its result, negative for an error message; the
VM then fetches the bytes with `take`. `import` indexes the import list from `prime_load`.
All host objects live in the application; the VM only holds their handles.

---

## 19. Complete example — `examples/daily_rainfall.prime`

```prime
module weather.reports.daily_rainfall;

import std.math;
import demo.weather;

@Report(id = "daily_rainfall", title = "report.daily_rainfall", mobile = true)
pub class DailyRainfall {

    pub static void run(ReportContext ctx) {
        float total = 0.0;
        for (date day : ctx.period.days()) {
            float mm = Math.max(0.0, rainfall(ctx.station, day));
            ctx.out.row(day, metric("rainfall", Math.round(mm, 2), Unit.MM));
            total += mm;
        }
        ctx.out.total(metric("rainfall_total", Math.round(total, 2), Unit.MM));
    }
}
```

Its tests are in `examples/daily_rainfall_test.prime` (§15). The title and metric keys
(`report.daily_rainfall`, `rainfall`, `rainfall_total`) are translated by the host.

---

## 20. Milestones

1. **M1 — Front end:** lexer, parser, AST, error JSON; `prime check` on the example files.
2. **M2 — Types:** resolution and type checking (optionals, classes, collections, temporal).
3. **M3 — VM:** compiler to bytecode, register VM, std library, limits; `prime run` with a
   stub host.
4. **M4 — Tests:** `@TestSpecification`, fixtures, `prime test`, admission rule.
5. **M5 — Wasm (done):** `prime-wasm`, PrimeVM embedded by other applications through the
   JSON ABI of §18.2.
6. **M6 — First production embedding (done):** an external application runs its reports on
   PrimeVM, with its own host module, tests and admission.

---

## Appendix A — Error codes

Codes are stable: a code never changes meaning.

| Code | Phase | Meaning |
|---|---|---|
| E0001 | lex | unexpected character |
| E0002 | lex | unterminated string |
| E0003 | lex | word reserved for a later version |
| E0004 | lex | invalid number |
| E0005 | lex | invalid escape sequence |
| E0006 | lex | unterminated block comment |
| E0007 | lex | invalid date / datetime literal |
| E0101 | parse | unexpected token (missing `;`, `)`, …) |
| E0102 | parse | missing module declaration |
| E0103 | parse | invalid assignment target |
| E0104 | parse | expected an expression |
| E0105 | parse | expected a type |
| E0106 | parse | chained comparison (`a < b < c`) |
| E0107 | parse | expected a member name after `.` |
| E0108 | parse | expression nested too deeply |
| E0201 | resolve | unknown name |
| E0202 | resolve | unknown type |
| E0203 | resolve | unknown module |
| E0204 | resolve | unknown member |
| E0205 | resolve | duplicate definition |
| E0206 | resolve | variable shadows an enclosing one |
| E0207 | resolve | invalid or unknown annotation |
| E0208 | resolve | `break` / `continue` outside a loop |
| E0209 | resolve | `select` or a data source used outside `for (… : select from …)` |
| E0210 | resolve | instance member used from a static context |
| E0211 | resolve | test-only API (`Assert`, `Reports.run`) outside a test class |
| E0212 | resolve | report without `pub static void run(ReportContext ctx)` |
| E0213 | resolve | duplicate report id |
| E0214 | resolve | private class or member used from another module |
| E0215 | resolve | ambiguous name (defined in several imported modules) |
| E0301 | type | type mismatch |
| E0302 | type | wrong arguments (count, unknown or repeated name) |
| E0303 | type | not callable |
| E0304 | type | optional value may be null |
| E0305 | type | invalid operands for an operator |
| E0306 | type | condition is not boolean |
| E0307 | type | missing return |
| E0308 | type | invalid return (value in void method or missing value) |
| E0309 | type | cannot infer a type (`var x = null`, empty list) |
| E0310 | type | expression statement has no effect |
| E0311 | type | cannot assign (constant, read-only host field) |
| E0312 | type | invalid `new` (positional values, unknown or missing field, host type) |
| E0313 | type | value is not iterable |
| E0314 | type | invalid conversion |
| E0315 | type | constant is not computable at compile time |
| E0316 | type | function needs more than 255 registers (split it) |
| E0317 | type | `order by` on something other than a field of the row variable |
| E0401 | runtime | division or remainder by zero |
| E0402 | runtime | integer overflow or number out of range |
| E0403 | runtime | null value used |
| E0404 | runtime | list index out of range |
| E0405 | runtime | host error (host function failed, missing fixture data, …) |
| E0406 | runtime | conversion failed (`int("12x")`) |
| E0407 | runtime | invalid value for the operation |
| E0501 | limit | instruction budget exhausted |
| E0502 | limit | memory limit exceeded |
| E0503 | limit | call depth limit exceeded |
| E0504 | limit | time limit exceeded (test timeout, host wall time) |
| E0601 | test | assertion failed |
| E0602 | test | fixture file missing or invalid |
