primate 0.1.3

A small DSL for cross-language constants. Write once, generate typed Rust, TypeScript, and Python.
Documentation
# Plugin protocol

primate's built-in generators (Rust, TypeScript, Python) are part of
the binary, but they implement the same protocol every plugin does:
**JSON request on stdin, JSON response on stdout, errors on stderr.**

You can plug in any executable on `PATH` (or a script path) as a
generator by referencing it from `primate.toml`.

## Wire format

primate spawns the plugin and writes a single JSON request to its
stdin, then closes stdin. The plugin reads stdin, generates whatever
it generates, and writes a single JSON response to stdout.

### Request

```json
{
  "version": 1,
  "outputPath": "scripts/generated/constants.lua",
  "options": { "naming": "camelCase" },
  "modules": [
    {
      "namespace": "limits",
      "sourceFile": "constants/limits.prim",
      "doc": null,
      "constants": [
        {
          "name": "TIMEOUT",
          "doc": "How long the gateway waits before bailing.",
          "type": { "kind": "duration" },
          "value": { "duration": { "nanoseconds": 30000000000 } },
          "source": { "file": "constants/limits.prim", "line": 4, "column": 10 }
        }
      ]
    }
  ],
  "enums": [...],
  "aliases": [...]
}
```

Top-level fields:

- `version` — protocol version. Currently `1`.
- `outputPath` — the `output` value from the relevant `[generators.*]`
  section of `primate.toml`, relative to project root.
- `options` — every other key from that `[generators.*]` section,
  passed through verbatim as a JSON object.
- `modules` — one per namespace; carries that namespace's constants.
- `enums`, `aliases` — top-level enum and type-alias definitions.

### Response

```json
{
  "files": [
    {
      "path": "scripts/generated/constants.lua",
      "content": "-- generated by primate\n...",
      "mappings": [
        { "symbol": "limits::TIMEOUT", "line": 7, "column": 1 }
      ]
    }
  ],
  "errors": []
}
```

- `files` — one or more output files. The plugin can emit multiple
  files; primate writes each. (Most plugins emit just one.)
- `mappings` — symbol → generated-file location, used to populate the
  sourcemap so the LSP can jump from `.prim` to generated code.
- `errors` — non-empty array signals failure. Each entry has `message`
  and an optional `source` (file/line/column).

## Type and value JSON shapes

Type expressions are tagged unions:

```json
{ "kind": "u32" }
{ "kind": "string" }
{ "kind": "array", "element": { "kind": "u32" } }
{ "kind": "fixed_array", "element": { "kind": "u32" }, "length": 3 }
{ "kind": "map", "key": { "kind": "string" }, "value": { "kind": "u32" } }
{ "kind": "tuple", "elements": [ ... ] }
{ "kind": "optional", "inner": { "kind": "string" } }
{ "kind": "enum", "name": "LogLevel", "namespace": "logging" }
{ "kind": "alias", "name": "Port", "namespace": "network" }
```

Values are untagged (each shape has a uniquely-typed payload):

```json
8                                                      // integer
3.14                                                   // float
true                                                   // bool
"hello"                                                // string
{ "nanoseconds": 30000000000 }                         // duration
[1, 2, 3]                                              // array / fixed-array / tuple
{ "key": value, ... }                                  // map (untagged JSON object)
null                                                   // optional, none case
{ "variant": "Info", "value": 1 }                      // enum
```

Plugins typically deserialize into language-native types and re-serialize
to the target syntax.

## Wiring up a plugin

In `primate.toml`:

```toml
[[output]]
generator = "lua"
path      = "scripts/generated/constants.lua"
command   = "/usr/local/bin/primate-lua"        # absolute or on $PATH
options.naming = "camelCase"
```

Or, if your plugin is a script in the project:

```toml
[[output]]
generator = "lua"
path      = "scripts/generated/constants.lua"
command   = ["python", "scripts/primate_lua.py"]
```

`path` can be a single file or a directory — that's up to your
plugin. The plugin decides how many files to emit and what their
relative paths are; it's purely a convention between you and your
generator.

primate runs the command with stdin/stdout/stderr piped, sends the
request, waits for the response, and writes whatever the response says.

## See also

- [Writing a generator]./writing-a-generator.md — worked example.
- [`primate build`]../cli/build.md — invokes plugins.