trixy 0.4.0

A rust crate used to generate multi-language apis for your application
Documentation
/*
* Copyright (C) 2023 - 2024:
* The Trinitrix Project <soispha@vhack.eu, antifallobst@systemausfall.org>
* SPDX-License-Identifier: GPL-3.0-or-later
*
* This file is part of the Trixy crate for Trinitrix.
*
* Trixy is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as
* published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* and the GNU General Public License along with this program.
* If not, see <https://www.gnu.org/licenses/>.
*/

use proc_macro2::TokenStream as TokenStream2;
use quote::{format_ident, quote};

use crate::{
    macros::config::trixy::TrixyConfig,
    parser::command_spec::{Function, Identifier, NamedType, Namespace},
};

impl Function {
    pub fn to_c(&self, config: &TrixyConfig, namespaces: &Vec<&Identifier>) -> TokenStream2 {
        let ident = self.identifier.to_c_with_path(namespaces);
        let inputs: Vec<TokenStream2> = self
            .inputs
            .iter()
            .filter_map(|a| NamedType::to_c(a))
            .collect();

        let callback_function = format_ident!("{}", &config.callback_function);

        let command_value: TokenStream2 = self.to_rust_path(&namespaces);

        let no_output = || -> TokenStream2 {
            quote! {
                #[no_mangle]
                pub extern "C" fn #ident(#(#inputs),*) -> core::ffi::c_int {
                    let error = std::panic::catch_unwind(|| {
                        crate :: #callback_function (crate :: #command_value);

                        // I have no idea, why this closure needs to return an integer, but this
                        // should suffice and be utterly irrelevant, as we don't use the ok exit
                        // condition
                        0
                    });
                    if let Err(_) = error {
                        // NOTE: We don't need to print the error message here, as the
                        // standart panic handler should already do this (or a custom one,
                        // if the one from std was overridden) <2024-07-23>
                        eprintln!("Catched a panic just before the c ffi.");

                        std::process::exit(1);
                    }
                    return 1;
                }
            }
        };

        if let Some(r#type) = &self.output {
            if let Some(output_ident) = r#type.to_c() {
                quote! {
                   #[no_mangle]
                   pub extern "C" fn #ident(output: *mut #output_ident, #(#inputs),*) -> core::ffi::c_int {
                       let output_val: #output_ident = {
                           let (tx, rx) = trixy::oneshot::channel();

                           let error = std::panic::catch_unwind(|| {
                                crate :: #callback_function (crate :: #command_value);

                                // I have no idea, why this closure needs to return an integer, but this
                                // should suffice and be utterly irrelevant, as we don't use the ok exit
                                // condition
                                0
                           });
                           if let Err(_) = error {
                               // NOTE: We don't need to print the error message here, as the
                               // standart panic handler should already do this (or a custom one,
                               // if the one from std was overridden) <2024-07-23>
                               eprintln!("Catched a panic just before the c ffi.");

                               std::process::exit(1);
                           }


                           let recv = rx.recv().expect("The channel should not be closed until this value is received");
                           recv.into()
                       };
                       unsafe {
                           std::ptr::write(output, output_val);
                       }
                       return 1;
                   }
                }
            } else {
                no_output()
            }
        } else {
            no_output()
        }
    }

    /// Turns a function in namespaces to the generated host enum path:
    /// *trixy code:*
    /// ```text
    /// fn fn_alone();
    /// mod one {
    ///     fn fn_one();
    ///     mod two {
    ///         fn fn_two(input: String);
    ///     }
    /// }
    /// ```
    /// *rust enum path for fn_alone:*
    /// ```
    /// Commands::fn_alone
    /// ```
    /// *rust enum path for fn_one:*
    /// ```
    /// // `Commands` is just the name for the top-level namespace
    /// Commands::One(one::One(
    ///     One::fn_one
    /// ))
    /// ```
    /// *rust enum path for fn_two:*
    /// ```
    /// Commands::One(one::One(
    ///     one::two::Two(one::two::Two(
    ///         Two::fn_two {input: String}
    /// ))))
    /// ```
    pub fn to_rust_path(&self, namespaces: &Vec<&Identifier>) -> TokenStream2 {
        let function_ident = self.to_rust_identifier(
            |a| NamedType::to_rust_assignment(a).unwrap_or(TokenStream2::default()),
            |_| {
                quote! {
                // This is defined in the outer call
                tx
                }
            },
        );

        if namespaces.is_empty() {
            quote! {
                Commands :: #function_ident
            }
        } else {
            let nasp_pascal_ident = namespaces.last().expect("We checked").to_rust_pascalized();

            let namespace_path: TokenStream2 = Namespace::to_rust_path(namespaces);

            let function_call = quote! {
               #namespace_path :: #nasp_pascal_ident :: #function_ident
            };

            let output: TokenStream2 = namespaces
                .iter()
                .enumerate()
                .rev()
                .fold(function_call, |acc, (index, nasp)| {
                    nasp_path_one_part(nasp, &acc, &namespaces, index)
                });

            output
        }
    }
}
/// This function add a namespace component to the [input] value like so:
/// (taking the example from the [function_path_to_rust] function)
/// ```text
/// one::two::Two::fn_two [= <input>]
///     ->
///         one::One::Two(<input>) [= <input>]
///             ->
///             Commands::One(<input>) [= <input>]
/// ```
fn nasp_path_one_part(
    current_nasp: &Identifier,
    input: &TokenStream2,
    namespaces: &Vec<&Identifier>,
    index: usize,
) -> TokenStream2 {
    let namespaces_to_do = &namespaces[..index];

    let ident_pascal = current_nasp.to_rust_pascalized();

    if index == 0 {
        quote! {
            Commands ::  #ident_pascal ( #input )
        }
    } else {
        let ident_pascal_next = namespaces_to_do
            .last()
            .expect("We checked the index")
            .to_rust_pascalized();
        let namespace_path = Namespace::to_rust_path(namespaces_to_do);
        quote! {
            #namespace_path :: #ident_pascal_next :: #ident_pascal ( #input )
        }
    }
}