autd3capi-wrapper-generator 18.0.1

Wrapper code generator for autd3 C API
Documentation
/*
 * File: lib.rs
 * Project: src
 * Created Date: 10/11/2022
 * Author: Shun Suzuki
 * -----
 * Last Modified: 29/11/2023
 * Modified By: Shun Suzuki (suzuki@hapis.k.u-tokyo.ac.jp)
 * -----
 * Copyright (c) 2023 Shun Suzuki. All rights reserved.
 *
 */

mod parse;
mod python;
mod traits;
mod types;

use std::path::Path;

use anyhow::Result;

use cargo_metadata::MetadataCommand;
use convert_case::{Case, Casing};
use parse::{parse_const, parse_enum, parse_func, parse_struct};
use python::PythonGenerator;
use traits::Generator;

fn gen<G: Generator, P1: AsRef<Path>, P2: AsRef<Path>>(
    path: P1,
    crate_path: P2,
    use_single: bool,
) -> Result<()> {
    std::fs::create_dir_all(path.as_ref())?;

    let metadata = MetadataCommand::new()
        .manifest_path(crate_path.as_ref().join("Cargo.toml"))
        .exec()?;

    let crate_name = metadata.root_package().unwrap().name.as_str();

    glob::glob(&format!(
        "{}/**/*.rs",
        crate_path.as_ref().join("src").display()
    ))?
    .try_fold(G::new(), |acc, path| -> Result<_> {
        let path = path?;
        Ok(acc
            .register_func(parse_func(&path, use_single)?)
            .register_const(parse_const(&path, use_single)?)
            .register_enum(parse_enum(&path, use_single)?)
            .register_struct(parse_struct(&path, use_single)?))
    })?
    .write(path, crate_name)
}

fn generate_cs<P1: AsRef<Path>, P2: AsRef<Path>>(
    path: P1,
    crate_path: P2,
    use_single: bool,
) -> Result<()> {
    let sub_abbr =
        |str: String| -> String { str.replace("Twincat", "TwinCAT").replace("Soem", "SOEM") };

    let to_pascal = |name: &str| -> String {
        let res = name.to_case(Case::Pascal);
        sub_abbr(res)
    };

    let to_class_name = |name: &str| {
        if name.split('-').count() == 1 {
            return "Base".to_string();
        }
        to_pascal(&name.replace("autd3capi-", ""))
    };

    let crate_name = crate_path.as_ref().file_name().unwrap().to_str().unwrap();
    let out_file = Path::new(path.as_ref()).join(format!("{}.cs", to_class_name(crate_name)));
    let dll_name = crate_name.replace('-', "_");
    let class_name = to_class_name(crate_name);

    glob::glob(&format!(
        "{}/**/*.rs",
        crate_path.as_ref().join("src").display()
    ))?
    .try_fold(csbindgen::Builder::default(), |acc, path| -> Result<_> {
        let path = path?;
        Ok(acc.input_extern_file(path))
    })?
    .csharp_dll_name(dll_name)
    .csharp_class_name(format!("NativeMethods{}", class_name))
    .csharp_namespace("AUTD3Sharp.NativeMethods")
    .csharp_generate_const_filter(|_| true)
    .csharp_class_accessibility("public")
    .generate_csharp_file(&out_file)
    .map_err(|_| anyhow::anyhow!("failed to generate cs wrapper"))?;

    let content = std::fs::read_to_string(&out_file)?;
    let content = content.replace("@float", if use_single { "float" } else { "double" });
    let content = content.replace("ConstPtr", "IntPtr");
    let content = content.replace("void*", "IntPtr");
    let content = content.replace("SamplingConfiguration", "SamplingConfigurationRaw");

    let content = content.replace("Drive*", "DriveRaw*");

    let content = if use_single {
        let re = regex::Regex::new(r"public const float (.*) = (.*);").unwrap();
        re.replace_all(&content, "public const float $1 = ${2}f;")
            .to_string()
    } else {
        content
    };

    std::fs::write(&out_file, content)?;

    Ok(())
}

pub fn gen_c<P1: AsRef<Path>, P2: AsRef<Path>>(crate_path: P1, dest_dir: P2) -> Result<()> {
    let out_file = dest_dir.as_ref().join(format!(
        "{}.h",
        crate_path.as_ref().file_name().unwrap().to_str().unwrap()
    ));
    let mut config = cbindgen::Config::default();
    config.language = cbindgen::Language::Cxx;
    config.pragma_once = true;
    config.autogen_warning = Some(
        "/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */"
            .to_string(),
    );
    config.namespace = Some("autd3::internal::native_methods".to_string());
    config.no_includes = true;
    config.sys_includes = vec!["cstdint".to_string()];
    config.sort_by = cbindgen::SortKey::None;
    config.usize_is_size_t = true;
    config.export = cbindgen::ExportConfig {
        include: vec![
            "TimerStrategy".to_string(),
            "GainSTMMode".to_string(),
            "ControllerPtr".to_string(),
            "EmissionConstraintPtr".to_string(),
            "FirmwareInfoListPtr".to_string(),
            "GroupKVMapPtr".to_string(),
            "CachePtr".to_string(),
            "DevicePtr".to_string(),
            "TransducerPtr".to_string(),
            "GeometryPtr".to_string(),
            "ModulationPtr".to_string(),
            "GainPtr".to_string(),
            "LinkPtr".to_string(),
            "DatagramPtr".to_string(),
            "DatagramSpecialPtr".to_string(),
            "STMPropsPtr".to_string(),
            "BackendPtr".to_string(),
            "GroupGainMapPtr".to_string(),
            "GainCalcDrivesMapPtr".to_string(),
            "LinkBuilderPtr".to_string(),
            "ResultI32".to_string(),
            "ResultModulation".to_string(),
            "ResultBackend".to_string(),
            "ResultController".to_string(),
            "ResultGainCalcDrivesMap".to_string(),
            "ResultDatagram".to_string(),
            "Drive".to_string(),
        ],
        exclude: vec!["ConstPtr".to_string()],
        rename: vec![
            ("float".to_string(), "double".to_string()),
            ("ConstPtr".to_string(), "void*".to_string()),
        ]
        .into_iter()
        .collect(),
        ..Default::default()
    };
    config.function = cbindgen::FunctionConfig {
        sort_by: None,
        must_use: Some("[[nodiscard]]".to_string()),
        ..Default::default()
    };
    config.constant = cbindgen::ConstantConfig {
        allow_static_const: false,
        allow_constexpr: true,
        sort_by: Some(cbindgen::SortKey::None),
    };

    cbindgen::Builder::new()
        .with_crate(crate_path)
        .with_config(config)
        .generate()?
        .write_to_file(out_file);

    Ok(())
}

pub fn gen_py<P1: AsRef<Path>, P2: AsRef<Path>>(crate_path: P1, dest_dir: P2) -> Result<()> {
    gen::<PythonGenerator, _, _>(dest_dir, crate_path, false)
}

pub fn gen_cs<P1: AsRef<Path>, P2: AsRef<Path>>(crate_path: P1, dest_dir: P2) -> Result<()> {
    generate_cs(dest_dir, crate_path, false)
}

pub fn gen_unity<P1: AsRef<Path>, P2: AsRef<Path>>(crate_path: P1, dest_dir: P2) -> Result<()> {
    generate_cs(dest_dir, crate_path, true)
}

pub fn generate<P: AsRef<Path>>(crate_path: P) -> Result<()> {
    gen_py(&crate_path, "../../python/pyautd3/native_methods")?;

    gen_c(
        &crate_path,
        "../../cpp/include/autd3/internal/native_methods",
    )?;
    gen_cs(&crate_path, "../../dotnet/cs/src/NativeMethods")?;
    gen_unity(
        &crate_path,
        "../../dotnet/unity/Assets/Scripts/NativeMethods",
    )?;

    Ok(())
}