In questa sezione

Questa guida mostra come aggiungere un linguaggio a lungo generate con un plugin, nello stesso modo in cui i programmi protoc-gen-<language> aggiungono linguaggi a protoc.

Scrivi il programma

Un plugin legge una GenerateRequest (JSON) dallo standard input e scrive una GenerateResponse (JSON) sullo standard output. Il riferimento del protocollo elenca ogni campo. In Rust, il crate lungo-build fornisce i tipi:

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());
}

Un generatore per un linguaggio reale:

  1. copia program_files: il pacchetto compila ogni file .c con program/ nel percorso degli include, e collega il runtime di request.runtime (tramite URL della release e SHA-256, oppure dalla distribuzione locale);
  2. emette i tipi del linguaggio per boundary.table e boundary.types, e una funzione per ogni voce di boundary.functions che codifica i suoi argomenti nel formato di trasmissione, chiama il punto di ingresso symbol e decodifica il risultato secondo returns;
  3. si installa come host del processo (lungo_set_host) per ricevere le chiamate delle funzioni dell’host, dà a ogni facility di boundary.facilities una funzione che la installa registrando l’implementazione di ciascuna sua operazione con set_host_extern (tramite l’index dell’operazione), e rifiuta una chiamata precedente all’installazione di ogni facility, indicando la facility e una sua operazione;
  4. per una funzione il cui returns è async, chiama il punto di ingresso per ottenere il primo passo del programma e lo porta avanti: chiede all’handler del chiamante di eseguire ogni operazione (della facility asincrona di boundary.async_facilities con quel tipo di operazioni), riprende il programma con la risposta (lungo_async_resume) e lo abbandona (lungo_async_cancel) quando l’handler fallisce o la chiamata viene annullata (vedi il formato di trasmissione);
  5. se il linguaggio può esporlo, incorpora il documento di garanzia del programma (request.assurance) esattamente come lungo lo serializza. lungo scrive assurance.json in ogni output da sé; un plugin la cui risposta contiene quel file fallisce.

Le librerie di supporto dei linguaggi integrati (lungo-go, lungo-py, lungo-ts e lungo-swift) sono esempi completi del lato runtime, e compiler-tests/wire/vectors.json in lungo-rs verifica un codec.

Installalo ed eseguilo

Metti il programma nel PATH come lungo-gen-<language> (.exe su Windows):

cargo install --path my-plugin   # installa lungo-gen-kotlin
lungo generate --kotlin_out=gen/kotlin --kotlin_opt=package=org.example

oppure configuralo in lungo.toml:

[plugins.kotlin]
out = "gen/kotlin"
options = { package = "org.example" }

La directory di output viene sostituita dai file della risposta, con l’aggiunta di assurance.json. Un plugin che fallisce, scrive una risposta malformata o segnala errori fa fallire il comando con LNG0504, mostrando il suo standard error o i suoi errori; non viene scritto nulla.