# FixtureLang 0.1 language reference

FixtureLang describes finite, reproducible sets of typed rows. Generation is independent of the system clock and local timezone. The language has no executable JavaScript, natural-language prompts or external services.

## Program shape

```fixture
fixture example seed 0 {
  table people rows 4 {
    id: id = seq(10);
    name: string = text("Person ");
    active: bool = bool(50);
  }
}
```

The fixture name, table names and field names are case-sensitive identifiers. The explicit seed is required, including when all fields use nonrandom generators. Tables run in declaration order; rows and fields retain their declaration order. A table may have zero rows, but must declare at least one field. Every field ends in `;`.

Whitespace separates tokens. `#` starts a comment until the next line ending, except inside a quoted string. Names match `[A-Za-z_][A-Za-z0-9_]*` and have at most 64 characters. Keywords are contextual: a field named `missing` is valid. Names within each table, and table names within a fixture, must be unique.

## Grammar

`+` means one or more, `*` zero or more, and `|` alternatives. Punctuation in quotes is literal. `EOF` requires the end of input; trailing declarations are rejected.

```ebnf
program   = "fixture" IDENT "seed" UINT "{" table+ "}" EOF ;
table     = "table" IDENT "rows" UINT "{" field+ "}" ;
field     = IDENT ":" TYPE "=" generator modifier* ";" ;
TYPE      = "id" | "int" | "string" | "bool" | "date" ;
generator = "seq"  "(" INTEGER ")"
          | "int"  "(" INTEGER "," INTEGER ")"
          | "bool" "(" UINT ")"
          | "pick" "(" STRING ("," STRING)* ")"
          | "text" "(" STRING ")"
          | "date" "(" STRING "," STRING ")"
          | "ref"  "(" IDENT "." IDENT ")" ;
modifier  = ("missing" | "null" | "duplicate") "every" UINT ;
```

`INTEGER` is an optional minus followed by decimal digits; `UINT` disallows minus. Multi-digit leading zeros, `-0`, fractions, exponents and numbers outside the JavaScript safe-integer range are rejected. The seed is more narrowly bounded to `0..4294967295`.

`STRING` follows JSON double-quoted string syntax, including `\"`, `\\`, `\n`, `\r`, `\t`, `\b`, `\f`, `\/` and `\uXXXX`. Literal control characters are forbidden inside strings. Strings must contain valid Unicode scalar values, so unpaired surrogate escapes are rejected. A string literal may contain at most 256 UTF-16 code units after decoding. Empty strings are valid. There is no string interpolation.

## Types and generators

| Type | Allowed generators | Result |
| --- | --- | --- |
| `id` | `seq(start)`, `ref(table.field)` | Safe-integer relation value; uniqueness is not enforced |
| `int` | `seq(start)`, `int(min, max)` | Safe integer |
| `string` | `pick("a", "b", …)`, `text("prefix")` | Unicode string |
| `bool` | `bool(percent)` | JSON boolean |
| `date` | `date("YYYY-MM-DD", "YYYY-MM-DD")` | ISO date string |

No type is inferred or coerced. For example, `price: string = int(0, 9);` is a validation error.

- `seq(start)` produces `start + rowIndex`, with a zero-based row index. The checker validates the entire table's range before generation. Negative starts are permitted.
- `int(min, max)` includes both endpoints. Both endpoints must be safe integers, `min <= max`, and `max - min + 1 <= 2^32`.
- `bool(percent)` takes an integer in `0..100`. The chance of true is expressed in whole percentage points. Even `bool(0)` and `bool(100)` consume a random draw.
- `pick(...)` takes 1–32 quoted strings. Selection is uniform over list positions. Repeated choices therefore add weight.
- `text(prefix)` appends the one-based row number. `text("item-")` gives `"item-1"`, `"item-2"`, etc. This generator consumes no random draw.
- `date(start, end)` selects an inclusive UTC calendar day. Bounds must be real Gregorian dates, spelled exactly `YYYY-MM-DD`, in years `0001..9999`, and ordered. Leap days are validated. Outputs do not use local timezone arithmetic.
- `ref(table.field)` selects a final, present, non-null value from an earlier table's `id` field generated by `seq`. A `ref` field cannot itself be a relation target. Forward and self references are rejected. A nonempty child table requires at least one eligible parent value. An empty child table may refer to an empty eligible set.

Relations sample parent rows with equal weight, retaining duplicate IDs in that pool. If a parent ID occurs in three rows, it has three times the weight of an ID occurring in one row. Parent IDs removed by mutations are excluded. Relations do not repair duplicate or otherwise intentionally damaged parent data.

## Deliberate faults

All `every N` rules count rows starting at 1 and fire on rows divisible by `N`.

```fixture
fixture faults seed 9 {
  table people rows 6 {
    id: id = seq(1) duplicate every 3;
    email: string = pick("person@example.test") missing every 2;
    nickname: string = pick("Pat") null every 3;
  }
}
```

Here IDs are `[1, 2, 2, 4, 5, 5]`. Email is absent from rows 2, 4 and 6. Nickname is explicitly `null` on rows 3 and 6. JSON can preserve all these cases.

| Modifier | Preconditions | Effect |
| --- | --- | --- |
| `missing every N` | Integer `N >= 1` | Omits the field property |
| `null every N` | Integer `N >= 1` | Writes explicit `null` |
| `duplicate every N` | Integer `N >= 2`; type `id`; generator `seq` | Copies the previous row's final value |

A field can have at most one missing/null rule and at most one duplicate rule. Repeating a modifier is an error. `missing` and `null` together are an error. Modifiers never change the number of rows.

The base generator runs first, even when its result will be omitted or replaced. Duplicate applies next; current-row missing/null applies last, regardless of the textual order of modifiers. When the previous final ID is missing or null, duplication copies that state. For example, `seq(1) duplicate every 2 missing every 3` yields `1, 1, missing, missing, 5, missing` for six rows. This detail makes faults composable and deterministic.

## Determinism contract

FixtureLang 0.1 uses one Mulberry32 state initialized from the unsigned 32-bit seed. Generators consume it in table order, then row order, then field order. `seq` and `text` consume no draws. `int`, `bool`, `pick`, `date` and `ref` consume exactly one draw per generated row, including degenerate ranges, deterministic probabilities and subsequently mutated fields. Selection maps a draw in `[0,1)` through `floor(draw * span)`.

The standard 32-bit Mulberry32 transition is:

```js
state = (state + 0x6d2b79f5) >>> 0;
let t = state;
t = Math.imul(t ^ (t >>> 15), t | 1);
t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
const draw = ((t ^ (t >>> 14)) >>> 0) / 4294967296;
```

The same source and version produce the same ordered data and export bytes. Changing whitespace or comments does not change generated data. Changing the seed, table/field order, random generator count, choices or modifiers may change results. Row extension can change later tables because they share the random stream. This is a fixture generator, not a cryptographic source or an independent-per-field random model. An algorithm change requires a documented language version change.

## Fixed budgets

| Resource | Maximum |
| --- | ---: |
| Source length | 65,536 UTF-16 code units |
| Lexer tokens | 20,000 |
| Identifier length | 64 ASCII characters |
| Decoded string literal | 256 UTF-16 code units |
| Tables | 8 |
| Fields in one table | 32 |
| Rows in one table | 10,000 |
| Rows across all tables | 20,000 |
| Generated field cells | 200,000 |
| Choices in one `pick` | 32 |
| Generated string units | 8,000,000 UTF-16 code units |

Cell cost is `sum(tableRows * declaredFields)`; missing/null mutations do not reduce it. The string-unit budget counts generated base strings before omission or null mutations, including `pick`, `text` and `date` values. There are no source-controlled increases to these budgets. Resource violations report `LIMIT` with a source position. Cancellation reports `CANCELLED` through the library API. The playground can terminate the worker immediately.

## JSON and CSV

JSON exports the whole fixture:

```json
{
  "fixture": "example",
  "seed": 0,
  "tables": {
    "people": [{"id": 10, "name": "Person 1", "active": true}]
  }
}
```

The illustration shows one row; real exports include all completed rows. CSV exports one selected table with field names as the header, CRLF record endings, and doubled quotes inside quoted cells. Commas, quotes, CR and LF trigger quoting. Missing, null and empty strings all appear as empty cells; JSON is necessary to retain the distinction.

`raw` mode preserves string payloads exactly. `spreadsheet` mode prepends `'` to string cells or headers beginning with tab, CR or LF, or with optional whitespace followed by `=`, `+`, `-` or `@`. Unicode whitespace is included before formula markers. Negative integer values remain numeric and are not prefixed. The protected-cell count includes protected headers. This is an explicit transformation and never mutates the generated dataset. Spreadsheet behavior varies; the mode does not promise universal protection after later edits, re-exports or format conversions.

## Diagnostics

Syntax and semantic errors have a stable machine-readable code and a readable message with a 1-based line and UTF-16 column. The UI shows the offending source line and offers to focus that position. A malformed program produces no partial downloadable dataset. Errors stop at the first failure; this prototype does not attempt multi-error recovery.
