motto 0.4.2

Compiler-as-a-Service: Turn Rust schema.rs into multi-platform SDK toolkits
Documentation
//! Backend Emitters Module
//!
//! Generates platform-specific SDK code from the IR manifest.
//! Implements Single-Version Policy with 1-byte version header.

#[cfg(feature = "emitter-kotlin")]
pub mod kotlin;
pub mod rust;
#[cfg(feature = "emitter-swift")]
pub mod swift;
#[cfg(feature = "emitter-typescript")]
pub mod typescript;
#[cfg(feature = "emitter-unity")]
pub mod unity;

use crate::ir::manifest::SchemaManifest;
use std::path::PathBuf;

/// Transport mode for generated SDKs
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum TransportMode {
    /// Each SDK uses its own native-language transport implementation (default)
    #[default]
    Runtime,
    /// SDKs bind to a shared Rust transport core via C ABI / FFI
    Ffi,
}

impl std::fmt::Display for TransportMode {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            TransportMode::Runtime => write!(f, "runtime"),
            TransportMode::Ffi => write!(f, "ffi"),
        }
    }
}

impl std::str::FromStr for TransportMode {
    type Err = String;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s.to_lowercase().as_str() {
            "runtime" => Ok(TransportMode::Runtime),
            "ffi" => Ok(TransportMode::Ffi),
            _ => Err(format!(
                "invalid transport mode '{}': expected 'runtime' or 'ffi'",
                s
            )),
        }
    }
}

/// Configuration for emitters
#[derive(Debug, Clone)]
pub struct EmitterConfig {
    /// Output directory
    pub output_dir: PathBuf,
    /// Generate WASM bindings
    pub wasm_bindings: bool,
    /// Generate native addon bindings (napi-rs)
    pub native_bindings: bool,
    /// Transport mode for generated SDK transport layer
    pub transport_mode: TransportMode,
    /// The schema manifest to emit
    pub manifest: SchemaManifest,
}

/// Trait for all backend emitters
pub trait Emitter {
    /// Target platform name
    fn platform(&self) -> &'static str;

    /// File extension for generated files
    fn extension(&self) -> &'static str;

    /// Generate SDK code
    fn emit(&self, config: &EmitterConfig) -> anyhow::Result<Vec<GeneratedFile>>;
}

/// A generated file
#[derive(Debug, Clone)]
pub struct GeneratedFile {
    /// Relative path from output directory
    pub path: PathBuf,
    /// File content
    pub content: String,
}

/// Protocol version byte constant for generated code
pub const VERSION_BYTE_COMMENT: &str = r#"
// ============================================================================
// MOTTO GENERATED CODE - DO NOT EDIT
// 
// This file was generated by motto from a Rust schema definition.
// Any changes will be overwritten on next generation.
//
// Protocol Version Byte: {VERSION_BYTE}
// Schema Fingerprint: {FINGERPRINT}
// Generated At: {TIMESTAMP}
// ============================================================================
"#;

/// Common utility functions for emitters
pub mod utils {
    use heck::{ToLowerCamelCase, ToPascalCase, ToSnakeCase, ToUpperCamelCase};

    /// Convert a Rust type name to the target language's convention
    pub fn to_pascal_case(s: &str) -> String {
        s.to_pascal_case()
    }

    pub fn to_camel_case(s: &str) -> String {
        s.to_lower_camel_case()
    }

    pub fn to_snake_case(s: &str) -> String {
        s.to_snake_case()
    }

    pub fn to_upper_camel_case(s: &str) -> String {
        s.to_upper_camel_case()
    }

    /// Escape reserved words in various languages
    pub fn escape_reserved(name: &str, reserved: &[&str]) -> String {
        if reserved.contains(&name) {
            format!("{}_", name)
        } else {
            name.to_string()
        }
    }

    /// Generate file header with version info
    pub fn generate_header(version_byte: u8, fingerprint: &str, timestamp: &str) -> String {
        super::VERSION_BYTE_COMMENT
            .replace("{VERSION_BYTE}", &format!("0x{:02X}", version_byte))
            .replace("{FINGERPRINT}", fingerprint)
            .replace("{TIMESTAMP}", timestamp)
    }
}