vyrn

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.

// 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.

// 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
}
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.

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