refactor(treewide): Rework file structure in `src/macros`

This might be a big diff, but I _hope_ that it does not change much
functionally (hopefully it changes nothing generation specific).

What has changed?
-----------------
- I had to merge the three crates into one, to allow `macros` to impl
  functions on Types defined in `parser`.
- As mentioned in the point above, the conversion function are now
  inherent to the type they convert (i. e. `r#type.to_rust()` instead of
  `type_to_rust(r#type)`).
- The conversion function have been sorted, by what they convert to:
   - `to_rust()` converts a type to be used in rust *host* code.
   - `to_c()` converts a type to be used in c *host* code.
   - `to_auxiliary_c()` converts a type to be used in c *auxiliary*
     code.
- The top-most `generate` method of `TrixyConfig` now returns a
  `FileTree` instead of writing the files directly. The `FileTree` can
  still be materialize with the `materialize` method. But this change
  facilitates moving non-generation focused code out of the `generate`
  method.

What is the difference between _host_ and _auxiliary_ code?
-----------------------------------------------------------
Auxiliary code is always written in the language it is generated for. So
auxiliary code for c would be written in c (or at least in a subset of c),
as it represents c header files.
Host code is always written in rust. This is the code that is
responsible for implementing the actual ffi stuff. In our case these are
the `extern "C"` functions and the types, defined in trixy.
The rust host code is generating the native rust representations of
these types.
This commit is contained in:
Benedikt Peetz 2024-03-26 10:38:14 +01:00
parent c898c48de2
commit eb7a901d09
Signed by: bpeetz
GPG Key ID: A5E94010C3A642AD
119 changed files with 2280 additions and 2901 deletions

View File

@ -23,7 +23,30 @@ version = "0.1.0"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
trixy-parser = { path = "./trixy-parser" } clap = { version = "4.5.4", features = ["derive"], optional = true }
trixy-macros = { path = "./trixy-macros" } convert_case = {version = "0.6.0", optional = true}
trixy-types = { path = "./trixy-types" } proc-macro2 = {version = "1.0.79", optional = true}
thiserror = "1.0.57" quote = {version = "1.0.35", optional = true}
syn = { version = "2.0.55", features = ["extra-traits", "full", "parsing"], optional = true }
thiserror = { version = "1.0.58", optional = true}
# macros
prettyplease = {version = "0.2.17", optional = true}
# parser
regex = {version = "1.10.4", optional = true}
# types
libc ={ version = "0.2.153", optional = true}
log = { version = "0.4.21", optional = true}
[dev-dependencies]
# parser
pretty_assertions = "1.4.0"
[features]
default = ["parser", "types", "macros"]
parser = [ "regex", "thiserror", "convert_case" ]
types = [ "parser", "libc", "log", "proc-macro2", "quote", "syn", "thiserror", "convert_case" ]
macros = [ "parser", "types", "prettyplease", "proc-macro2", "quote", "syn", "convert_case" ]

View File

@ -18,7 +18,7 @@
# and the Lesser GNU General Public License along with this program. # and the Lesser GNU General Public License along with this program.
# If not, see <https://www.gnu.org/licenses/>. # If not, see <https://www.gnu.org/licenses/>.
ebnf2pdf make "./docs/grammar.ebnf" ebnf2pdf make "./grammar.ebnf"
mv grammar.ebnf.pdf ./docs/grammar.pdf mv grammar.ebnf.pdf ./grammar.pdf
# vim: ft=sh # vim: ft=sh

View File

@ -19,13 +19,13 @@
* If not, see <https://www.gnu.org/licenses/>. * If not, see <https://www.gnu.org/licenses/>.
*/ */
pub mod types { #[cfg(feature = "macros")]
pub use trixy_types::*; pub mod macros;
} #[cfg(feature = "types")]
pub mod types;
#[cfg(feature = "parser")]
pub mod parser;
pub mod macros {
pub use trixy_macros::*;
}
pub mod __private { pub mod __private {
//! This module contains crates needed for the generated code, it should not be used by humans. //! This module contains crates needed for the generated code, it should not be used by humans.
pub mod thiserror { pub mod thiserror {

View File

@ -0,0 +1,127 @@
//! [`FileTree`]s are the fundamental data structure used by trixy to present generated data to
//! you.
use std::{
fmt::Display,
fs, io,
path::{Path, PathBuf},
};
use super::trixy::Language;
/// A file tree containing all files that were generated. These are separated into host and
/// auxiliary files. See their respective descriptions about what differentiates them.
#[derive(Default, Debug)]
pub struct FileTree {
/// Files, that are supposed to be included in the compiled crate.
pub host_files: Vec<GeneratedFile>,
/// Files, that should be shared with the consumer (e. g. c headers).
pub auxiliary_files: Vec<GeneratedFile>,
}
/// A generated files
#[derive(Default, Debug)]
pub struct GeneratedFile {
/// The path this generated file would like to be placed at.
/// This path is relative to the crate root.
pub path: PathBuf,
/// The content of this file.
///
/// This should already be formatted and ready to be used.
pub value: String,
/// The language this file is written in.
///
/// # Note
/// This can also be a associated language,
/// like having this set to [`Language::C`] for a c header file.
pub language: Language,
}
impl GeneratedFile {
pub fn new(path: PathBuf, value: String, language: Language) -> Self {
Self {
path,
value,
language,
}
}
pub fn new_in_out_dir(name: String, value: String, language: Language, out_dir: &Path) -> Self {
let path = out_dir.join(name);
Self {
path,
value,
language,
}
}
pub fn materialize(self) -> io::Result<()> {
fs::create_dir_all(self.path.parent().expect("This path should have a parent"))?;
fs::write(self.path, self.value.as_bytes())?;
Ok(())
}
}
impl FileTree {
pub fn new() -> Self {
Self::default()
}
pub fn add_host_file(&mut self, file: GeneratedFile) {
self.host_files.push(file)
}
pub fn add_auxiliary_file(&mut self, file: GeneratedFile) {
self.auxiliary_files.push(file)
}
pub fn materialize(self) -> io::Result<()> {
self.host_files
.into_iter()
.map(|file| -> io::Result<()> { file.materialize() })
.collect::<io::Result<()>>()?;
self.auxiliary_files
.into_iter()
.map(|file| -> io::Result<()> { file.materialize() })
.collect::<io::Result<()>>()?;
Ok(())
}
}
impl Display for Language {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self {
Language::Rust => f.write_str("rust"),
Language::C => f.write_str("c"),
Language::Lua => f.write_str("lua"),
Language::All => unreachable!("The `all` language variant should never be displayed"),
}
}
}
impl Display for GeneratedFile {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!("File path: `{}`\n", self.path.display()))?;
f.write_fmt(format_args!("```{}\n", self.language))?;
f.write_fmt(format_args!("{}\r", &self.value))?;
f.write_str("```\n\n")
}
}
impl Display for FileTree {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("# Host files\n")?;
self.host_files
.iter()
.map(|file| -> std::fmt::Result { f.write_str(&file.to_string()) })
.collect::<std::fmt::Result>()?;
f.write_str("# Auxiliary files\n")?;
self.auxiliary_files
.iter()
.map(|file| -> std::fmt::Result { f.write_str(&file.to_string()) })
.collect::<std::fmt::Result>()
}
}

2
src/macros/config/mod.rs Normal file
View File

@ -0,0 +1,2 @@
pub mod file_tree;
pub mod trixy;

View File

@ -21,64 +21,72 @@
//! This module is responsible for the config passed to trixy. //! This module is responsible for the config passed to trixy.
//! It works using the popular builder syntax: //! It works using the popular builder syntax:
//! ```no_run //! ```
//! use trixy_macros::config::{Language, TrixyConfig}; //! use crate::macros::config::{Language, TrixyConfig};
//!# fn main() { //!# fn main() {
//! let config = TrixyConfig::new() //! let config = TrixyConfig::new()
//! .set_input_path("path/to/trixy/api.tri") //! .trixy_path("path/to/trixy/api.tri")
//! .set_output_path("api.rs") //! .output_path("api.rs")
//! .set_languages(vec![Language::Rust, Language::C]) //! .languages(vec![Language::Rust, Language::C])
//! .set_generate_debug(false);
//!# } //!# }
//! ``` //! ```
use std::path::PathBuf; use std::{env, path::PathBuf};
#[derive(Debug)] #[derive(Debug, Default)]
pub enum Language { pub enum Language {
Rust, Rust,
C, C,
Lua, Lua,
#[default]
All,
} }
#[derive(Default, Debug)] #[derive(Default, Debug)]
pub struct TrixyConfig { pub struct TrixyConfig {
/// The Path to the base command interface config file /// The Path to the base command interface config file.
pub trixy_path: PathBuf, pub trixy_path: PathBuf,
/// The name of the outputted host code (rust and c bindings) /// The name of the outputted host code (rust and c bindings).
/// This file is written in $OUT_DIR /// This file is written in $OUT_DIR>
pub host_code_name: PathBuf, pub host_code_name: String,
/// The name of the c header /// The name of the c header.
/// This file is written in $OUT_DIR /// This file is written in $OUT_DIR.
pub c_header_name: PathBuf, pub c_header_name: String,
/// The path from the root to the distribution directory. /// The path from the root to the distribution directory.
/// Things like the c headers are copied in this dir /// Things like the c headers are copied in this dir.
/// When this is [None] no dist dir will be generated /// When this is [None] no dist dir will be generated.
pub dist_dir_path: Option<PathBuf>, pub dist_dir_path: Option<PathBuf>,
/// Whether to check if the dist dir is empty before writing something to it // /// Whether to check if the dist dir is empty before writing something to it.
pub check_dist_dir: bool, // pub check_dist_dir: bool,
/// Should the macro generate Debug trait implementation for each enum
/// These are very useful but completely obscure the `cargo expand` output
pub generate_debug: bool,
/// This function is executed whenever an API function is called. /// This function is executed whenever an API function is called.
/// The only argument is the command (encoded as the `Commands`) enum /// The only argument is the command (encoded as the `Commands`) enum
/// which is represented by this function /// which is represented by this function.
/// Because of rust limitations this is supposed to be the name of a macro acting as said
/// function. This macro must be in scope of the generated code
pub callback_function: String, pub callback_function: String,
/// This path is used to place the outputted host code files.
/// Normally this would be the `$OUT_DIR` environment variable set by cargo at build time.
/// You have to set this, if you want to use trixy in an other context.
pub out_dir: PathBuf,
} }
impl TrixyConfig { impl TrixyConfig {
pub fn new<T: Into<String>>(callback_function: T) -> Self { pub fn new<T: Into<String>>(callback_function: T) -> Self {
let out_dir;
if let Ok(out_dir_new) = env::var("OUT_DIR") {
let out_path = PathBuf::from(out_dir_new);
out_dir = out_path;
} else {
out_dir = PathBuf::default();
};
Self { Self {
callback_function: callback_function.into(), callback_function: callback_function.into(),
host_code_name: "api.rs".into(), host_code_name: "api.rs".into(),
c_header_name: "interface.h".into(), c_header_name: "interface.h".into(),
out_dir,
..Default::default() ..Default::default()
} }
} }
@ -90,13 +98,6 @@ impl TrixyConfig {
} }
} }
pub fn generate_debug(self, generate_debug: bool) -> Self {
Self {
generate_debug,
..self
}
}
pub fn dist_dir_path<T: Into<PathBuf>>(self, dist_dir_path: T) -> Self { pub fn dist_dir_path<T: Into<PathBuf>>(self, dist_dir_path: T) -> Self {
Self { Self {
dist_dir_path: Some(dist_dir_path.into()), dist_dir_path: Some(dist_dir_path.into()),
@ -104,21 +105,21 @@ impl TrixyConfig {
} }
} }
pub fn check_dist_dir(self, check_dist_dir: bool) -> Self { pub fn host_code_name<T: Into<String>>(self, output_path: T) -> Self {
Self {
check_dist_dir,
..self
}
}
pub fn host_code_name<T: Into<PathBuf>>(self, output_path: T) -> Self {
Self { Self {
host_code_name: output_path.into(), host_code_name: output_path.into(),
..self ..self
} }
} }
pub fn c_header_name<T: Into<PathBuf>>(self, output_path: T) -> Self { pub fn out_dir<T: Into<PathBuf>>(self, out_dir: T) -> Self {
Self {
out_dir: out_dir.into(),
..self
}
}
pub fn c_header_name<T: Into<String>>(self, output_path: T) -> Self {
Self { Self {
c_header_name: output_path.into(), c_header_name: output_path.into(),
..self ..self

View File

@ -23,14 +23,8 @@
//! It works by firstly listing the functions and then by grouping them into structures, effectively //! It works by firstly listing the functions and then by grouping them into structures, effectively
//! simulating namespaces in c. //! simulating namespaces in c.
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use trixy_parser::command_spec::{Attribute, CommandSpec, NamedType};
use trixy_types::header_names;
use crate::{ use crate::{
config::TrixyConfig, macros::config::trixy::TrixyConfig, parser::command_spec::CommandSpec, types::header_names,
generate::{c_api::type_to_c, identifier_to_rust},
}; };
mod pure_header; mod pure_header;
@ -109,19 +103,3 @@ pub fn generate(trixy: &CommandSpec, _config: &TrixyConfig) -> String {
END_HEADER_GUARD END_HEADER_GUARD
) )
} }
fn attribute_to_doc_comment(attribute: &Attribute) -> String {
if let Attribute::doc(doc_comment) = attribute {
format!("/** {}\n*/", doc_comment)
} else {
"".to_owned()
}
}
fn named_type_to_c(named_type: &NamedType) -> TokenStream2 {
let ident = identifier_to_rust(&named_type.name);
let c_type = type_to_c(&named_type.r#type, false);
quote! {
#c_type #ident
}
}

View File

@ -19,14 +19,31 @@
* If not, see <https://www.gnu.org/licenses/>. * If not, see <https://www.gnu.org/licenses/>.
*/ */
fn print(message: String); use crate::parser::command_spec::{CommandSpec, Enumeration, Structure};
/// First doc comment pub fn generate(trixy: &CommandSpec) -> String {
// Some more text let functions: String = trixy
mod trinitrix { .functions
/// Second doc comment .iter()
fn hi(name: String) -> String; .map(|r#fn| r#fn.to_auxiliary_c(&[]))
.collect();
let namespaces: String = trixy
.namespaces
.iter()
.map(|nasp| nasp.to_auxiliary_c(&vec![]))
.collect();
let structures: String = trixy
.structures
.iter()
.map(Structure::to_auxiliary_c)
.collect();
let enumerations: String = trixy
.enumerations
.iter()
.map(Enumeration::to_auxiliary_c)
.collect();
format!(
"{}\n{}\n{}\n{}",
enumerations, structures, functions, namespaces
)
} }
// That's a flat out lie, but it results in a rather nice syntax highlight compared to nothing:
// vim: syntax=rust

View File

@ -19,15 +19,15 @@
* If not, see <https://www.gnu.org/licenses/>. * If not, see <https://www.gnu.org/licenses/>.
*/ */
fn print(message: CommandTransferValue); use proc_macro2::TokenStream as TokenStream2;
mod trinitrix { use crate::parser::command_spec::CommandSpec;
fn hi(name: String) -> String;
pub fn generate(trixy: &CommandSpec) -> String {
let struct_initializer: TokenStream2 = trixy
.namespaces
.iter()
.map(|nasp| nasp.to_auxiliary_c_full_struct_init(&vec![]))
.collect();
struct_initializer.to_string()
} }
mod trinitrix {
fn ho(name: String, name2: String) -> String;
}
// That's a flat out lie, but it results in a rather nice syntax highlight compared to nothing:
// vim: syntax=rust

View File

@ -19,12 +19,15 @@
* If not, see <https://www.gnu.org/licenses/>. * If not, see <https://www.gnu.org/licenses/>.
*/ */
struct Callback { use crate::parser::command_spec::{CommandSpec, Namespace};
func: Function,
timeout: Integer, pub fn generate(trixy: &CommandSpec) -> String {
let type_defs: String = trixy
.namespaces
.iter()
.rev()
.map(Namespace::to_auxiliary_c_full_typedef)
.collect::<Vec<String>>()
.join("\n");
type_defs.to_string()
} }
fn execute_callback(callback: Name);
// That's a flat out lie, but it results in a rather nice syntax highlight compared to nothing:
// vim: syntax=rust

View File

@ -0,0 +1,47 @@
use std::io::Write;
use std::process::{Command, Stdio};
use crate::{
macros::{config::trixy::TrixyConfig, VIM_LINE_C},
parser::command_spec::CommandSpec,
};
pub mod c;
// FIXME(@soispha): We made to promise to not panic (outside of the toplevel generate function).
// Therefore these panics here should be avoided. <2024-03-25>
pub fn format_c(input: String) -> String {
let mut clang_format = Command::new("clang-format")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.args(["--style", "GNU"])
.spawn()
.expect("`clang-format` is a dependency and should be provided by the user");
let mut stdin = clang_format
.stdin
.take()
.expect("We should be able to take stdin");
std::thread::spawn(move || {
stdin
.write_all(input.as_bytes())
.expect("Should be able to write to stdin");
});
let output = clang_format
.wait_with_output()
.expect("Should be able to read stdout");
String::from_utf8(output.stdout).expect("The input was utf8, it should not have changed")
}
pub fn generate(trixy: &CommandSpec, config: &TrixyConfig) -> String {
let c_code = c::generate(trixy, config);
let c_code = format_c(c_code);
format!(
"
{}\n{}
",
c_code, VIM_LINE_C
)
}

View File

@ -0,0 +1,11 @@
use crate::parser::command_spec::Attribute;
impl Attribute {
pub fn to_auxiliary_c(&self) -> String {
if let Attribute::doc(doc_comment) = self {
format!("/**\n {}\n*/", doc_comment)
} else {
"".to_owned()
}
}
}

View File

@ -0,0 +1,36 @@
use crate::parser::command_spec::{Attribute, DocIdentifier, Enumeration};
impl Enumeration {
pub fn to_auxiliary_c(&self) -> String {
let doc_comments: String = self
.attributes
.iter()
.map(Attribute::to_auxiliary_c)
.collect::<String>();
let ident = &self.identifier.to_auxiliary_c();
let states = self
.states
.iter()
.map(DocIdentifier::to_auxiliary_c)
.collect::<String>();
let states = if self.states.is_empty() {
"/**
* This enum does not have variants on the rust side
* to work around c limitiation with variant-less enums
* we added a `__never` variant:
*/
__never,"
.to_owned()
} else {
states
};
format!(
"
{}
{} {{
{}
}};",
doc_comments, ident, states
)
}
}

View File

@ -0,0 +1,77 @@
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use crate::parser::command_spec::{Attribute, Function, Identifier, NamedType};
impl Function {
pub fn to_auxiliary_c(&self, namespaces: &[&Identifier]) -> String {
let doc_comments: String = self
.attributes
.iter()
.map(Attribute::to_auxiliary_c)
.collect::<String>();
let ident = self.identifier.to_c_with_path(namespaces);
let inputs: Vec<TokenStream2> = self.inputs.iter().map(NamedType::to_auxiliary_c).collect();
let function_output = if let Some(out) = &self.output {
let type_name = &out.to_auxiliary_c(true);
let comma = if !inputs.is_empty() {
quote! {
,
}
} else {
TokenStream2::default()
};
quote! {
#type_name trixy_output #comma
}
} else {
TokenStream2::default()
};
let output = quote! {
extern int #ident(#function_output #(#inputs),*);
};
format!("{}{}\n", doc_comments, output)
}
pub fn to_auxiliary_c_namespace_init(&self, namespaces: &[&Identifier]) -> TokenStream2 {
let ident = &self.identifier.to_rust();
let full_ident = &self.identifier.to_c_with_path(namespaces);
quote! {
. #ident = #full_ident,
}
}
pub fn to_auxiliary_c_typedef(&self) -> TokenStream2 {
let ident = self.identifier.to_rust();
let (output, output_comma) = if let Some(output) = &self.output {
let output = output.to_auxiliary_c(true);
(quote! { #output }, quote! {,})
} else {
(TokenStream2::default(), TokenStream2::default())
};
let inputs: TokenStream2 = if self.inputs.is_empty() && output.is_empty() {
quote! { void }
} else if !self.inputs.is_empty() && !output.is_empty() {
let inputs: Vec<TokenStream2> = self
.inputs
.iter()
.map(|named_type| &named_type.r#type)
.map(|r#type| r#type.to_auxiliary_c(false))
.collect();
quote! {
#output_comma #(#inputs),*
}
} else {
TokenStream2::default()
};
quote! {
int (* #ident ) (#output #inputs);
}
}
}

View File

@ -0,0 +1,13 @@
use crate::parser::command_spec::{Attribute, DocIdentifier};
impl DocIdentifier {
pub fn to_auxiliary_c(&self) -> String {
let doc_comments: String = self
.attributes
.iter()
.map(Attribute::to_auxiliary_c)
.collect::<String>();
let ident = &self.name;
format!("{}{},", doc_comments, ident)
}
}

View File

@ -0,0 +1,42 @@
use convert_case::{Case, Casing};
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use crate::parser::command_spec::{Identifier, Variant};
mod doc_identifier;
//
// pub use doc_identifier::*;
impl Identifier {
pub fn to_auxiliary_c(&self) -> TokenStream2 {
let ident = self.to_rust_pascalized();
match &self.variant {
Variant::Structure { .. } => {
let ident = self.to_rust_pascalized();
quote! {
struct #ident
}
}
Variant::Enumeration { .. } => {
quote! {
enum #ident
}
}
Variant::Primitive => match self.name.to_case(Case::Snake).as_str() {
"string" => {
quote! {
const char*
}
}
other => {
todo!("'{}' is not yet supported", other)
}
},
other => {
unimplemented!("{:#?}", other)
}
}
}
}

View File

@ -0,0 +1,16 @@
mod attribute;
mod enumeration;
mod function;
mod identifier;
mod namespace;
mod structure;
mod r#type;
// pub use attribute::*;
// pub use enumeration::*;
// pub use function::*;
// pub use identifier::*;
// pub use namespace::*;
// pub use structure::*;
// pub use r#type::*;

View File

@ -0,0 +1,122 @@
use proc_macro2::TokenStream as TokenStream2;
use quote::{format_ident, quote};
use crate::parser::command_spec::{
Attribute, Enumeration, Function, Identifier, Namespace, Structure,
};
impl Namespace {
pub fn to_auxiliary_c(&self, namespaces: &Vec<&Identifier>) -> String {
let mut nasps = namespaces.clone();
nasps.push(&self.name);
let structures: String = self
.structures
.iter()
.map(Structure::to_auxiliary_c)
.collect::<Vec<String>>()
.join("\n");
let enumerations: String = self
.enumerations
.iter()
.map(Enumeration::to_auxiliary_c)
.collect::<Vec<String>>()
.join("\n");
let functions: String = self
.functions
.iter()
.map(|r#fn| r#fn.to_auxiliary_c(&nasps))
.collect::<Vec<String>>()
.join("\n");
let namespaces: String = self
.namespaces
.iter()
.map(|nasp| nasp.to_auxiliary_c(&nasps))
.collect();
format! {"{}\n{}\n{}\n{}", enumerations, structures, functions, namespaces}
}
pub fn to_auxiliary_c_full_struct_init(&self, namespaces: &Vec<&Identifier>) -> TokenStream2 {
let mut input_namespaces = namespaces.clone();
input_namespaces.push(&self.name);
let ident = &self.name.to_rust();
let type_ident = ident.clone();
let functions: TokenStream2 = self
.functions
.iter()
.map(|r#fn| r#fn.to_auxiliary_c_namespace_init(&input_namespaces))
.collect();
let namespaces: TokenStream2 = self
.namespaces
.iter()
.map(Namespace::to_auxiliary_c_namespace_init)
.collect();
let next_namespace: TokenStream2 = self
.namespaces
.iter()
.map(|nasp| nasp.to_auxiliary_c_full_struct_init(&input_namespaces))
.collect();
quote! {
#next_namespace
const struct #type_ident #ident = {
#functions
#namespaces
};
}
}
pub fn to_auxiliary_c_typedef(&self) -> TokenStream2 {
let ident = &self.name.to_rust();
let type_ident = ident.clone();
quote! {
struct #type_ident #ident;
}
}
pub fn to_auxiliary_c_full_typedef(&self) -> String {
let ident = format_ident!("{}", self.name.name);
let doc_comments = self
.attributes
.iter()
.map(Attribute::to_auxiliary_c)
.collect::<String>();
let functions: TokenStream2 = self
.functions
.iter()
.map(Function::to_auxiliary_c_typedef)
.collect();
let namespaces: TokenStream2 = self
.namespaces
.iter()
.map(Namespace::to_auxiliary_c_typedef)
.collect();
let next_namespace: String = self
.namespaces
.iter()
.map(Namespace::to_auxiliary_c_full_typedef)
.collect::<Vec<String>>()
.join("\n");
let namespace = quote! {
struct #ident {
#functions
#namespaces
};
};
format! {"{}\n{}{}\n", next_namespace, doc_comments, namespace}
}
pub fn to_auxiliary_c_namespace_init(&self) -> TokenStream2 {
let ident = self.name.to_rust();
quote! {
. #ident = #ident ,
}
}
}

View File

@ -0,0 +1,25 @@
use crate::parser::command_spec::{Attribute, DocNamedType, Structure};
impl Structure {
pub fn to_auxiliary_c(&self) -> String {
let doc_comments: String = self
.attributes
.iter()
.map(Attribute::to_auxiliary_c)
.collect::<String>();
let ident = self.identifier.to_rust();
let contents = self
.contents
.iter()
.map(DocNamedType::to_auxiliary_c)
.collect::<String>();
format!(
"
{}
{} {{
{}
}};",
doc_comments, ident, contents
)
}
}

View File

@ -0,0 +1,14 @@
use crate::parser::command_spec::{Attribute, DocNamedType};
impl DocNamedType {
pub fn to_auxiliary_c(&self) -> String {
let doc_comments: String = self
.attributes
.iter()
.map(Attribute::to_auxiliary_c)
.collect::<String>();
let ident = &self.name.to_rust();
let r#type = self.r#type.to_auxiliary_c(false);
format!("{}{} {};", doc_comments, r#type, ident)
}
}

View File

@ -0,0 +1,52 @@
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use crate::parser::command_spec::{NamedType, Type};
mod doc_named_type;
mod named_type;
// pub use doc_named_type::*;
// pub use named_type::*;
impl Type {
pub fn to_auxiliary_c(&self, is_output: bool) -> TokenStream2 {
match self {
Type::Typical {
identifier,
generic_args: _,
} => {
let ident = identifier.to_auxiliary_c();
let output = if is_output {
quote! {
*
}
} else {
TokenStream2::default()
};
quote! {
#ident #output
}
}
Type::Function { inputs, output } => {
// We cannot return a function pointer as of yet, thus guard against it
assert_eq!(is_output, false);
let output = if let Some(output) = output {
output.to_auxiliary_c(false)
} else {
quote! {
void
}
};
let inputs: Vec<TokenStream2> =
inputs.iter().map(NamedType::to_auxiliary_c).collect();
quote! {
#output (*name) (#(#inputs),*)
}
}
}
}
}

View File

@ -0,0 +1,14 @@
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use crate::parser::command_spec::NamedType;
impl NamedType {
pub fn to_auxiliary_c(&self) -> TokenStream2 {
let ident = self.name.to_rust();
let c_type = self.r#type.to_auxiliary_c(false);
quote! {
#c_type #ident
}
}
}

View File

@ -0,0 +1 @@
mod c;

View File

@ -0,0 +1,47 @@
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use crate::parser::command_spec::{DocIdentifier, Enumeration};
impl Enumeration {
pub fn to_c(&self) -> TokenStream2 {
let ident = &self.identifier.to_c();
let doc_comments: TokenStream2 = self
.attributes
.iter()
.map(|attr| attr.to_rust(&self.identifier))
.collect();
let states: Vec<TokenStream2> = self.states.iter().map(DocIdentifier::to_rust).collect();
let paired_type = {
let path = &self
.identifier
.variant
.to_rust_path()
.expect("This should always be some for enums");
let ident = &self.identifier.to_rust();
if path.is_empty() {
quote! {
crate :: #ident
}
} else {
quote! {
#path :: #ident
}
}
};
let convertible = &self.derive_from_c_paried_rust(&paired_type);
quote! {
#doc_comments
#[allow(non_camel_case_types)]
#[repr(C)]
#[derive(Debug)]
pub enum #ident {
#(#states),*
}
#convertible
}
}
}

View File

@ -0,0 +1,146 @@
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()
// FIXME(@soispha): This was `named_type_to_rust_trixy` <2024-03-25>
.map(NamedType::to_rust)
.collect();
let callback_function = format_ident!("{}", config.callback_function);
let command_value: TokenStream2 = self.identifier.to_c_with_path(&namespaces);
if let Some(r#type) = &self.output {
let 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();
#callback_function (#command_value);
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 {
quote! {
#[no_mangle]
pub extern "C" fn #ident(#(#inputs),*) -> core::ffi::c_int {
#callback_function (#command_value);
return 1;
}
}
}
}
/// 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(NamedType::to_rust_assignment, |_| {
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 = 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 )
}
}
}

View File

@ -0,0 +1,26 @@
use proc_macro2::TokenStream as TokenStream2;
use quote::{format_ident, ToTokens};
use crate::parser::command_spec::Identifier;
impl Identifier {
pub fn to_c_with_path(&self, namespaces: &[&Identifier]) -> TokenStream2 {
let namespace_str = namespaces.iter().fold(String::default(), |acc, nasp| {
if acc.is_empty() {
nasp.name.clone()
} else {
format!("{}_{}", acc, nasp.name)
}
});
if namespace_str.is_empty() {
format_ident!("{}", self.name).to_token_stream()
} else {
format_ident!("{}_{}", namespace_str, self.name).to_token_stream()
}
}
pub fn to_c(&self) -> TokenStream2 {
format_ident!("{}_c", self.name).to_token_stream()
}
}

View File

@ -0,0 +1,15 @@
mod enumeration;
mod function;
mod identifier;
mod namespace;
mod structure;
mod r#type;
mod variant;
// pub use enumeration::*;
// pub use function::*;
// pub use identifier::*;
// pub use namespace::*;
// pub use r#type::*;
// pub use structure::*;
// pub use variant::*;

View File

@ -0,0 +1,36 @@
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use crate::{
macros::config::trixy::TrixyConfig,
parser::command_spec::{Enumeration, Identifier, Namespace, Structure},
};
impl Namespace {
pub fn to_c(&self, config: &TrixyConfig, namespaces: &Vec<&Identifier>) -> TokenStream2 {
let ident = &self.name.to_c();
let mut namespaces = namespaces.clone();
namespaces.push(&self.name);
let functions: TokenStream2 = self
.functions
.iter()
.map(|r#fn| r#fn.to_c(&config, &namespaces))
.collect();
let additional_functions: TokenStream2 = self
.namespaces
.iter()
.map(|nasp| nasp.to_c(&config, &namespaces))
.collect();
let structures: TokenStream2 = self.structures.iter().map(Structure::to_c).collect();
let enumerations: TokenStream2 = self.enumerations.iter().map(Enumeration::to_c).collect();
quote! {
pub mod #ident {
#enumerations
#structures
}
#functions
#additional_functions
}
}
}

View File

@ -0,0 +1,28 @@
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use crate::parser::command_spec::{DocNamedType, Structure};
impl Structure {
pub fn to_c(&self) -> TokenStream2 {
let ident = &self.identifier.to_c();
let doc_comments: TokenStream2 = self
.attributes
.iter()
.map(|attr| attr.to_rust(&self.identifier))
.collect();
let contents: Vec<TokenStream2> = self.contents.iter().map(DocNamedType::to_c).collect();
// TODO: Convertible <2024-03-08>
quote! {
#doc_comments
#[allow(non_camel_case_types)]
#[repr(C)]
#[derive(Debug)]
pub struct #ident {
#(#contents),*
}
}
}
}

View File

@ -0,0 +1,19 @@
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use crate::parser::command_spec::{DocNamedType, NamedType};
impl DocNamedType {
pub fn to_c(&self) -> TokenStream2 {
let doc_comments: TokenStream2 = self
.attributes
.iter()
.map(|attr| attr.to_rust(&self.name))
.collect();
let named_type = &Into::<NamedType>::into(self).to_c();
quote! {
#doc_comments
pub #named_type
}
}
}

View File

@ -0,0 +1,100 @@
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use crate::parser::command_spec::{Identifier, NamedType, Type};
mod doc_named_type;
mod named_type;
// pub use doc_named_type::*;
// pub use named_type::*;
impl Type {
pub fn to_c(&self) -> TokenStream2 {
match self {
Type::Typical {
identifier,
generic_args,
} => Type::to_c_typical(&identifier, &generic_args),
Type::Function { inputs, output } => Type::to_c_function(&inputs, &output),
}
}
pub fn to_c_function(inputs: &[NamedType], output: &Option<Box<Type>>) -> TokenStream2 {
Type::to_rust_function(&inputs, &output)
}
fn to_c_typical(identifier: &Identifier, generic_args: &Vec<Type>) -> TokenStream2 {
let trixy_build_in_types: Vec<&str> = crate::types::BASE_TYPES
.iter()
.filter_map(|(name, _)| {
if name == &identifier.name {
Some(*name)
} else {
None
}
})
.collect();
if trixy_build_in_types.is_empty() {
// The types was specified in the api.tri file
let ident = identifier.to_c();
let namespaces_path = &identifier.variant.to_c_path();
let nasp_path = if namespaces_path.is_empty() {
quote! {
crate ::
}
} else {
let path = namespaces_path;
quote! {
#path ::
}
};
quote! {
#nasp_path #ident
}
} else {
debug_assert_eq!(trixy_build_in_types.len(), 1);
let type_name = trixy_build_in_types
.first()
.expect("The names should not be dublicated, this should be the only value");
match *type_name {
"Result" => {
let ident_ok = &generic_args.first().expect("This is a result").to_c();
let ident_err = &generic_args.last().expect("This is a result").to_c();
quote! {
// eg: <Result<TrainedDog, TrainingMistake> as Convertible>::Ptr,
<Result<#ident_ok, #ident_err> as Convertible>::Ptr
}
}
"Option" => {
let value = generic_args
.first()
.expect("An option does only have one arg")
.to_rust();
quote! {
*const #value
}
}
_ => {
let ident = &identifier.to_rust();
let generics: TokenStream2 = {
let generics: Vec<TokenStream2> =
generic_args.iter().map(|val| val.to_c()).collect();
quote! {
<#(#generics),*>
}
};
quote! {
trixy::types:: #ident #generics
}
}
}
}
}
}

View File

@ -0,0 +1,33 @@
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use crate::parser::command_spec::NamedType;
impl NamedType {
pub fn to_c(&self) -> TokenStream2 {
let ident = self.name.to_rust();
let r#type = self.r#type.to_c();
quote! {
#ident : #r#type
}
}
pub fn to_rust_c_assignment(&self) -> TokenStream2 {
let ident = self.name.to_rust();
let type_ident = self.r#type.to_c();
quote! {
#ident : #type_ident
}
}
pub fn to_rust_assignment(&self) -> TokenStream2 {
let ident = self.name.to_rust();
quote! {
#ident : match #ident.try_into() {
Ok(ok) => ok,
Err(err) => {
trixy::types::traits::errno::set(err);
return 0;
}
}
}
}
}

View File

@ -0,0 +1,36 @@
use proc_macro2::TokenStream as TokenStream2;
use crate::parser::command_spec::{Identifier, Namespace, Variant};
impl Variant {
pub fn to_c_path(&self) -> TokenStream2 {
fn mangle_namespace_name(vec: &Vec<Identifier>) -> Vec<Identifier> {
vec.into_iter()
.map(|ident| {
if "<root>" == &ident.name && ident.variant == Variant::RootNamespace {
// The [`namespaces_to_path_expansive`] function already deals with
// [`RootNamespace`] variants, so we just leave that as is
ident.clone()
} else {
Identifier {
name: ident.to_c().to_string(),
variant: ident.variant.clone(),
}
}
})
.collect()
}
let main_namespace;
match self {
Variant::Structure { namespace } => {
main_namespace = mangle_namespace_name(namespace);
}
Variant::Enumeration { namespace } => {
main_namespace = mangle_namespace_name(namespace);
}
_ => unreachable!("This should never be called"),
}
Namespace::to_rust_path_owned(&main_namespace[..])
}
}

View File

@ -0,0 +1,2 @@
pub mod rust;
pub mod c;

View File

@ -0,0 +1,22 @@
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use crate::parser::command_spec::{Attribute, Identifier};
impl Attribute {
pub fn to_rust(&self, _target: &Identifier) -> TokenStream2 {
match self {
Attribute::doc(comment) => quote! {
#[doc = #comment]
},
Attribute::error => quote! {
// We simply use thiserror here
#[derive(trixy::__private::thiserror::Error)]
},
Attribute::msg(msg) => quote! {
#[error(#msg)]
},
Attribute::derive(_) => unimplemented!("Derive is not used as of now"),
}
}
}

View File

@ -0,0 +1,48 @@
use proc_macro2::TokenStream as TokenStream2;
use quote::{quote, ToTokens};
use crate::parser::command_spec::{Enumeration, Identifier};
impl Enumeration {
fn derive_from_paired(
&self,
ident_self: &TokenStream2,
ident_paired: &TokenStream2,
paired_type: &TokenStream2,
) -> TokenStream2 {
let match_lines: TokenStream2 = self
.states
.iter()
.map(|state| {
let name = Into::<Identifier>::into(state).to_rust();
quote! {
#ident_paired :: #name => Self :: #name,
}
})
.collect();
quote! {
impl From<#paired_type> for #ident_self {
fn from(value: #paired_type) -> Self {
match value {
#match_lines
}
}
}
}
}
/// This function generates the `From<paired_type>` trait implementation for a given c enumeration
pub fn derive_from_c_paried_rust(&self, paired_type: &TokenStream2) -> TokenStream2 {
let c_ident = &self.identifier.to_c();
let ident = &self.identifier.to_rust();
self.derive_from_paired(&c_ident.into_token_stream(), &ident, &paired_type)
}
/// This function generates the `From<paired_type>` trait implementation for a given rust enumeration
pub fn derive_from_rust_paried_c(&self, paired_type: &TokenStream2) -> TokenStream2 {
let c_ident = &self.identifier.to_c();
let ident = &self.identifier.to_rust();
self.derive_from_paired(&ident, &c_ident.into_token_stream(), &paired_type)
}
}

View File

@ -0,0 +1,2 @@
pub mod structure;
pub mod enumeration;

View File

@ -0,0 +1,68 @@
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use crate::parser::command_spec::Structure;
impl Structure {
/// This function generates the `Convertible` implementation for a structure
// was: `structure_convertable_derive`
pub fn derive_convertible(&self, paired_type: &TokenStream2) -> TokenStream2 {
let ident = self.identifier.to_rust();
let into_fields: TokenStream2 = self
.contents
.iter()
.map(|con| {
let ident = &con.name.to_rust();
quote! {
#ident: self.#ident.into(),
}
})
.collect();
quote! {
impl trixy::types::traits::convert_trait::Convertible for #ident {
type Ptr = #paired_type;
fn into_ptr(self) -> Self::Ptr {
Self::Ptr {
#into_fields
}
}
fn from_ptr(ptr: Self::Ptr) -> Result<Self, trixy::types::error::TypeConversionError> {
todo!()
}
}
}
}
/// This function generates the `TryFrom<paired_type>` trait implementation for a given structure
// was: `structure_into_impl`
pub fn derive_into_impl(&self, paired_type: &TokenStream2) -> TokenStream2 {
let ident = self.identifier.to_rust();
let try_into_fields: TokenStream2 = self
.contents
.iter()
.map(|con| {
let ident = &con.name.to_rust();
quote! {
#ident: value.#ident.try_into()?,
}
})
.collect();
quote! {
impl TryFrom<#paired_type> for #ident {
type Error = trixy::types::error::TypeConversionError;
fn try_from(value: #paired_type) -> Result<Self, Self::Error> {
Ok(Self {
#try_into_fields
})
}
}
}
}
}

View File

@ -0,0 +1,45 @@
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use crate::parser::command_spec::{DocIdentifier, Enumeration};
impl Enumeration {
pub fn to_rust(&self) -> TokenStream2 {
let doc_comments: TokenStream2 = self
.attributes
.iter()
.map(|attr| attr.to_rust(&self.identifier))
.collect();
let ident = self.identifier.to_rust();
let states: Vec<TokenStream2> = self.states.iter().map(DocIdentifier::to_rust).collect();
let paired_type = {
let path = self.identifier.variant.to_c_path();
let ident = self.identifier.to_c();
if path.is_empty() {
quote! {
crate :: #ident
}
} else {
quote! {
#path :: #ident
}
}
};
// was: `rust_enumeration_into_impl`
let convertible = self.derive_from_rust_paried_c(&paired_type);
quote! {
#doc_comments
#[allow(non_camel_case_types)]
#[derive(Debug)]
pub enum #ident {
#(#states),*
}
#convertible
}
}
}

View File

@ -0,0 +1,61 @@
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use crate::parser::command_spec::{Function, Identifier, NamedType, Type};
impl Function {
// was called function_identifier_to_rust
pub fn to_rust_identifier<F>(
&self,
input_fmt_fn: fn(&NamedType) -> TokenStream2,
output_fmt_fn: F,
) -> TokenStream2
where
F: Fn(&Type) -> TokenStream2,
{
let ident = self.identifier.to_rust();
let inputs: Vec<TokenStream2> = self.inputs.iter().map(input_fmt_fn).collect();
let output = &self.output;
if inputs.is_empty() && output.is_none() {
quote! {
#ident
}
} else if output.is_some() {
let output = output_fmt_fn(&output.as_ref().expect("We checked"));
quote! {
#ident {
trixy_output: #output ,
#(#inputs),*
}
}
} else if output.is_none() && !inputs.is_empty() {
quote! {
#ident { #(#inputs),* }
}
} else {
unreachable!("All other conditions should be met")
}
}
pub fn to_rust(&self, _namespaces: &[&Identifier]) -> TokenStream2 {
let doc_comments: TokenStream2 = self
.attributes
.iter()
.map(|attr| attr.to_rust(&self.identifier))
.collect();
let function_ident = self.to_rust_identifier(NamedType::to_rust, move |r#type| {
let ident = r#type.to_c();
quote! {
trixy::oneshot::Sender<#ident>
}
});
quote! {
#doc_comments
#[allow(non_camel_case_types)]
#function_ident
}
}
}

View File

@ -0,0 +1,20 @@
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use crate::parser::command_spec::{DocIdentifier, Identifier};
impl DocIdentifier {
pub fn to_rust(&self) -> TokenStream2 {
let attributes: TokenStream2 = self
.attributes
.iter()
.map(|attr| attr.to_rust(&Into::<Identifier>::into(self)))
.collect();
let identifier = Into::<Identifier>::into(self).to_rust();
quote! {
#attributes
#identifier
}
}
}

View File

@ -0,0 +1,29 @@
use convert_case::{Case, Casing};
use proc_macro2::TokenStream as TokenStream2;
use quote::{format_ident, quote};
use crate::parser::command_spec::Identifier;
mod doc_identifier;
// pub use doc_identifier::*;
impl Identifier {
pub fn to_rust(&self) -> TokenStream2 {
if self.name == "()" {
quote! {
()
}
} else {
let ident = format_ident!("{}", self.name);
quote! {
#ident
}
}
}
pub fn to_rust_pascalized(&self) -> TokenStream2 {
let pascal_ident = format_ident!("{}", self.name.to_case(Case::Pascal));
quote! {
#pascal_ident
}
}
}

View File

@ -0,0 +1,19 @@
mod attribute;
mod enumeration;
mod function;
mod identifier;
mod namespace;
mod structure;
mod r#type;
mod variant;
// pub use attribute::*;
// pub use enumeration::*;
// pub use function::*;
// pub use identifier::*;
// pub use namespace::*;
// pub use r#type::*;
// pub use structure::*;
// pub use variant::*;
pub mod derive;

View File

@ -0,0 +1,2 @@
pub mod path;
pub mod module;

View File

@ -0,0 +1,59 @@
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use crate::parser::command_spec::{Enumeration, Identifier, Namespace, Structure};
impl Namespace {
pub fn to_rust_module(&self, namespaces: &Vec<&Identifier>) -> TokenStream2 {
let mut namespaces = namespaces.clone();
namespaces.push(&self.name);
let ident = self.name.to_rust();
let enum_ident = self.name.to_rust_pascalized();
let doc_comments: TokenStream2 = self
.attributes
.iter()
.map(|attr| attr.to_rust(&self.name))
.collect();
let structures: TokenStream2 = self.structures.iter().map(Structure::to_rust).collect();
let enumerations: TokenStream2 =
self.enumerations.iter().map(Enumeration::to_rust).collect();
let functions: Vec<TokenStream2> = self
.functions
.iter()
.map(|r#fn| r#fn.to_rust(&namespaces))
.collect();
let namespace_modules: Vec<TokenStream2> = self
.namespaces
.iter()
.map(Self::to_rust_module_enum)
.collect();
let namespaces: TokenStream2 = self
.namespaces
.iter()
.map(|nasp| nasp.to_rust_module(&namespaces))
.collect();
quote! {
#doc_comments
pub mod #ident {
#structures
#enumerations
#[derive(Debug)]
pub enum #enum_ident {
#(#functions,)*
#(#namespace_modules),*
}
#namespaces
}
}
}
pub fn to_rust_module_enum(&self) -> TokenStream2 {
let pascal_ident = self.name.to_rust_pascalized();
let ident = self.name.to_rust();
quote! {
#pascal_ident(#ident :: #pascal_ident)
}
}
}

View File

@ -0,0 +1,65 @@
use std::ops::Deref;
use proc_macro2::TokenStream as TokenStream2;
use quote::{format_ident, quote};
use crate::parser::command_spec::{Identifier, Namespace, Variant};
impl Namespace {
pub fn to_rust_path<T: Deref<Target = Identifier>>(namespaces: &[T]) -> TokenStream2 {
namespaces
.iter()
.fold(TokenStream2::default(), |acc, nasp| {
if nasp.variant == Variant::RootNamespace && nasp.name == "<root>" {
if acc.is_empty() {
quote! {
crate
}
} else {
unreachable!("An root namespace can never come, after another namespcae")
}
} else {
let ident = format_ident!("{}", nasp.name);
if acc.is_empty() {
quote! {
crate :: #ident
}
} else {
quote! {
#acc :: #ident
}
}
}
})
}
pub fn to_rust_path_owned(namespaces: &[Identifier]) -> TokenStream2 {
// PERFORMANCE(@soispha): Reallocating a new vector here can't really be the most efficient
// way to do this <2024-03-25>
let vec: Vec<&Identifier> = namespaces.iter().map(|val| val).collect();
Namespace::to_rust_path(&vec)
// namespaces
// .iter()
// .fold(TokenStream2::default(), |acc, nasp| {
// if nasp.variant == Variant::RootNamespace && nasp.name == "<root>" {
// if acc.is_empty() {
// quote! {
// crate
// }
// } else {
// unreachable!("An root namespace can never come, after another namespcae")
// }
// } else {
// let ident = format_ident!("{}", nasp.name);
// if acc.is_empty() {
// quote! {
// crate :: #ident
// }
// } else {
// quote! {
// #acc :: #ident
// }
// }
// }
// })
}
}

View File

@ -0,0 +1,43 @@
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use crate::parser::command_spec::{DocNamedType, Structure};
impl Structure {
pub fn to_rust(&self) -> TokenStream2 {
let doc_comments: TokenStream2 = self
.attributes
.iter()
.map(|attr| attr.to_rust(&self.identifier))
.collect();
let ident = &self.identifier.to_rust();
let c_ident = {
let path = &self.identifier.variant.to_c_path();
let ident = &self.identifier.to_c();
if path.is_empty() {
quote! {
crate :: #ident
}
} else {
quote! {
#path :: #ident
}
}
};
let contents: Vec<TokenStream2> = self.contents.iter().map(DocNamedType::to_rust).collect();
let convertible = self.derive_convertible(&c_ident);
let into_impl = self.derive_into_impl(&c_ident);
quote! {
#doc_comments
#[allow(non_camel_case_types)]
#[derive(Debug)]
pub struct #ident {
#(#contents),*
}
#convertible
#into_impl
}
}
}

View File

@ -0,0 +1,19 @@
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use crate::parser::command_spec::{DocNamedType, NamedType};
impl DocNamedType {
pub fn to_rust(&self) -> TokenStream2 {
let attributes: TokenStream2 = self
.attributes
.iter()
.map(|attr| attr.to_rust(&self.name))
.collect();
let named_type = Into::<NamedType>::into(self).to_rust();
quote! {
#attributes
pub #named_type
}
}
}

View File

@ -0,0 +1,67 @@
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use crate::parser::command_spec::{Identifier, NamedType, Type, Variant};
mod doc_named_type;
mod named_type;
// pub use doc_named_type::*;
// pub use named_type::*;
impl Type {
pub fn to_rust(&self) -> TokenStream2 {
match self {
Type::Typical {
identifier,
generic_args,
} => Self::to_rust_typical(&identifier, &generic_args),
Type::Function { inputs, output } => Self::to_rust_function(&inputs, &output),
}
}
pub fn to_rust_function(inputs: &[NamedType], output: &Option<Box<Type>>) -> TokenStream2 {
let inputs: Vec<TokenStream2> = inputs.iter().map(NamedType::to_rust).collect();
let output = if let Some(output) = output {
let output = output.to_rust();
quote! {
-> #output
}
} else {
TokenStream2::default()
};
quote! {
fn(#(#inputs),*) #output
}
}
pub fn to_rust_typical(identifier: &Identifier, generic_args: &Vec<Type>) -> TokenStream2 {
let ident = identifier.to_rust();
let namespaces_path = Variant::to_rust_path(&identifier.variant);
let nasp_path = if let Some(nasp_path) = Variant::to_rust_path(&identifier.variant) {
if nasp_path.is_empty() {
quote! {
crate ::
}
} else {
let path = namespaces_path;
quote! {
#path ::
}
}
} else {
quote! {}
};
if generic_args.is_empty() {
quote! {
#nasp_path #ident
}
} else {
let generics: Vec<TokenStream2> = generic_args.iter().map(Type::to_rust).collect();
quote! {
#nasp_path #ident <#(#generics),*>
}
}
}
}

View File

@ -0,0 +1,14 @@
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use crate::parser::command_spec::NamedType;
impl NamedType {
pub fn to_rust(&self) -> TokenStream2 {
let ident = self.name.to_rust();
let r#type = self.r#type.to_rust();
quote! {
#ident : #r#type
}
}
}

View File

@ -0,0 +1,25 @@
use proc_macro2::TokenStream as TokenStream2;
use crate::parser::command_spec::{Identifier, Namespace, Variant};
impl Variant {
// was named `type_variant_rust_path`
pub fn to_rust_path(&self) -> Option<TokenStream2> {
fn namespace_to_borrowed(vec: &Vec<Identifier>) -> Vec<&Identifier> {
let vec_2: Vec<&Identifier> = vec.iter().collect();
vec_2
}
let main_namespace;
match self {
Variant::Structure { namespace } => {
main_namespace = namespace_to_borrowed(namespace);
}
Variant::Enumeration { namespace } => {
main_namespace = namespace_to_borrowed(namespace);
}
_ => return None,
}
Some(Namespace::to_rust_path(&main_namespace[..]))
}
}

View File

@ -0,0 +1,2 @@
pub mod host;
pub mod auxiliary;

View File

@ -0,0 +1,74 @@
/*
* Copyright (C) 2023 - 2024:
* The Trinitrix Project <soispha@vhack.eu, antifallobst@systemausfall.org>
*
* 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 Lesser 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 Lesser GNU General Public License along with this program.
* If not, see <https://www.gnu.org/licenses/>.
*/
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use crate::{
macros::config::trixy::TrixyConfig,
parser::command_spec::{CommandSpec, Enumeration, Structure},
};
/// This function generates the main c API provided by Trixy.
/// This works for example like this:
/// Turning this:
/// ```text
/// nasp trinitrix {
/// struct Callback {
/// func: String,
/// timeout: String,
/// };
///
/// enum CallbackPriority {
/// High,
/// Medium,
/// Low,
/// };
///
/// fn execute_callback(callback: Callback, priority: CallbackPriority);
/// }
/// ```
/// to this:
/// ```no_run
/// pub extern "C" fn exectute_callback(callback: Callback, priority: CallbackPriority) {
/// /* Here we simply call your handler function, with the command of the function */
/// }
/// ```
pub fn generate(trixy: &CommandSpec, config: &TrixyConfig) -> TokenStream2 {
let functions: TokenStream2 = trixy
.functions
.iter()
.map(|r#fn| r#fn.to_c(&config, &vec![]))
.collect();
let namespaced_functions: TokenStream2 = trixy
.namespaces
.iter()
.map(|nasp| nasp.to_c(&config, &vec![]))
.collect();
let structures: TokenStream2 = trixy.structures.iter().map(Structure::to_c).collect();
let enumerations: TokenStream2 = trixy.enumerations.iter().map(Enumeration::to_c).collect();
quote! {
#enumerations
#structures
#functions
#namespaced_functions
}
}

View File

@ -19,20 +19,23 @@
* If not, see <https://www.gnu.org/licenses/>. * If not, see <https://www.gnu.org/licenses/>.
*/ */
/// other doc comment use crate::{
fn hi(name: String) -> String; macros::{config::trixy::TrixyConfig, generate::host::format_rust},
parser::command_spec::CommandSpec,
};
/// struct comment pub mod host;
struct ho {
ident: String, pub fn generate(trixy: &CommandSpec, config: &TrixyConfig) -> String {
/// also a doc comment let host_rust_code = host::generate(&trixy, &config);
codebase: Vec<String>,
let rust_code = format_rust(host_rust_code);
format!(
"
/* c API */
{}
",
rust_code
)
} }
/// Some doc comment
mod trinitrix {
fn hi(name: String) -> String;
}
// That's a flat out lie, but it results in a rather nice syntax highlight compared to nothing:
// vim: syntax=rust

View File

@ -0,0 +1,34 @@
//! Host files that are included in the final binary
use proc_macro2::TokenStream as TokenStream2;
use crate::{
macros::{config::trixy::TrixyConfig, VIM_LINE_RUST},
parser::command_spec::CommandSpec,
};
pub mod c;
pub mod rust;
pub fn format_rust(input: TokenStream2) -> String {
prettyplease::unparse(
&syn::parse2(input).expect("This code was generated, it should also be parsable"),
)
}
pub fn generate(trixy: &CommandSpec, config: &TrixyConfig) -> String {
let rust_host = rust::generate(trixy, config);
let c_host = c::generate(trixy, config);
format!(
"
// Host code {{{{{{
{}
/* C API */
{}
// }}}}}}
{}
",
rust_host, c_host, VIM_LINE_RUST
)
}

View File

@ -0,0 +1,114 @@
/*
* Copyright (C) 2023 - 2024:
* The Trinitrix Project <soispha@vhack.eu, antifallobst@systemausfall.org>
*
* 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 Lesser 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 Lesser GNU General Public License along with this program.
* If not, see <https://www.gnu.org/licenses/>.
*/
//! This module is responsible for generating the rust code used to interface with the api.
//! That includes the structs and enums declared in the trixy file and the enum used to describe the
//! command being executed.
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use crate::{
macros::config::trixy::TrixyConfig,
parser::command_spec::{CommandSpec, Enumeration, Namespace, Structure},
};
/// This function turns, for example, the following trixy input into this rust code:
/// ```text
/// nasp trinitrix {
/// struct Callback {
/// func: String,
/// timeout: String,
/// };
///
/// enum CallbackPriority {
/// High,
/// Medium,
/// Low,
/// };
///
/// fn execute_callback(callback: Callback, priority: CallbackPriority) -> String;
/// }
/// ```
/// ```no_run
/// #[derive(Debug)]
/// pub enum Commands {
/// Trinitrix(trinitrix::Trinitrix),
/// }
/// pub mod trinitrix {
/// #[allow(non_camel_case_types)]
/// #[derive(Debug)]
/// struct Callback {
/// func: String,
/// timeout: String,
/// }
/// #[allow(non_camel_case_types)]
/// #[derive(Debug)]
/// enum CallbackPriority {
/// High,
/// Medium,
/// Low,
/// }
/// #[derive(Debug)]
/// pub enum Trinitrix {
/// #[allow(non_camel_case_types)]
/// execute_callback {
/// callback: Callback,
/// priority: CallbackPriority,
/// trixy_output: trixy::oneshot::channel<String>
/// },
/// }
/// }
/// ```
pub fn generate(trixy: &CommandSpec, _config: &TrixyConfig) -> TokenStream2 {
let modules: TokenStream2 = trixy
.namespaces
.iter()
.map(|nasp| nasp.to_rust_module(&vec![]))
.collect();
let structures: TokenStream2 = trixy.structures.iter().map(Structure::to_rust).collect();
let enumerations: TokenStream2 = trixy
.enumerations
.iter()
.map(Enumeration::to_rust)
.collect();
let functions: Vec<TokenStream2> = trixy
.functions
.iter()
.map(|r#fn| r#fn.to_rust(&[]))
.collect();
let namespace_modules: Vec<TokenStream2> = trixy
.namespaces
.iter()
.map(Namespace::to_rust_module_enum)
.collect();
quote! {
#structures
#enumerations
#[derive(Debug)]
pub enum Commands {
#(#functions,)*
#(#namespace_modules),*
}
#modules
}
}

View File

@ -19,20 +19,22 @@
* If not, see <https://www.gnu.org/licenses/>. * If not, see <https://www.gnu.org/licenses/>.
*/ */
mod trinitrix { use crate::parser::command_spec::CommandSpec;
struct Callback {
func: Function,
timeout: Integer,
}
enum CallbackPriority { use crate::macros::{config::trixy::TrixyConfig, generate::host::format_rust};
High,
Medium,
Low,
}
fn execute_callback(callback: Callback, priority: CallbackPriority); pub mod host;
}
// That's a flat out lie, but it results in a rather nice syntax highlight compared to nothing: pub fn generate(trixy: &CommandSpec, config: &TrixyConfig) -> String {
// vim: syntax=rust let host_rust_code = host::generate(&trixy, &config);
let rust_code = format_rust(host_rust_code);
format!(
"
/* Rust API */
{}
",
rust_code
)
}

View File

@ -0,0 +1,3 @@
pub mod host;
pub mod auxiliary;
pub mod convert;

123
src/macros/mod.rs Normal file
View File

@ -0,0 +1,123 @@
/*
* Copyright (C) 2023 - 2024:
* The Trinitrix Project <soispha@vhack.eu, antifallobst@systemausfall.org>
*
* 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 Lesser 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 Lesser GNU General Public License along with this program.
* If not, see <https://www.gnu.org/licenses/>.
*/
use std::{
fs, iter,
path::{Path, PathBuf},
};
use crate::parser::parse_trixy_lang;
use crate::types::C_TYPE_HEADER;
use config::{
file_tree::{FileTree, GeneratedFile},
trixy::Language,
};
use self::config::trixy::TrixyConfig;
pub mod config;
mod generate;
const VIM_LINE_RUST: &'static str = "// vim: filetype=rust\n";
const VIM_LINE_C: &'static str = "// vim: filetype=c\n";
impl TrixyConfig {
/// This is the heart of the trixy crate.
/// It's main purpose is to generate, what you have asked for in the [`TrixyConfig`] and to
/// return this generated code as a [`FileTree`].
///
/// # Panic
///
/// Beware that this function will panic on errors, as it is written with the expectation that
/// it's run from a `build.rs` file. If you want to avoid that, usage of the raw generation
/// functions is guaranteed to never panic.
///
/// # Examples
///
/// ```
/// use crate::macros::TrixyConfig;
///
/// let trixy_config = TrixyConfig::new("some_callback_function")
/// .trixy_path("./path/to/api/file.tri")
/// /* other config settings */;
/// trixy_config.generate();
/// ```
pub fn generate(&self) -> FileTree {
let mut file_tree = FileTree::new();
let source_code = fs::read_to_string(&self.trixy_path).unwrap_or_else(|err| {
panic! {"Can't read file at path: '{}'. The Error was: '{}'", self.trixy_path.display(), err};
});
let trixy_code = parse_trixy_lang(&source_code).unwrap_or_else(|err| {
panic! {"Parsing of the trixy file failed: \n{}", err}
});
// host code
let host_code = generate::host::generate(&trixy_code, &self);
file_tree.add_host_file(GeneratedFile::new_in_out_dir(
self.host_code_name.clone(),
host_code,
Language::Rust,
&self.out_dir,
));
// auxiliary code
let c_header = generate::auxiliary::generate(&trixy_code, &self);
if let Some(dist_dir) = &self.dist_dir_path {
let c_header_dist =
PathBuf::from(format!("{}/{}", dist_dir.display(), &self.c_header_name));
file_tree.add_auxiliary_file(GeneratedFile::new(c_header_dist, c_header, Language::C));
// TODO(@soispha): Is this even necessary? <2024-03-25>
let (interface_name, interface_content) = {
let interface_header = format!(
"\
/* This file is automatcially generated by Trixy */ \n\
#ifndef TRIXY_INTERFACE_H \n\
#define TRIXY_INTERFACE_H \n\
#include \"{}\" \n\
#endif // TRIXY_INTERFACE_H \n\
",
&self.c_header_name
);
("interface.h", interface_header)
};
C_TYPE_HEADER
.iter()
.chain(iter::once(&(interface_name, &interface_content[..])))
.for_each(|(name, content)| {
let path: &Path = &Path::new(name);
let header_path =
PathBuf::from(format!("{}/{}", dist_dir.display(), path.display()));
file_tree.add_auxiliary_file(GeneratedFile::new(
header_path,
content.to_string(),
Language::C,
));
});
}
file_tree
}
}

View File

@ -23,7 +23,7 @@
use std::fmt::{Display, Write}; use std::fmt::{Display, Write};
use crate::lexing::TokenKind; use crate::parser::lexing::TokenKind;
use super::unchecked::{self, DeriveValue}; use super::unchecked::{self, DeriveValue};

View File

@ -25,9 +25,9 @@
use std::fmt::{Display, Write}; use std::fmt::{Display, Write};
use crate::lexing::{Token, TokenKind, TokenSpan}; use crate::parser::lexing::{Token, TokenKind, TokenSpan};
#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord)] #[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Clone)]
pub struct CommandSpec { pub struct CommandSpec {
pub structures: Vec<Structure>, pub structures: Vec<Structure>,
pub enumerations: Vec<Enumeration>, pub enumerations: Vec<Enumeration>,
@ -189,7 +189,7 @@ pub enum Type {
impl Display for NamedType { impl Display for NamedType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let ident = match self.name.kind() { let ident = match self.name.kind() {
crate::lexing::TokenKind::Identifier(ident) => ident, crate::parser::lexing::TokenKind::Identifier(ident) => ident,
_ => panic!("Tried to display a non identifier token in the Type display implementation. This is a bug"), _ => panic!("Tried to display a non identifier token in the Type display implementation. This is a bug"),
}; };
f.write_str(ident)?; f.write_str(ident)?;
@ -206,7 +206,7 @@ impl Display for Type {
generic_args, generic_args,
} => { } => {
let ident = match identifier.kind() { let ident = match identifier.kind() {
crate::lexing::TokenKind::Identifier(ident) => ident, crate::parser::lexing::TokenKind::Identifier(ident) => ident,
_ => panic!("Tried to display a non identifier token in the Type display implementation. This is a bug"), _ => panic!("Tried to display a non identifier token in the Type display implementation. This is a bug"),
}; };

View File

@ -23,9 +23,9 @@ use core::fmt;
use thiserror::Error; use thiserror::Error;
use crate::{ use crate::parser::{
lexing::{error::SpannedLexingError, TokenSpan}, lexing::{error::SpannedLexingError, TokenSpan},
parsing::{self}, parsing,
}; };
#[derive(Error, Debug)] #[derive(Error, Debug)]

View File

@ -22,7 +22,7 @@
use std::{error::Error, fmt::Display}; use std::{error::Error, fmt::Display};
use thiserror::Error; use thiserror::Error;
use crate::error::{AdditionalHelp, ErrorContext, ErrorContextDisplay}; use crate::parser::error::{AdditionalHelp, ErrorContext, ErrorContextDisplay};
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum LexingError { pub enum LexingError {
@ -67,7 +67,7 @@ impl Error for SpannedLexingError {
impl ErrorContextDisplay for SpannedLexingError { impl ErrorContextDisplay for SpannedLexingError {
type Error = LexingError; type Error = LexingError;
fn context(&self) -> &crate::error::ErrorContext { fn context(&self) -> &crate::parser::error::ErrorContext {
&self.context &self.context
} }

View File

@ -31,7 +31,7 @@ mod tokenizer;
#[cfg(test)] #[cfg(test)]
mod test; mod test;
#[derive(Debug, PartialEq, PartialOrd, Ord, Eq)] #[derive(Debug, PartialEq, PartialOrd, Ord, Eq, Clone)]
pub struct TokenStream { pub struct TokenStream {
pub original_file: String, pub original_file: String,
tokens: Vec<Token>, tokens: Vec<Token>,
@ -308,7 +308,7 @@ impl Display for AttributeKeyword {
/// # Examples /// # Examples
/// ///
/// ``` /// ```
/// use trixy_parser::token; /// use crate::parser::token;
///# fn main() { ///# fn main() {
/// token![mod]; /// token![mod];
/// token![;]; /// token![;];
@ -317,56 +317,56 @@ impl Display for AttributeKeyword {
/// ``` /// ```
#[macro_export] #[macro_export]
macro_rules! token { macro_rules! token {
[Semicolon] => { $crate::lexing::TokenKind::Semicolon }; [Semicolon] => { $crate::parser::lexing::TokenKind::Semicolon };
[;] => { $crate::lexing::TokenKind::Semicolon }; [;] => { $crate::parser::lexing::TokenKind::Semicolon };
[Colon] => { $crate::lexing::TokenKind::Colon }; [Colon] => { $crate::parser::lexing::TokenKind::Colon };
[:] => { $crate::lexing::TokenKind::Colon }; [:] => { $crate::parser::lexing::TokenKind::Colon };
[Comma] => { $crate::lexing::TokenKind::Comma }; [Comma] => { $crate::parser::lexing::TokenKind::Comma };
[,] => { $crate::lexing::TokenKind::Comma }; [,] => { $crate::parser::lexing::TokenKind::Comma };
[Arrow] => { $crate::lexing::TokenKind::Arrow }; [Arrow] => { $crate::parser::lexing::TokenKind::Arrow };
[->] => { $crate::lexing::TokenKind::Arrow }; [->] => { $crate::parser::lexing::TokenKind::Arrow };
[PoundSign] => { $crate::lexing::TokenKind::PoundSign }; [PoundSign] => { $crate::parser::lexing::TokenKind::PoundSign };
[#] => { $crate::lexing::TokenKind::PoundSign }; [#] => { $crate::parser::lexing::TokenKind::PoundSign };
[EqualsSign] => { $crate::lexing::TokenKind::EqualsSign }; [EqualsSign] => { $crate::parser::lexing::TokenKind::EqualsSign };
[=] => { $crate::lexing::TokenKind::EqualsSign }; [=] => { $crate::parser::lexing::TokenKind::EqualsSign };
[AngledBracketOpen] => { $crate::lexing::TokenKind::AngledBracketOpen }; [AngledBracketOpen] => { $crate::parser::lexing::TokenKind::AngledBracketOpen };
[<] => { $crate::lexing::TokenKind::AngledBracketOpen }; [<] => { $crate::parser::lexing::TokenKind::AngledBracketOpen };
[AngledBracketClose] => { $crate::lexing::TokenKind::AngledBracketClose }; [AngledBracketClose] => { $crate::parser::lexing::TokenKind::AngledBracketClose };
[>] => { $crate::lexing::TokenKind::AngledBracketClose }; [>] => { $crate::parser::lexing::TokenKind::AngledBracketClose };
[CurlyBracketOpen] => { $crate::lexing::TokenKind::CurlyBracketOpen}; [CurlyBracketOpen] => { $crate::parser::lexing::TokenKind::CurlyBracketOpen};
// [{] => { $crate::lexing::TokenKind::CurlyBracketOpen }; // [{] => { $crate::parser::lexing::TokenKind::CurlyBracketOpen };
[CurlyBracketClose] => { $crate::lexing::TokenKind::CurlyBracketClose}; [CurlyBracketClose] => { $crate::parser::lexing::TokenKind::CurlyBracketClose};
// [}] => { $crate::lexing::TokenKind::CurlyBracketClose }; // [}] => { $crate::parser::lexing::TokenKind::CurlyBracketClose };
[CurvedBracketOpen] => { $crate::lexing::TokenKind::CurvedBracketOpen}; [CurvedBracketOpen] => { $crate::parser::lexing::TokenKind::CurvedBracketOpen};
// [(] => { $crate::lexing::TokenKind::ParenthesisOpen }; // [(] => { $crate::parser::lexing::TokenKind::ParenthesisOpen };
[CurvedBracketClose] => { $crate::lexing::TokenKind::CurvedBracketClose}; [CurvedBracketClose] => { $crate::parser::lexing::TokenKind::CurvedBracketClose};
// [)] => { $crate::lexing::TokenKind::ParenthesisClose }; // [)] => { $crate::parser::lexing::TokenKind::ParenthesisClose };
[SquareBracketOpen] => { $crate::lexing::TokenKind::SquareBracketOpen}; [SquareBracketOpen] => { $crate::parser::lexing::TokenKind::SquareBracketOpen};
// [[] => { $crate::lexing::TokenKind::ParenthesisOpen }; // [[] => { $crate::parser::lexing::TokenKind::ParenthesisOpen };
[SquareBracketClose] => { $crate::lexing::TokenKind::SquareBracketClose}; [SquareBracketClose] => { $crate::parser::lexing::TokenKind::SquareBracketClose};
// []] => { $crate::lexing::TokenKind::ParenthesisClose }; // []] => { $crate::parser::lexing::TokenKind::ParenthesisClose };
[mod] => { $crate::lexing::TokenKind::Keyword($crate::lexing::Keyword::r#mod) }; [mod] => { $crate::parser::lexing::TokenKind::Keyword($crate::parser::lexing::Keyword::r#mod) };
[fn] => { $crate::lexing::TokenKind::Keyword($crate::lexing::Keyword::r#fn) }; [fn] => { $crate::parser::lexing::TokenKind::Keyword($crate::parser::lexing::Keyword::r#fn) };
[struct] => { $crate::lexing::TokenKind::Keyword($crate::lexing::Keyword::r#struct) }; [struct] => { $crate::parser::lexing::TokenKind::Keyword($crate::parser::lexing::Keyword::r#struct) };
[enum] => { $crate::lexing::TokenKind::Keyword($crate::lexing::Keyword::r#enum) }; [enum] => { $crate::parser::lexing::TokenKind::Keyword($crate::parser::lexing::Keyword::r#enum) };
// The `derive` here is completely arbitrary. It is only for comparisons (see `same_kind`) // The `derive` here is completely arbitrary. It is only for comparisons (see `same_kind`)
[AttributeKeyword] => { $crate::lexing::TokenKind::AttributeKeyword($crate::lexing::AttributeKeyword::derive) }; [AttributeKeyword] => { $crate::parser::lexing::TokenKind::AttributeKeyword($crate::parser::lexing::AttributeKeyword::derive) };
// This is only works for checking for a identifier or comment // This is only works for checking for a identifier or comment
// see the `same_kind` method on TokenKind // see the `same_kind` method on TokenKind
[Ident] => { $crate::lexing::TokenKind::Identifier("".to_owned()) }; [Ident] => { $crate::parser::lexing::TokenKind::Identifier("".to_owned()) };
[Identifier] => { $crate::lexing::TokenKind::Identifier("".to_owned()) }; [Identifier] => { $crate::parser::lexing::TokenKind::Identifier("".to_owned()) };
[StringLiteral] => { $crate::lexing::TokenKind::StringLiteral("".to_owned()) }; [StringLiteral] => { $crate::parser::lexing::TokenKind::StringLiteral("".to_owned()) };
[Comment] => { $crate::lexing::TokenKind::Comment("".to_owned()) }; [Comment] => { $crate::parser::lexing::TokenKind::Comment("".to_owned()) };
} }
#[cfg(test)] #[cfg(test)]
@ -397,6 +397,6 @@ mod tests {
token_macro_test!(tok_expands_to_arrow, ->, => TokenKind::Arrow); token_macro_test!(tok_expands_to_arrow, ->, => TokenKind::Arrow);
token_macro_test!(tok_expands_to_semicolon, Semicolon, => TokenKind::Semicolon); token_macro_test!(tok_expands_to_semicolon, Semicolon, => TokenKind::Semicolon);
token_macro_test!(tok_expands_to_mod, mod, => TokenKind::Keyword(crate::lexing::Keyword::r#mod)); token_macro_test!(tok_expands_to_mod, mod, => TokenKind::Keyword(crate::parser::lexing::Keyword::r#mod));
token_macro_test!(tok_expands_to_fn, fn, => TokenKind::Keyword(crate::lexing::Keyword::r#fn)); token_macro_test!(tok_expands_to_fn, fn, => TokenKind::Keyword(crate::parser::lexing::Keyword::r#fn));
} }

View File

@ -21,7 +21,7 @@
// This code is heavily inspired by: https://michael-f-bryan.github.io/static-analyser-in-rust/book/lex.html // This code is heavily inspired by: https://michael-f-bryan.github.io/static-analyser-in-rust/book/lex.html
use crate::{ use crate::parser::{
error::ErrorContext, error::ErrorContext,
lexing::{Keyword, TokenSpan}, lexing::{Keyword, TokenSpan},
}; };

View File

@ -21,7 +21,7 @@
use error::TrixyError; use error::TrixyError;
use crate::lexing::TokenStream; use crate::parser::lexing::TokenStream;
use self::command_spec::checked::CommandSpec; use self::command_spec::checked::CommandSpec;

View File

@ -23,7 +23,7 @@ use thiserror::Error;
use std::{error::Error, fmt::Display}; use std::{error::Error, fmt::Display};
use crate::{ use crate::parser::{
command_spec::{checked::Identifier, unchecked::Attribute}, command_spec::{checked::Identifier, unchecked::Attribute},
error::{AdditionalHelp, ErrorContext, ErrorContextDisplay}, error::{AdditionalHelp, ErrorContext, ErrorContextDisplay},
lexing::TokenSpan, lexing::TokenSpan,
@ -133,7 +133,7 @@ impl Display for SpannedParsingError {
impl ErrorContextDisplay for SpannedParsingError { impl ErrorContextDisplay for SpannedParsingError {
type Error = ParsingError; type Error = ParsingError;
fn context(&self) -> &crate::error::ErrorContext { fn context(&self) -> &crate::parser::error::ErrorContext {
&self.context &self.context
} }

View File

@ -21,10 +21,10 @@
use std::{iter, mem}; use std::{iter, mem};
use crate::types::BASE_TYPES;
use convert_case::{Case, Casing}; use convert_case::{Case, Casing};
use trixy_types::BASE_TYPES;
use crate::{ use crate::parser::{
command_spec::{ command_spec::{
checked::{ checked::{
self, CommandSpec, DocIdentifier, DocNamedType, Enumeration, Function, Identifier, self, CommandSpec, DocIdentifier, DocNamedType, Enumeration, Function, Identifier,
@ -68,13 +68,13 @@ macro_rules! take_attrs {
} }
}; };
(@process_val $iden:ident, $val:ident) => { (@process_val $iden:ident, $val:ident) => {
if let $crate::command_spec::unchecked::Attribute::$val{..} = $iden { if let $crate::parser::command_spec::unchecked::Attribute::$val{..} = $iden {
return Ok($iden.into()); return Ok($iden.into());
}; };
take_attrs!{@process_val_last $iden} take_attrs!{@process_val_last $iden}
}; };
(@process_val $iden:ident, $val:ident, $($other:tt),+ $(,)*) => { (@process_val $iden:ident, $val:ident, $($other:tt),+ $(,)*) => {
if let $crate::command_spec::unchecked::Attribute::$val{..} = $iden { if let $crate::parser::command_spec::unchecked::Attribute::$val{..} = $iden {
return Ok($iden.into()); return Ok($iden.into());
}; };
take_attrs!{@process_val $iden, $($other),*} take_attrs!{@process_val $iden, $($other),*}

View File

@ -22,7 +22,7 @@
use std::{error::Error, fmt::Display}; use std::{error::Error, fmt::Display};
use thiserror::Error; use thiserror::Error;
use crate::{ use crate::parser::{
command_spec::unchecked::{Attribute, StringLiteral}, command_spec::unchecked::{Attribute, StringLiteral},
error::{AdditionalHelp, ErrorContext, ErrorContextDisplay}, error::{AdditionalHelp, ErrorContext, ErrorContextDisplay},
lexing::{TokenKind, TokenSpan}, lexing::{TokenKind, TokenSpan},
@ -113,7 +113,7 @@ impl Display for SpannedParsingError {
impl ErrorContextDisplay for SpannedParsingError { impl ErrorContextDisplay for SpannedParsingError {
type Error = ParsingError; type Error = ParsingError;
fn context(&self) -> &crate::error::ErrorContext { fn context(&self) -> &crate::parser::error::ErrorContext {
&self.context &self.context
} }

View File

@ -22,12 +22,14 @@
use std::{iter::once, mem}; use std::{iter::once, mem};
use crate::{ use crate::{
parser::{
command_spec::unchecked::{ command_spec::unchecked::{
Attribute, CommandSpec, Declaration, DeriveValue, DocNamedType, DocToken, Enumeration, Attribute, CommandSpec, Declaration, DeriveValue, DocNamedType, DocToken, Enumeration,
Function, NamedType, Namespace, StringLiteral, Structure, Type, Function, NamedType, Namespace, StringLiteral, Structure, Type,
}, },
error::ErrorContext, error::ErrorContext,
lexing::{AttributeKeyword, Token, TokenKind, TokenSpan, TokenStream}, lexing::{AttributeKeyword, Token, TokenKind, TokenSpan, TokenStream},
},
token, token,
}; };

View File

@ -19,7 +19,7 @@
* If not, see <https://www.gnu.org/licenses/>. * If not, see <https://www.gnu.org/licenses/>.
*/ */
use crate::error::{self, TypeConversionError}; use crate::types::error::{self, TypeConversionError};
use log::warn; use log::warn;
use std::{ use std::{
error::Error, error::Error,
@ -180,7 +180,7 @@ impl<T: Convertible, E: Error> Convertible for Result<T, E> {
} }
impl<T: Convertible> Convertible for Vec<T> { impl<T: Convertible> Convertible for Vec<T> {
type Ptr = crate::Vec<<T as Convertible>::Ptr>; type Ptr = crate::types::Vec<<T as Convertible>::Ptr>;
fn into_ptr(self) -> Self::Ptr { fn into_ptr(self) -> Self::Ptr {
let data_vec: Vec<_> = self.into_iter().map(|val| val.into_ptr()).collect(); let data_vec: Vec<_> = self.into_iter().map(|val| val.into_ptr()).collect();
@ -222,7 +222,7 @@ impl<T: Convertible> Convertible for Vec<T> {
macro_rules! make_vec_free { macro_rules! make_vec_free {
($value:ident, $name:ident) => { ($value:ident, $name:ident) => {
#[no_mangle] #[no_mangle]
pub extern "C" fn $name(ptr: crate::Vec<<$value as Convertible>::Ptr>) { pub extern "C" fn $name(ptr: crate::types::Vec<<$value as Convertible>::Ptr>) {
Vec::<$value>::drop_ptr(ptr); Vec::<$value>::drop_ptr(ptr);
} }
}; };

View File

@ -29,7 +29,7 @@ use std::{
use log::{error, warn}; use log::{error, warn};
use crate::error::TypeConversionError; use crate::types::error::TypeConversionError;
// This code is heavily inspired by: https://michael-f-bryan.github.io/rust-ffi-guide/errors/return_types.html // This code is heavily inspired by: https://michael-f-bryan.github.io/rust-ffi-guide/errors/return_types.html
thread_local! { thread_local! {

View File

@ -22,4 +22,4 @@
pub mod convert_trait; pub mod convert_trait;
pub mod errno; pub mod errno;
mod try_from_impl; mod try_from_impl;
pub use try_from_impl::*; // pub use try_from_impl::*;

View File

@ -19,7 +19,7 @@
* If not, see <https://www.gnu.org/licenses/>. * If not, see <https://www.gnu.org/licenses/>.
*/ */
use crate::String; use crate::types::String;
use std::ffi::CString; use std::ffi::CString;
use super::convert_trait::Convertible; use super::convert_trait::Convertible;
@ -34,7 +34,7 @@ impl From<std::string::String> for String {
} }
impl TryFrom<String> for std::string::String { impl TryFrom<String> for std::string::String {
type Error = crate::error::TypeConversionError; type Error = crate::types::error::TypeConversionError;
fn try_from(value: String) -> Result<Self, Self::Error> { fn try_from(value: String) -> Result<Self, Self::Error> {
let cstring = CString::from_ptr(value.0)?; let cstring = CString::from_ptr(value.0)?;

View File

@ -1,6 +0,0 @@
# build
/target
/result
# This crate is a library
Cargo.lock

View File

@ -1,32 +0,0 @@
# Copyright (C) 2023 - 2024:
# The Trinitrix Project <soispha@vhack.eu, antifallobst@systemausfall.org>
#
# 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 Lesser 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 Lesser GNU General Public License along with this program.
# If not, see <https://www.gnu.org/licenses/>.
[package]
name = "trixy-macros"
version = "0.1.0"
edition = "2021"
[dependencies]
convert_case = "0.6.0"
prettyplease = "0.2.15"
proc-macro2 = {version = "1.0.70", features = [ ]}
quote = "1.0.33"
syn = { version = "2.0.41", features = ["extra-traits", "full", "parsing"] }
trixy-parser = { path = "../trixy-parser" }
trixy-types = { path = "../trixy-types" }

View File

@ -1,194 +0,0 @@
/*
* Copyright (C) 2023 - 2024:
* The Trinitrix Project <soispha@vhack.eu, antifallobst@systemausfall.org>
*
* 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 Lesser 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 Lesser GNU General Public License along with this program.
* If not, see <https://www.gnu.org/licenses/>.
*/
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use trixy_parser::command_spec::{
CommandSpec, DocIdentifier, DocNamedType, Enumeration, Function, Identifier, Namespace,
Structure,
};
use crate::generate::{
c_api::{
header::{attribute_to_doc_comment, named_type_to_c, type_to_c},
identifier_to_c, mangle_c_function_identifier,
},
identifier_to_rust,
};
pub fn generate(trixy: &CommandSpec) -> String {
let functions: String = trixy
.functions
.iter()
.map(|r#fn| function_to_header(r#fn, &[]))
.collect();
let namespaces: String = trixy
.namespaces
.iter()
.map(|nasp| namespace_to_header(nasp, &vec![]))
.collect();
let structures: String = trixy
.structures
.iter()
.map(|r#struct| structure_to_header(r#struct))
.collect();
let enumerations: String = trixy
.enumerations
.iter()
.map(|r#enum| enumeration_to_header(r#enum))
.collect();
format!(
"{}\n{}\n{}\n{}",
enumerations, structures, functions, namespaces
)
}
fn structure_to_header(structure: &Structure) -> String {
let doc_comments: String = structure
.attributes
.iter()
.map(attribute_to_doc_comment)
.collect::<String>();
let ident = identifier_to_c(&structure.identifier);
let contents = structure
.contents
.iter()
.map(doc_named_type_to_header)
.collect::<String>();
format!(
"
{}
{} {{
{}
}};",
doc_comments, ident, contents
)
}
fn doc_named_type_to_header(doc_named_type: &DocNamedType) -> String {
let doc_comments: String = doc_named_type
.attributes
.iter()
.map(attribute_to_doc_comment)
.collect::<String>();
let ident = identifier_to_rust(&doc_named_type.name);
let r#type = type_to_c(&doc_named_type.r#type, false);
format!("{}{} {};", doc_comments, r#type, ident)
}
fn enumeration_to_header(enumeration: &Enumeration) -> String {
let doc_comments: String = enumeration
.attributes
.iter()
.map(attribute_to_doc_comment)
.collect::<String>();
let ident = identifier_to_c(&enumeration.identifier);
let states = enumeration
.states
.iter()
.map(doc_identifier_to_header)
.collect::<String>();
let states = if enumeration.states.is_empty() {
"/**
* This enum does not have variants on the rust side
* to work around c limitiation with variant-less enums
* we added a `__never` variant:
*/
__never,"
.to_owned()
} else {
states
};
format!(
"
{}
{} {{
{}
}};",
doc_comments, ident, states
)
}
fn doc_identifier_to_header(doc_identifier: &DocIdentifier) -> String {
let doc_comments: String = doc_identifier
.attributes
.iter()
.map(attribute_to_doc_comment)
.collect::<String>();
let ident = &doc_identifier.name;
format!("{}{},", doc_comments, ident)
}
fn function_to_header(function: &Function, namespaces: &[&Identifier]) -> String {
let doc_comments: String = function
.attributes
.iter()
.map(attribute_to_doc_comment)
.collect::<String>();
let ident = mangle_c_function_identifier(&function.identifier, namespaces);
let inputs: Vec<TokenStream2> = function.inputs.iter().map(named_type_to_c).collect();
let function_output = if let Some(out) = &function.output {
let type_name = type_to_c(&out, true);
let comma = if !inputs.is_empty() {
quote! {
,
}
} else {
TokenStream2::default()
};
quote! {
#type_name trixy_output #comma
}
} else {
TokenStream2::default()
};
let output = quote! {
extern int #ident(#function_output #(#inputs),*);
};
format!("{}{}\n", doc_comments, output)
}
fn namespace_to_header(nasp: &Namespace, namespaces: &Vec<&Identifier>) -> String {
let mut nasps = namespaces.clone();
nasps.push(&nasp.name);
let structures: String = nasp
.structures
.iter()
.map(|r#fn| structure_to_header(r#fn))
.collect::<Vec<String>>()
.join("\n");
let enumerations: String = nasp
.enumerations
.iter()
.map(|r#fn| enumeration_to_header(r#fn))
.collect::<Vec<String>>()
.join("\n");
let functions: String = nasp
.functions
.iter()
.map(|r#fn| function_to_header(r#fn, &nasps))
.collect::<Vec<String>>()
.join("\n");
let namespaces: String = nasp
.namespaces
.iter()
.map(|nasp| namespace_to_header(nasp, &nasps))
.collect();
format! {"{}\n{}\n{}\n{}", enumerations, structures, functions, namespaces}
}

View File

@ -1,85 +0,0 @@
/*
* Copyright (C) 2023 - 2024:
* The Trinitrix Project <soispha@vhack.eu, antifallobst@systemausfall.org>
*
* 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 Lesser 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 Lesser 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;
use quote::quote;
use trixy_parser::command_spec::{CommandSpec, Function, Identifier, Namespace};
use crate::generate::{c_api::mangle_c_function_identifier, identifier_to_rust};
pub fn generate(trixy: &CommandSpec) -> String {
let struct_initializer: TokenStream2 = trixy
.namespaces
.iter()
.map(|nasp| namespace_to_full_struct_init(nasp, &vec![]))
.collect();
struct_initializer.to_string()
}
fn namespace_to_full_struct_init(nasp: &Namespace, namespaces: &Vec<&Identifier>) -> TokenStream2 {
let mut input_namespaces = namespaces.clone();
input_namespaces.push(&nasp.name);
let ident = identifier_to_rust(&nasp.name);
let type_ident = format_ident!("{}", ident.to_string());
let functions: TokenStream2 = nasp
.functions
.iter()
.map(|r#fn| function_to_struct_init(r#fn, &input_namespaces))
.collect();
let namespaces: TokenStream2 = nasp
.namespaces
.iter()
.map(namespace_to_struct_init)
.collect();
let next_namespace: TokenStream2 = nasp
.namespaces
.iter()
.map(|nasp| namespace_to_full_struct_init(nasp, &input_namespaces))
.collect();
quote! {
#next_namespace
const struct #type_ident #ident = {
#functions
#namespaces
};
}
}
fn function_to_struct_init(function: &Function, namespaces: &[&Identifier]) -> TokenStream2 {
let ident = identifier_to_rust(&function.identifier);
let full_ident = mangle_c_function_identifier(&function.identifier, namespaces);
quote! {
. #ident = #full_ident,
}
}
fn namespace_to_struct_init(namespace: &Namespace) -> TokenStream2 {
let ident = identifier_to_rust(&namespace.name);
quote! {
. #ident = #ident ,
}
}

View File

@ -1,114 +0,0 @@
/*
* Copyright (C) 2023 - 2024:
* The Trinitrix Project <soispha@vhack.eu, antifallobst@systemausfall.org>
*
* 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 Lesser 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 Lesser 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 trixy_parser::command_spec::{CommandSpec, Function, Namespace};
use crate::generate::{
c_api::{header::attribute_to_doc_comment, type_to_c},
identifier_to_rust,
};
pub fn generate(trixy: &CommandSpec) -> String {
let type_defs: String = trixy
.namespaces
.iter()
.rev()
.map(|nasp| namespace_to_full_typedef(nasp))
.collect::<Vec<String>>()
.join("\n");
type_defs.to_string()
}
fn function_to_typedef(function: &Function) -> TokenStream2 {
let ident = identifier_to_rust(&function.identifier);
let (output, output_comma) = if let Some(output) = &function.output {
let output = type_to_c(output, true);
(quote! { #output }, quote! {,})
} else {
(TokenStream2::default(), TokenStream2::default())
};
let inputs: TokenStream2 = if function.inputs.is_empty() && output.is_empty() {
quote! { void }
} else if !function.inputs.is_empty() && !output.is_empty() {
let inputs: Vec<TokenStream2> = function
.inputs
.iter()
.map(|named_type| &named_type.r#type)
.map(|r#type| type_to_c(&r#type, false))
.collect();
quote! {
#output_comma #(#inputs),*
}
} else {
TokenStream2::default()
};
quote! {
int (* #ident ) (#output #inputs);
}
}
fn namespace_to_typedef(namespace: &Namespace) -> TokenStream2 {
let ident = identifier_to_rust(&namespace.name);
let type_ident = format_ident!("{}", ident.to_string());
quote! {
struct #type_ident #ident;
}
}
fn namespace_to_full_typedef(nasp: &Namespace) -> String {
let ident = format_ident!("{}", nasp.name.name);
let doc_comments = nasp
.attributes
.iter()
.map(attribute_to_doc_comment)
.collect::<String>();
let functions: TokenStream2 = nasp
.functions
.iter()
.map(|r#fn| function_to_typedef(r#fn))
.collect();
let namespaces: TokenStream2 = nasp
.namespaces
.iter()
.map(|nasp| namespace_to_typedef(nasp))
.collect();
let next_namespace: String = nasp
.namespaces
.iter()
.map(|nasp| namespace_to_full_typedef(nasp))
.collect::<Vec<String>>()
.join("\n");
let namespace = quote! {
struct #ident {
#functions
#namespaces
};
};
format! {"{}\n{}{}\n", next_namespace, doc_comments, namespace}
}

View File

@ -1,375 +0,0 @@
/*
* Copyright (C) 2023 - 2024:
* The Trinitrix Project <soispha@vhack.eu, antifallobst@systemausfall.org>
*
* 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 Lesser 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 Lesser GNU General Public License along with this program.
* If not, see <https://www.gnu.org/licenses/>.
*/
use convert_case::{Case, Casing};
use proc_macro2::TokenStream as TokenStream2;
use quote::{format_ident, quote};
use trixy_parser::command_spec::{
CommandSpec, Enumeration, Function, Identifier, NamedType, Namespace, Structure,
};
use crate::{
config::TrixyConfig,
generate::{
attribute_to_rust,
c_api::{
doc_named_type_to_c_equivalent, mangle_c_function_identifier, mangle_c_type_identifier,
type_to_c_equivalent,
},
convertible_derive::c_enumeration_into_impl,
doc_identifier_to_rust, function_identifier_to_rust, identifier_to_rust,
namespaces_to_path, type_variant_rust_path,
},
};
/// This function generates the main c API provided by Trixy.
/// This works for example like this:
/// Turning this:
/// ```text
/// nasp trinitrix {
/// struct Callback {
/// func: String,
/// timeout: String,
/// };
///
/// enum CallbackPriority {
/// High,
/// Medium,
/// Low,
/// };
///
/// fn execute_callback(callback: Callback, priority: CallbackPriority);
/// }
/// ```
/// to this:
/// ```no_run
/// pub extern "C" fn exectute_callback(callback: Callback, priority: CallbackPriority) {
/// /* Here we simply call your handler function, with the command of the function */
/// }
/// ```
pub fn generate(trixy: &CommandSpec, config: &TrixyConfig) -> TokenStream2 {
let functions: TokenStream2 = trixy
.functions
.iter()
.map(|r#fn| function_to_c(r#fn, &config, &vec![]))
.collect();
let namespaced_functions: TokenStream2 = trixy
.namespaces
.iter()
.map(|nasp| namespace_to_c(&nasp, &config, &vec![]))
.collect();
let structures: TokenStream2 = trixy
.structures
.iter()
.map(|nasp| structure_to_c(&nasp))
.collect();
let enumerations: TokenStream2 = trixy
.enumerations
.iter()
.map(|nasp| enumeration_to_c(&nasp))
.collect();
quote! {
#enumerations
#structures
#functions
#namespaced_functions
}
}
fn enumeration_to_c(enumeration: &Enumeration) -> TokenStream2 {
let ident = mangle_c_type_identifier(&enumeration.identifier);
let doc_comments: TokenStream2 = enumeration
.attributes
.iter()
.map(|attr| attribute_to_rust(&enumeration.identifier, attr))
.collect();
let states: Vec<TokenStream2> = enumeration
.states
.iter()
.map(doc_identifier_to_rust)
.collect();
let paired_type = {
let path = type_variant_rust_path(&enumeration.identifier.variant)
.expect("This should always be some for enums");
let ident = identifier_to_rust(&enumeration.identifier);
if path.is_empty() {
quote! {
crate :: #ident
}
} else {
quote! {
#path :: #ident
}
}
};
let convertible = c_enumeration_into_impl(&enumeration, &paired_type);
quote! {
#doc_comments
#[allow(non_camel_case_types)]
#[repr(C)]
#[derive(Debug)]
pub enum #ident {
#(#states),*
}
#convertible
}
}
fn structure_to_c(structure: &Structure) -> TokenStream2 {
let ident = mangle_c_type_identifier(&structure.identifier);
let doc_comments: TokenStream2 = structure
.attributes
.iter()
.map(|attr| attribute_to_rust(&structure.identifier, attr))
.collect();
let contents: Vec<TokenStream2> = structure
.contents
.iter()
.map(doc_named_type_to_c_equivalent)
.collect();
// TODO: Convertible <2024-03-08>
quote! {
#doc_comments
#[allow(non_camel_case_types)]
#[repr(C)]
#[derive(Debug)]
pub struct #ident {
#(#contents),*
}
}
}
fn namespace_to_c(
namespace: &Namespace,
config: &TrixyConfig,
namespaces: &Vec<&Identifier>,
) -> TokenStream2 {
let ident = mangle_c_type_identifier(&namespace.name);
let mut namespaces = namespaces.clone();
namespaces.push(&namespace.name);
let functions: TokenStream2 = namespace
.functions
.iter()
.map(|r#fn| function_to_c(&r#fn, &config, &namespaces))
.collect();
let additional_functions: TokenStream2 = namespace
.namespaces
.iter()
.map(|nasp| namespace_to_c(&nasp, &config, &namespaces))
.collect();
let structures: TokenStream2 = namespace
.structures
.iter()
.map(|nasp| structure_to_c(&nasp))
.collect();
let enumerations: TokenStream2 = namespace
.enumerations
.iter()
.map(|nasp| enumeration_to_c(&nasp))
.collect();
quote! {
pub mod #ident {
#enumerations
#structures
}
#functions
#additional_functions
}
}
fn function_to_c(
function: &Function,
config: &TrixyConfig,
namespaces: &Vec<&Identifier>,
) -> TokenStream2 {
let ident = mangle_c_function_identifier(&function.identifier, namespaces);
let inputs: Vec<TokenStream2> = function
.inputs
.iter()
.map(|named_type| named_type_to_rust_trixy(named_type))
.collect();
let callback_function = format_ident!("{}", config.callback_function);
let command_value: TokenStream2 = function_path_to_rust(&namespaces, &function);
if let Some(r#type) = &function.output {
let output_ident = type_to_c_equivalent(&r#type);
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();
#callback_function (#command_value);
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 {
quote! {
#[no_mangle]
pub extern "C" fn #ident(#(#inputs),*) -> core::ffi::c_int {
#callback_function (#command_value);
return 1;
}
}
}
}
fn named_type_to_rust_trixy(named_type: &NamedType) -> TokenStream2 {
let ident = identifier_to_rust(&named_type.name);
let type_ident = type_to_c_equivalent(&named_type.r#type);
quote! {
#ident : #type_ident
}
}
/// Turns a function in namespaces to the generated host enum path:
/// *trixy code:*
/// ```text
/// fn fn_alone();
/// nasp one {
/// fn fn_one();
/// nasp two {
/// fn fn_two(input: String);
/// }
/// }
/// ```
/// *rust enum path for fn_alone:*
/// ```no_run
/// Commands::fn_alone
/// ```
/// *rust enum path for fn_one:*
/// ```no_run
/// // `Commands` is just the name for the top-level namespace
/// Commands::One(one::One(
/// One::fn_one
/// ))
/// ```
/// *rust enum path for fn_two:*
/// ```no_run
/// Commands::One(one::One(
/// one::two::Two(one::two::Two(
/// Two::fn_two {input: String}
/// ))))
/// ```
fn function_path_to_rust(namespaces: &Vec<&Identifier>, function: &Function) -> TokenStream2 {
let function_ident =
function_identifier_to_rust(&function, named_type_to_rust_assignment, |_| {
quote! {
// This is defined in the outer call
tx
}
});
if namespaces.is_empty() {
quote! {
Commands:: #function_ident
}
} else {
let nasp_pascal_ident = format_ident!(
"{}",
namespaces
.last()
.expect("We checked")
.name
.to_case(Case::Pascal)
);
let namespace_path = namespaces_to_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
}
}
fn named_type_to_rust_assignment(named_type: &NamedType) -> TokenStream2 {
let ident = identifier_to_rust(&named_type.name);
quote! {
#ident : match #ident.try_into() {
Ok(ok) => ok,
Err(err) => {
trixy::types::traits::errno::set(err);
return 0;
}
}
}
}
/// 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 = format_ident!("{}", current_nasp.name.to_case(Case::Pascal));
if index == 0 {
quote! {
Commands :: #ident_pascal ( #input )
}
} else {
let ident_pascal_next = format_ident!(
"{}",
namespaces_to_do
.last()
.expect("We checked the index")
.name
.to_case(Case::Pascal)
);
let namespace_path = namespaces_to_path(namespaces_to_do);
quote! {
#namespace_path :: #ident_pascal_next :: #ident_pascal ( #input )
}
}
}

View File

@ -1,226 +0,0 @@
/*
* Copyright (C) 2023 - 2024:
* The Trinitrix Project <soispha@vhack.eu, antifallobst@systemausfall.org>
*
* 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 Lesser 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 Lesser GNU General Public License along with this program.
* If not, see <https://www.gnu.org/licenses/>.
*/
use convert_case::{Case, Casing};
use proc_macro2::{Ident, TokenStream as TokenStream2};
use quote::{format_ident, quote};
use trixy_parser::command_spec::{DocNamedType, Identifier, NamedType, Type, Variant};
use crate::generate::{attribute_to_rust, namespaces_to_path_expansive, type_to_rust};
use super::identifier_to_rust;
pub mod header;
pub mod host;
pub fn mangle_c_function_identifier(identifier: &Identifier, namespaces: &[&Identifier]) -> Ident {
let namespace_str = namespaces.iter().fold(String::default(), |acc, nasp| {
if acc.is_empty() {
nasp.name.clone()
} else {
format!("{}_{}", acc, nasp.name)
}
});
if namespace_str.is_empty() {
format_ident!("{}", &identifier.name)
} else {
format_ident!("{}_{}", namespace_str, &identifier.name)
}
}
pub fn mangle_c_type_identifier(identifier: &Identifier) -> Ident {
format_ident!("{}_c", &identifier.name)
}
pub fn type_to_c(r#type: &Type, is_output: bool) -> TokenStream2 {
let ident = identifier_to_c(&r#type.identifier);
let output = if is_output {
quote! {
*
}
} else {
TokenStream2::default()
};
quote! {
#ident #output
}
}
pub fn identifier_to_c(identifier: &Identifier) -> TokenStream2 {
match &identifier.variant {
Variant::Structure { .. } => {
let ident = format_ident!("{}", identifier.name.to_case(Case::Pascal));
quote! {
struct #ident
}
}
Variant::Enumeration { .. } => {
let ident = format_ident!("{}", identifier.name.to_case(Case::Pascal));
quote! {
enum #ident
}
}
Variant::Primitive => match identifier.name.to_case(Case::Snake).as_str() {
"string" => {
quote! {
const char*
}
}
other => {
todo!("'{}' is not yet supported", other)
}
},
other => {
unimplemented!("{:#?}", other)
}
}
}
pub fn type_to_c_equivalent(r#type: &Type) -> TokenStream2 {
let trixy_build_in_types: Vec<&str> = trixy_types::BASE_TYPES
.iter()
.filter_map(|(name, _)| {
if name == &r#type.identifier.name {
Some(*name)
} else {
None
}
})
.collect();
if trixy_build_in_types.is_empty() {
// The types was specified in the api.tri file
let ident = mangle_c_type_identifier(&r#type.identifier);
let namespaces_path = type_variant_c_path(&r#type.identifier.variant);
let nasp_path = if namespaces_path.is_empty() {
quote! {
crate ::
}
} else {
let path = namespaces_path;
quote! {
#path ::
}
};
quote! {
#nasp_path #ident
}
} else {
debug_assert_eq!(trixy_build_in_types.len(), 1);
let type_name = trixy_build_in_types
.first()
.expect("The names should not be dublicated, this should be the only value");
match *type_name {
"Result" => {
let ident_ok =
type_to_c_equivalent(&r#type.generic_args.first().expect("This is a result"));
let ident_err =
type_to_c_equivalent(&r#type.generic_args.last().expect("This is a result"));
quote! {
// <Result<TrainedDog, TrainingMistake> as Convertible>::Ptr,
<Result<#ident_ok, #ident_err> as Convertible>::Ptr
}
}
"Option" => {
let value = type_to_rust(
r#type
.generic_args
.first()
.expect("An option does only have one arg"),
);
quote! {
*const #value
}
}
_ => {
let ident = identifier_to_rust(&r#type.identifier);
let generics: TokenStream2 = {
let generics: Vec<TokenStream2> = r#type
.generic_args
.iter()
.map(|val| type_to_c_equivalent(val))
.collect();
quote! {
<#(#generics),*>
}
};
quote! {
trixy::types:: #ident #generics
}
}
}
}
}
fn doc_named_type_to_c_equivalent(doc_named_type: &DocNamedType) -> TokenStream2 {
let doc_comments: TokenStream2 = doc_named_type
.attributes
.iter()
.map(|attr| attribute_to_rust(&&doc_named_type.name, attr))
.collect();
let named_type = named_type_to_c_equivalent(&doc_named_type.into());
quote! {
#doc_comments
pub #named_type
}
}
fn named_type_to_c_equivalent(named_type: &NamedType) -> TokenStream2 {
let ident = identifier_to_rust(&named_type.name);
let r#type = type_to_c_equivalent(&named_type.r#type);
quote! {
#ident : #r#type
}
}
pub fn type_variant_c_path(variant: &Variant) -> TokenStream2 {
fn mangle_namespace_name(vec: &Vec<Identifier>) -> Vec<Identifier> {
vec.into_iter()
.map(|ident| {
if "<root>" == &ident.name && ident.variant == Variant::RootNamespace {
// The [`namespaces_to_path_expansive`] function already deals with
// [`RootNamespace`] variants, so we just leave that as is
ident.clone()
} else {
Identifier {
// TODO(@soispha): This should use [`mangle_c_type_name`]
// to ensure same mangling as the others <2024-03-05>
name: format!("{}_c", ident.name),
variant: ident.variant.clone(),
}
}
})
.collect()
}
let main_namespace;
match variant {
Variant::Structure { namespace } => {
main_namespace = mangle_namespace_name(namespace);
}
Variant::Enumeration { namespace } => {
main_namespace = mangle_namespace_name(namespace);
}
_ => unreachable!("This should never be called"),
}
namespaces_to_path_expansive(&main_namespace[..])
}

View File

@ -1,148 +0,0 @@
/*
* Copyright (C) 2023 - 2024:
* The Trinitrix Project <soispha@vhack.eu, antifallobst@systemausfall.org>
*
* 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 Lesser 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 Lesser GNU General Public License along with this program.
* If not, see <https://www.gnu.org/licenses/>.
*/
use proc_macro2::TokenStream;
use quote::{quote, ToTokens};
use trixy_parser::command_spec::{Enumeration, Structure};
use crate::generate::{c_api::mangle_c_type_identifier, identifier_to_rust};
/// This function generates the `Convertible` implementation for a structure
pub fn structure_convertable_derive(
structure: &Structure,
paired_type: &TokenStream,
) -> TokenStream {
let ident = identifier_to_rust(&structure.identifier);
let into_fields: TokenStream = structure
.contents
.iter()
.map(|con| {
let ident = identifier_to_rust(&con.name);
quote! {
#ident: self.#ident.into(),
}
})
.collect();
quote! {
impl trixy::types::traits::convert_trait::Convertible for #ident {
type Ptr = #paired_type;
fn into_ptr(self) -> Self::Ptr {
Self::Ptr {
#into_fields
}
}
fn from_ptr(ptr: Self::Ptr) -> Result<Self, trixy::types::error::TypeConversionError> {
todo!()
}
}
}
}
/// This function generates the `TryFrom<paired_type>` trait implementation for a given structure
pub fn structure_into_impl(structure: &Structure, paired_type: &TokenStream) -> TokenStream {
let ident = identifier_to_rust(&structure.identifier);
let try_into_fields: TokenStream = structure
.contents
.iter()
.map(|con| {
let ident = identifier_to_rust(&con.name);
quote! {
#ident: value.#ident.try_into()?,
}
})
.collect();
quote! {
impl TryFrom<#paired_type> for #ident {
type Error = trixy::types::error::TypeConversionError;
fn try_from(value: #paired_type) -> Result<Self, Self::Error> {
Ok(Self {
#try_into_fields
})
}
}
}
}
/// This function generates the `From<paired_type>` trait implementation for a given c enumeration
pub fn c_enumeration_into_impl(
enumeration: &Enumeration,
paired_type: &TokenStream,
) -> TokenStream {
let c_ident = mangle_c_type_identifier(&enumeration.identifier);
let ident = identifier_to_rust(&enumeration.identifier);
enumeration_into_impl(
enumeration,
&c_ident.into_token_stream(),
&ident,
&paired_type,
)
}
/// This function generates the `From<paired_type>` trait implementation for a given rust enumeration
pub fn rust_enumeration_into_impl(
enumeration: &Enumeration,
paired_type: &TokenStream,
) -> TokenStream {
let c_ident = mangle_c_type_identifier(&enumeration.identifier);
let ident = identifier_to_rust(&enumeration.identifier);
enumeration_into_impl(
enumeration,
&ident,
&c_ident.into_token_stream(),
&paired_type,
)
}
fn enumeration_into_impl(
enumeration: &Enumeration,
ident_main: &TokenStream,
ident_client: &TokenStream,
paired_type: &TokenStream,
) -> TokenStream {
let match_lines: TokenStream = enumeration
.states
.iter()
.map(|state| {
let name = identifier_to_rust(&(state.into()));
quote! {
#ident_client :: #name => Self :: #name,
}
})
.collect();
quote! {
impl From<#paired_type> for #ident_main {
fn from(value: #paired_type) -> Self {
match value {
#match_lines
}
}
}
}
}

Some files were not shown because too many files have changed in this diff Show More