telltale-runtime 17.0.0

Choreographic programming for Telltale - effect-based distributed protocols
Documentation
//! Generate Rust effect interfaces and simulator scaffolds from Telltale DSL declarations.

use std::env;
use std::fs;
use std::path::PathBuf;

use telltale_language::{generate_effect_interface_scaffold, generated_effect_families};
use telltale_runtime::compiler::parser::parse_choreography_str;

const DEFAULT_OUT_DIR: &str = "target/effect_handler_scaffold";
const DEFAULT_WITH_SIMULATOR: bool = true;

#[derive(Debug, Clone)]
struct ParsedArgs {
    out_dir: String,
    dsl_path: Option<String>,
    with_simulator: bool,
    help: bool,
}

fn main() {
    if let Err(error) = run() {
        eprintln!("error: {error}");
        std::process::exit(1);
    }
}

fn run() -> Result<(), String> {
    let args: Vec<String> = env::args().skip(1).collect();
    let parsed = parse_args(&args)?;
    if parsed.help {
        print_help();
        return Ok(());
    }

    let out_dir = PathBuf::from(&parsed.out_dir);
    let dsl_path = parsed
        .dsl_path
        .as_ref()
        .ok_or_else(|| "missing required `--dsl path/to/protocol.tell`".to_string())?;
    let dsl_src =
        fs::read_to_string(dsl_path).map_err(|e| format!("failed to read '{dsl_path}': {e}"))?;
    let choreography =
        parse_choreography_str(&dsl_src).map_err(|e| format!("failed to parse DSL: {e}"))?;
    let families = generated_effect_families(&choreography);
    if families.is_empty() {
        return Err("DSL does not declare any effect interfaces".to_string());
    }

    let generated = generate_effect_interface_scaffold(&out_dir, &families, parsed.with_simulator)?;
    for path in generated {
        println!("generated: {}", path.display());
    }

    Ok(())
}

fn parse_args(args: &[String]) -> Result<ParsedArgs, String> {
    let mut parsed = ParsedArgs {
        out_dir: DEFAULT_OUT_DIR.to_string(),
        dsl_path: None,
        with_simulator: DEFAULT_WITH_SIMULATOR,
        help: false,
    };

    let mut i = 0;
    while i < args.len() {
        let consumed = parse_arg_token(&mut parsed, args, i)?;
        i += consumed;
    }

    Ok(parsed)
}

fn parse_arg_token(parsed: &mut ParsedArgs, args: &[String], idx: usize) -> Result<usize, String> {
    let token = args[idx].as_str();
    match token {
        "-h" | "--help" => {
            parsed.help = true;
            Ok(1)
        }
        "--out" => {
            parsed.out_dir = required_flag_value(args, idx, "--out")?.to_string();
            Ok(2)
        }
        "--dsl" => {
            parsed.dsl_path = Some(required_flag_value(args, idx, "--dsl")?.to_string());
            Ok(2)
        }
        "--with-simulator" => {
            parsed.with_simulator = true;
            Ok(1)
        }
        "--no-simulator" => {
            parsed.with_simulator = false;
            Ok(1)
        }
        _ if token.starts_with('-') => Err(format!("unknown flag: {token}")),
        _ => Err(format!(
            "unexpected positional argument: {token}; use --out <dir> and --dsl <file>"
        )),
    }
}

fn required_flag_value<'a>(args: &'a [String], idx: usize, flag: &str) -> Result<&'a str, String> {
    args.get(idx + 1)
        .map(std::string::String::as_str)
        .ok_or_else(|| format!("missing value after {flag}"))
}

fn print_help() {
    println!("effect-scaffold");
    println!();
    println!("Generate Rust effect interfaces and simulator scaffolds from Telltale effect declarations.");
    println!();
    println!("USAGE:");
    println!("  cargo run -p telltale-runtime --bin effect-scaffold -- --out OUT_DIR --dsl path/to/protocol.tell");
    println!("  cargo run -p telltale-runtime --bin effect-scaffold -- --out OUT_DIR --dsl path/to/protocol.tell --no-simulator");
    println!();
    println!("DEFAULTS:");
    println!("  OUT_DIR: {DEFAULT_OUT_DIR}");
    println!(
        "  SIMULATOR_HARNESS: {}",
        if DEFAULT_WITH_SIMULATOR {
            "enabled"
        } else {
            "disabled"
        }
    );
}