# gen fns and generators

A `gen fn` is ordinary Vyrn that runs while the program compiles and returns Vyrn source. RPC, UI, i18n, OpenAPI and GraphQL are libraries built this way. None of them is a compiler feature.

## gen fns

A generator is a function marked `gen`. It returns a `String` of Vyrn source. It may read files through `readFile` and `listDir`, scoped to the paths its caller passed in, and it may not write, use module state, or reach an `extern`.

This one writes a lookup table. It takes no input, so its answer is the same on every machine — which is the property a build has to have.

There is no run link on this block. A generator is not a program: it needs the file that imports it, and the playground takes one source.

```vyrn
// A `gen fn` is ordinary Vyrn that runs while the program compiles and returns
// Vyrn SOURCE. The compiler links what it returns as a module. This one writes a
// lookup table nobody has to keep up to date.
export gen fn squares() -> String {
    let mut out = "/// Written by the `squares` generator while the program compiled.\n"
    out = out + "export fn squareOf(n: Int64) -> Int64 {\n"
    let mut i = 1
    while i <= 9 {
        out = out + "    if n == \{i} { return \{i * i} }\n"
        i = i + 1
    }
    return out + "    return 0\n}\n"
}
```

## Generator imports

The target of an import can be a call to a generator. The compiler runs the call at load time, caches it, and links the source it returned as an ordinary module. Everything after that is normal Vyrn.

`vyrn emit-gen <file>` prints the source a generator produced.

This page is built the same way. A generator reads `std/` and writes the [reference pages](https://vyrn-lang.github.io/docs.html).

```vyrn
// An import target can be a call to a generator. `squares()` runs at compile
// time and `squareOf` arrives from the source it returned — an ordinary
// function, in an ordinary module.
import { squares } from "./gentiny"
import { squareOf } from squares()
import { joinWith } from "std/strings"

export fn demo() -> String {
    return joinWith(["squareOf(3) is \{squareOf(3)}", "squareOf(9) is \{squareOf(9)}"], "\n")
}

fn main() -> Int64 {
    print(demo())
    return 0
}
```

Built output:

```
squareOf(3) is 9
squareOf(9) is 81
```

## Reflection

`schemaOf()` reads a validated type's own declaration. It answers with the name, the base type, the documentation line and every bound the `where` clause implies. `jsonSchema()` writes the JSON Schema document from the same facts.

This is not runtime reflection. Vyrn erases types before the program runs; the compiler computes the answer from the declaration.

```vyrn
// `schemaOf` and `jsonSchema` read a type's own declaration at compile time. The
// rule is written once, in the type, and the document follows from it.
import { joinWith } from "std/strings"

/// The port to listen on.
type Port = Int64 where value >= 1 && value <= 65535

fn bound(o: Option<Int64>) -> String {
    return match o {
        Some(n) => n.toString(),
        None => "none",
    }
}

export fn demo() -> String {
    let s = schemaOf<Port>()
    return joinWith([
        "name    \{s.name}",
        "base    \{s.base}",
        "minimum \{bound(s.min)}",
        "maximum \{bound(s.max)}",
        "schema  \{jsonSchema<Port>()}",
    ], "\n")
}

fn main() -> Int64 {
    print(demo())
    return 0
}
```

Built output:

```
name    Port
base    Int64
minimum 1
maximum 65535
schema  {"$schema":"https://json-schema.org/draft/2020-12/schema","type":"integer","minimum":1,"maximum":65535}
```

---

From https://vyrn-lang.github.io/guide/generators.html — generated by `site/export.vyrn` from the Vyrn repository.
