interoptopus_backend_haxe 0.0.1

Haxe (hxcpp) backend for Interoptopus FFI bindings generator
Documentation
//! Haxe code generator for Interoptopus.

mod constants;
mod functions;
mod types;

use std::fs::File;
use std::path::Path;

use derive_builder::Builder;
use interoptopus::inventory::Inventory;
use interoptopus_backend_utils::{Error, IndentWriter};

use self::constants::write_constants;
use self::functions::write_functions;
use self::types::write_type_definitions;

/// Configuration for Haxe code generation.
#[derive(Clone, Debug, Default)]
pub struct Config {
    /// The native library name (without extension).
    pub lib_name: String,
    /// The Haxe package name (e.g., "souprune.ffi").
    pub package_name: String,
    /// Additional imports to include.
    pub additional_imports: Vec<String>,
    /// File header comment.
    pub file_header_comment: String,
}

/// Generates Haxe extern class bindings, **get this with [`InteropBuilder`]**.
#[derive(Clone, Debug, Builder, Default)]
#[builder(default)]
pub struct Interop {
    /// Configuration for code generation.
    #[builder(setter(into))]
    config: Config,
    /// The inventory of types and functions to generate bindings for.
    inventory: Inventory,
}

impl Interop {
    /// Creates a new [`InteropBuilder`].
    #[must_use]
    pub fn builder() -> InteropBuilder {
        InteropBuilder::new()
    }

    /// Returns a reference to the inventory.
    pub fn inventory(&self) -> &Inventory {
        &self.inventory
    }

    /// Returns a reference to the config.
    pub fn config(&self) -> &Config {
        &self.config
    }

    /// Generates FFI binding code and writes them to the [`IndentWriter`].
    ///
    /// # Errors
    /// Can result in an error if I/O failed.
    pub fn write_to(&self, w: &mut IndentWriter) -> Result<(), Error> {
        self.write_file_header(w)?;
        self.write_package_declaration(w)?;
        w.newline()?;
        self.write_imports(w)?;
        w.newline()?;

        // Write the main extern class
        self.write_extern_class_header(w)?;
        w.indent();

        write_constants(self, w)?;
        write_type_definitions(self, w)?;
        write_functions(self, w)?;

        w.unindent();
        self.write_extern_class_footer(w)?;

        // Write struct definitions as separate abstracts/classes
        self.write_struct_definitions(w)?;

        Ok(())
    }

    /// Convenience method to write FFI bindings to the specified file.
    ///
    /// # Errors
    /// Can result in an error if I/O failed.
    pub fn write_file<P: AsRef<Path>>(&self, file_name: P) -> Result<(), Error> {
        let mut file = File::create(file_name)?;
        let mut writer = IndentWriter::new(&mut file);

        self.write_to(&mut writer)
    }

    /// Convenience method to write FFI bindings to a string.
    ///
    /// # Errors
    /// Can result in an error if I/O failed.
    pub fn to_string(&self) -> Result<String, Error> {
        let mut vec = Vec::new();
        let mut writer = IndentWriter::new(&mut vec);
        self.write_to(&mut writer)?;
        Ok(String::from_utf8(vec)?)
    }

    fn write_file_header(&self, w: &mut IndentWriter) -> Result<(), Error> {
        w.indented(|w| writeln!(w, "// <auto-generated>"))?;
        w.indented(|w| writeln!(w, "//"))?;
        w.indented(|w| {
            writeln!(
                w,
                "// This file was automatically generated by Interoptopus."
            )
        })?;
        w.indented(|w| writeln!(w, "//"))?;
        w.indented(|w| writeln!(w, "// Library:      {}", self.config.lib_name))?;
        w.indented(|w| writeln!(w, "// Package:      {}", self.config.package_name))?;
        w.indented(|w| writeln!(w, "// Builder:      interoptopus_backend_haxe (0.1.0)"))?;
        w.indented(|w| writeln!(w, "//"))?;
        w.indented(|w| writeln!(w, "// Do not edit this file manually."))?;
        w.indented(|w| writeln!(w, "//"))?;
        w.indented(|w| writeln!(w, "// </auto-generated>"))?;
        w.newline()?;
        Ok(())
    }

    fn write_package_declaration(&self, w: &mut IndentWriter) -> Result<(), Error> {
        if !self.config.package_name.is_empty() {
            w.indented(|w| writeln!(w, "package {};", self.config.package_name))?;
        }
        Ok(())
    }

    fn write_imports(&self, w: &mut IndentWriter) -> Result<(), Error> {
        // Standard hxcpp imports for FFI
        w.indented(|w| writeln!(w, "import cpp.Pointer;"))?;
        w.indented(|w| writeln!(w, "import cpp.ConstPointer;"))?;
        w.indented(|w| writeln!(w, "import cpp.RawPointer;"))?;

        for import in &self.config.additional_imports {
            w.indented(|w| writeln!(w, "import {};", import))?;
        }

        Ok(())
    }

    fn write_extern_class_header(&self, w: &mut IndentWriter) -> Result<(), Error> {
        let class_name = if self.config.lib_name.is_empty() {
            "NativeApi".to_string()
        } else {
            format!("{}Api", heck::AsUpperCamelCase(&self.config.lib_name))
        };

        w.indented(|w| writeln!(w, "/**"))?;
        w.indented(|w| writeln!(w, " * Native FFI bindings for {}.", self.config.lib_name))?;
        w.indented(|w| writeln!(w, " */"))?;
        w.indented(|w| writeln!(w, "@:include(\"souprune_api.h\")"))?;
        w.indented(|w| writeln!(w, "extern class {} {{", class_name))?;

        Ok(())
    }

    fn write_extern_class_footer(&self, w: &mut IndentWriter) -> Result<(), Error> {
        w.indented(|w| writeln!(w, "}}"))?;
        Ok(())
    }

    fn write_struct_definitions(&self, w: &mut IndentWriter) -> Result<(), Error> {
        use interoptopus::lang::Type;

        for the_type in self.inventory.c_types() {
            if let Type::Composite(composite) = the_type {
                w.newline()?;
                self.write_struct_abstract(w, composite)?;
            }
        }

        Ok(())
    }

    fn write_struct_abstract(
        &self,
        w: &mut IndentWriter,
        composite: &interoptopus::lang::Composite,
    ) -> Result<(), Error> {
        use crate::converters::{composite_to_typename, to_haxe_type};

        let struct_name = composite_to_typename(self, composite);
        let native_name = struct_name.to_uppercase();

        // Generate documentation
        let doc_lines = composite.meta().docs().lines();
        if let Some(docs) = doc_lines.first() {
            w.indented(|w| writeln!(w, "/** {} */", docs))?;
        }

        // Write as an extern class with @:structAccess
        w.indented(|w| writeln!(w, "@:structAccess"))?;
        w.indented(|w| writeln!(w, "@:native(\"{}\")", native_name))?;
        w.indented(|w| writeln!(w, "extern class {} {{", struct_name))?;

        w.indent();

        // Write fields
        for field in composite.fields() {
            let field_name = field.name();
            let field_type = to_haxe_type(self, field.the_type());

            let field_docs = field.docs().lines();
            if let Some(doc) = field_docs.first() {
                w.indented(|w| writeln!(w, "/** {} */", doc))?;
            }
            w.indented(|w| writeln!(w, "public var {}:{};", field_name, field_type))?;
        }

        w.unindent();
        w.indented(|w| writeln!(w, "}}"))?;

        Ok(())
    }
}

impl InteropBuilder {
    /// Creates a new builder instance.
    #[must_use]
    pub fn new() -> Self {
        Self::default()
    }
}