This guide shows how to add a language to lungo generate with a plugin, the way
protoc-gen-<language> programs add languages to protoc.
Write the program
A plugin reads a GenerateRequest (JSON) on standard input and writes a GenerateResponse
(JSON) on standard output. The protocol reference lists every
field. In Rust, the lungo-build crate provides the types:
use lungo_build::codegen::plugin::{GenerateRequest, GenerateResponse};
use std::io::Read;
fn main() {
let mut input = String::new();
std::io::stdin().read_to_string(&mut input).unwrap();
let request: GenerateRequest = serde_json::from_str(&input).unwrap();
let mut response = GenerateResponse::default();
for key in request.options.keys() {
response.errors.push(format!("unknown option `{key}`"));
}
if response.errors.is_empty() {
let mut api = String::new();
for f in &request.boundary.functions {
api.push_str(&format!("// {} : {}\n// entry point: {}\n", f.lean_name, f.lean_type, f.symbol));
}
response.files.insert("api.txt".into(), api);
// The program's C, which the package compiles and links against the runtime.
response.files.extend(request.program_files.clone());
}
println!("{}", serde_json::to_string(&response).unwrap());
}
A generator for a real language:
- copies
program_files: the package compiles every.cfile withprogram/on its include path, and links the runtime ofrequest.runtime(by release URL and SHA-256, or from the local distribution); - emits the language’s types for
boundary.tableandboundary.types, and a function per entry ofboundary.functionsthat encodes its arguments in the wire format, calls the entry pointsymbol, and decodes the result perreturns; - installs itself as the process’s host (
lungo_set_host) to receive calls of host functions, gives each facility ofboundary.facilitiesan installer that registers the implementation of each of its operations withset_host_extern(by the operation’sindex), and refuses a call before every facility is installed, naming the facility and an operation; - for a function whose
returnsisasync, calls the entry point for the program’s first step and drives it: asks the caller’s handler to perform each operation (of the async facility inboundary.async_facilitieswith that operation type), resumes the program with the answer (lungo_async_resume), and gives it up (lungo_async_cancel) when the handler fails or the call is cancelled (see the wire format); - if the language can expose it, embeds the program’s assurance document
(
request.assurance) exactly as lungo serializes it. lungo writesassurance.jsoninto every output itself; a plugin whose response contains that file fails.
The support libraries of the built-in languages (lungo-go,
lungo-py, lungo-ts
and lungo-swift) are complete examples of the
runtime side, and compiler-tests/wire/vectors.json in lungo-rs tests a codec.
Install and run it
Put the program on PATH as lungo-gen-<language> (.exe on Windows):
cargo install --path my-plugin # installs lungo-gen-kotlin
lungo generate --kotlin_out=gen/kotlin --kotlin_opt=package=org.example
or configure it in lungo.toml:
[plugins.kotlin]
out = "gen/kotlin"
options = { package = "org.example" }
The output directory is replaced by the response’s files, with assurance.json added. A
plugin that fails, writes a malformed response or reports errors fails the command with
LNG0504, showing its standard error or its errors;
nothing is written.