Skip to content

The engine (Rust) ​

Today, generators are defined and run by phony-core, a Rust workspace; the phony CLI (train / generate / info / validate / stats, plus the package commands install / add / remove / update / list) wraps it for the command line. (Language libraries — PHP first, then JS/Python — are planned and will read the same portable artifacts; this page documents the engine you can use now.)

phony-core has two crates:

CrateRoleAPI
phony-pgdlthe generator layer — the envelope, registry, the core generators, PEL, composition, packages, the locale resolverphony-pgdl API
ngram-corethe N-gram engine — the Model generator (train + generate + the .ngram format)ngram-core API

phony-pgdl calls into ngram-core for model-type generation.

Install ​

Until published to crates.io, use a path dependency:

toml
# Cargo.toml
phony-pgdl  = { path = "../phony-core/crates/pgdl" }
ngram-core = { path = "../phony-core/crates/ngram-core" }
serde_json = "1"

Quick start — call a generator ​

Define a generator, then invoke it. Definition and invocation are separate steps.

rust
use std::sync::Arc;
use phony_pgdl::{compile_generator, generate, register_composition, InMemoryAssets, Registry};
use ngram_core::NgramModel;
use serde_json::json;

// 1. Assets — the models/lists the `model`/`list` generators look up by name.
let assets = InMemoryAssets::new()
    .with_model("person.first_names", NgramModel::train_words(&["mehmet", "ayse", "mustafa", "zeynep"], 3));

// 2. Registry — built-ins + assets + (optionally) a frozen clock. Register any
//    data-defined (composition) generators you want available.
let mut registry = Registry::with_builtins()
    .with_assets(Arc::new(assets))
    .with_reference_time(1_704_067_200); // 2024-01-01T00:00:00Z
register_composition(&mut registry, &json!({
    "name": "@demo:user_email",
    "let": { "first": { "type": "model", "source": "person.first_names", "generation": { "mode": "word" } } },
    "body": "{{ lowercase(first) }}@example.com"
})).unwrap();

// 3. Invoke it — one value, or many. Byte-identical for the same seed.
let def = compile_generator(&json!({ "use": "@demo:user_email", "unique": true })).unwrap();
let emails = generate(&def, &registry, /* seed */ 2026, 100).unwrap(); // 100 unique emails (Vec<Value>)

Entry points ​

FunctionProduces
generate_one(def, registry, seed, index)one value for invocation index
generate(def, registry, root_seed, count)count values (honours unique/nullable)
compile_generator(json)a per-type PGDL def → the uniform GeneratorDef envelope
register_composition(registry, json)register a data-defined (Tier 1) generator
load_package / load_package_dirregister a .phony package's generators + assets

Assets ​

Generators never embed data — they resolve it from an AssetProvider:

rust
pub trait AssetProvider {
    fn model(&self, name: &str) -> Option<Arc<NgramModel>>;
    fn list(&self,  name: &str) -> Option<Arc<serde_json::Value>>;
}

The frozen clock ​

now()/today()/age() and relative date ranges resolve against an injected reference instant, never the wall clock:

rust
let registry = Registry::with_builtins().with_reference_time(1_704_067_200);

So (definition, seed, index, reference-time) fully determines the output — see How generation works.

The portable contract ​

Everything cross-runtime — the SplitMix64 PRNG, FNV-1a hashing, seed derivation, PEL semantics, generator output, and the n-gram train → serialize → generate pipeline — is pinned by a frozen conformance corpus (phony-core/conformance/vectors.json). A language port (PHP, JS, Python, …) is correct when it reproduces every vector byte-for-byte; any engine drift fails CI.

Build & test (contributing) ​

bash
cargo test                  # whole workspace
cargo clippy -p phony-pgdl
cargo bench -p ngram-core   # engine throughput

Phony Cloud — Documentation & Specification