This repository has been archived on 2024-05-26. You can view files and clone it, but cannot push or open issues or pull requests.
trixy/trixy-macros/src/lib.rs

130 lines
4.9 KiB
Rust
Raw Normal View History

feat(treewide): Add broken Vec<E>, Result<T,E> and Option<T> types to c api These are implemented right now by simply casting the generic arguments to void pointers and providing a `type_id` field in every struct denoting the original type. This implementation, whilst being extremely unwieldy to work with on the c side, also fails in a lot of fundamental ways: 1. The `type_id` enum *can* never really support user defined types because we would already need it to provide the c to rust value conversion. 2. Even without custom user types the type conversion is extremely hard to correctly implement in a somewhat performant way: A vector passed from c code to rust would need to completely reallocated *one element at a time*. And this only works if the c side has correctly cast the void pointer to the vectors data before accessing it, as any other way would have lead to possible unaligned data (which the rust side had to account for). 3. The c api is just simply bad in this state: You have to always look at the Trixy file to even be able to deal with the data the api returns (that is: There is no mention of a results generics in the c header). Additionally the question arises if these types should even be leaked into the c code because than c just becomes a worse version of rust, which undermines the whole reason of providing a c api in the first place. One way to fix all these issues would be to change the way generics are handled by using unions instead of the void pointer and trying to avoid leaking these rust types in c as far as possible. This approach would require a lot less binding code (both on the c and rust side), but also would make the rust based c-header-gen-code harder, as it would then be required to turn a `Vec<String>` to a `char **` (and obviously a whole wrapper struct with size and string length), whilst turning a `Vec<char>` to a `char*` differentiating it from a `string_t`.
2023-12-28 09:28:58 +00:00
use std::{
env,
fs::{self, File},
io::Write,
iter,
path::{Path, PathBuf},
process::Command,
};
2023-12-25 21:23:52 +00:00
use trixy_parser::parse_trixy_lang;
feat(treewide): Add broken Vec<E>, Result<T,E> and Option<T> types to c api These are implemented right now by simply casting the generic arguments to void pointers and providing a `type_id` field in every struct denoting the original type. This implementation, whilst being extremely unwieldy to work with on the c side, also fails in a lot of fundamental ways: 1. The `type_id` enum *can* never really support user defined types because we would already need it to provide the c to rust value conversion. 2. Even without custom user types the type conversion is extremely hard to correctly implement in a somewhat performant way: A vector passed from c code to rust would need to completely reallocated *one element at a time*. And this only works if the c side has correctly cast the void pointer to the vectors data before accessing it, as any other way would have lead to possible unaligned data (which the rust side had to account for). 3. The c api is just simply bad in this state: You have to always look at the Trixy file to even be able to deal with the data the api returns (that is: There is no mention of a results generics in the c header). Additionally the question arises if these types should even be leaked into the c code because than c just becomes a worse version of rust, which undermines the whole reason of providing a c api in the first place. One way to fix all these issues would be to change the way generics are handled by using unions instead of the void pointer and trying to avoid leaking these rust types in c as far as possible. This approach would require a lot less binding code (both on the c and rust side), but also would make the rust based c-header-gen-code harder, as it would then be required to turn a `Vec<String>` to a `char **` (and obviously a whole wrapper struct with size and string length), whilst turning a `Vec<char>` to a `char*` differentiating it from a `string_t`.
2023-12-28 09:28:58 +00:00
use trixy_types::C_TYPE_HEADER;
use crate::config::TrixyConfig;
2023-12-25 21:23:52 +00:00
pub mod config;
mod generate;
2023-12-25 21:23:52 +00:00
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 Trixy
/// It mainly does one thing:
/// - Generate a tree of modules from the input trixy file
///
pub fn generate(&self) {
let source_code = fs::read_to_string(&self.trixy_path).unwrap_or_else(|err| {
panic! {"Can't read file at path: '{}'. The Error is: '{}'",
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
feat(treewide): Add broken Vec<E>, Result<T,E> and Option<T> types to c api These are implemented right now by simply casting the generic arguments to void pointers and providing a `type_id` field in every struct denoting the original type. This implementation, whilst being extremely unwieldy to work with on the c side, also fails in a lot of fundamental ways: 1. The `type_id` enum *can* never really support user defined types because we would already need it to provide the c to rust value conversion. 2. Even without custom user types the type conversion is extremely hard to correctly implement in a somewhat performant way: A vector passed from c code to rust would need to completely reallocated *one element at a time*. And this only works if the c side has correctly cast the void pointer to the vectors data before accessing it, as any other way would have lead to possible unaligned data (which the rust side had to account for). 3. The c api is just simply bad in this state: You have to always look at the Trixy file to even be able to deal with the data the api returns (that is: There is no mention of a results generics in the c header). Additionally the question arises if these types should even be leaked into the c code because than c just becomes a worse version of rust, which undermines the whole reason of providing a c api in the first place. One way to fix all these issues would be to change the way generics are handled by using unions instead of the void pointer and trying to avoid leaking these rust types in c as far as possible. This approach would require a lot less binding code (both on the c and rust side), but also would make the rust based c-header-gen-code harder, as it would then be required to turn a `Vec<String>` to a `char **` (and obviously a whole wrapper struct with size and string length), whilst turning a `Vec<char>` to a `char*` differentiating it from a `string_t`.
2023-12-28 09:28:58 +00:00
let tokens = generate::generate(&trixy_code, &self);
eprintln!("{}", tokens);
2023-12-25 21:23:52 +00:00
let host_code = prettyplease::unparse(
feat(treewide): Add broken Vec<E>, Result<T,E> and Option<T> types to c api These are implemented right now by simply casting the generic arguments to void pointers and providing a `type_id` field in every struct denoting the original type. This implementation, whilst being extremely unwieldy to work with on the c side, also fails in a lot of fundamental ways: 1. The `type_id` enum *can* never really support user defined types because we would already need it to provide the c to rust value conversion. 2. Even without custom user types the type conversion is extremely hard to correctly implement in a somewhat performant way: A vector passed from c code to rust would need to completely reallocated *one element at a time*. And this only works if the c side has correctly cast the void pointer to the vectors data before accessing it, as any other way would have lead to possible unaligned data (which the rust side had to account for). 3. The c api is just simply bad in this state: You have to always look at the Trixy file to even be able to deal with the data the api returns (that is: There is no mention of a results generics in the c header). Additionally the question arises if these types should even be leaked into the c code because than c just becomes a worse version of rust, which undermines the whole reason of providing a c api in the first place. One way to fix all these issues would be to change the way generics are handled by using unions instead of the void pointer and trying to avoid leaking these rust types in c as far as possible. This approach would require a lot less binding code (both on the c and rust side), but also would make the rust based c-header-gen-code harder, as it would then be required to turn a `Vec<String>` to a `char **` (and obviously a whole wrapper struct with size and string length), whilst turning a `Vec<char>` to a `char*` differentiating it from a `string_t`.
2023-12-28 09:28:58 +00:00
&syn::parse2(tokens).expect("This code was generated, it should also be parsable"),
2023-12-25 21:23:52 +00:00
);
let mut host_code_out = fs::File::create(PathBuf::from(format!(
"{}/{}",
env::var("OUT_DIR").expect("The build script should have this define"),
&self.host_code_name.display()
)))
.expect("This file should always be free to use");
write!(host_code_out, "{}\n{}", host_code, VIM_LINE_RUST).expect("Write should work");
// c header
let c_header = generate::c_api::header::generate(&trixy_code, &self);
let c_header_path = PathBuf::from(format!(
"{}/{}",
env::var("OUT_DIR").expect("The build script should have this define"),
&self.c_header_name.display()
));
let mut c_header_out =
fs::File::create(&c_header_path).expect("This file should always be free to use");
write!(c_header_out, "{}\n{}", c_header, VIM_LINE_C).expect("Write should work");
Command::new("clang-format")
.args(["-i", &c_header_path.to_str().unwrap()])
.status()
.unwrap_or_else(|err| {
panic!(
"Failed to format the c header file with `clang-format`; Error: `{}`",
err
)
});
if let Some(dist_dir) = &self.dist_dir_path {
if !dist_dir.is_dir() {
fs::create_dir(dist_dir).unwrap_or_else(|err| {
panic! {
"Failed to create the dist directory ('{}') because of: `{}`",
dist_dir.display(), err}
});
}
feat(treewide): Add broken Vec<E>, Result<T,E> and Option<T> types to c api These are implemented right now by simply casting the generic arguments to void pointers and providing a `type_id` field in every struct denoting the original type. This implementation, whilst being extremely unwieldy to work with on the c side, also fails in a lot of fundamental ways: 1. The `type_id` enum *can* never really support user defined types because we would already need it to provide the c to rust value conversion. 2. Even without custom user types the type conversion is extremely hard to correctly implement in a somewhat performant way: A vector passed from c code to rust would need to completely reallocated *one element at a time*. And this only works if the c side has correctly cast the void pointer to the vectors data before accessing it, as any other way would have lead to possible unaligned data (which the rust side had to account for). 3. The c api is just simply bad in this state: You have to always look at the Trixy file to even be able to deal with the data the api returns (that is: There is no mention of a results generics in the c header). Additionally the question arises if these types should even be leaked into the c code because than c just becomes a worse version of rust, which undermines the whole reason of providing a c api in the first place. One way to fix all these issues would be to change the way generics are handled by using unions instead of the void pointer and trying to avoid leaking these rust types in c as far as possible. This approach would require a lot less binding code (both on the c and rust side), but also would make the rust based c-header-gen-code harder, as it would then be required to turn a `Vec<String>` to a `char **` (and obviously a whole wrapper struct with size and string length), whilst turning a `Vec<char>` to a `char*` differentiating it from a `string_t`.
2023-12-28 09:28:58 +00:00
let c_header_dist = PathBuf::from(format!("{}/{}", dist_dir.display(), "generated.h"));
2023-12-25 21:23:52 +00:00
fs::copy(c_header_path, c_header_dist).unwrap_or_else(
feat(treewide): Add broken Vec<E>, Result<T,E> and Option<T> types to c api These are implemented right now by simply casting the generic arguments to void pointers and providing a `type_id` field in every struct denoting the original type. This implementation, whilst being extremely unwieldy to work with on the c side, also fails in a lot of fundamental ways: 1. The `type_id` enum *can* never really support user defined types because we would already need it to provide the c to rust value conversion. 2. Even without custom user types the type conversion is extremely hard to correctly implement in a somewhat performant way: A vector passed from c code to rust would need to completely reallocated *one element at a time*. And this only works if the c side has correctly cast the void pointer to the vectors data before accessing it, as any other way would have lead to possible unaligned data (which the rust side had to account for). 3. The c api is just simply bad in this state: You have to always look at the Trixy file to even be able to deal with the data the api returns (that is: There is no mention of a results generics in the c header). Additionally the question arises if these types should even be leaked into the c code because than c just becomes a worse version of rust, which undermines the whole reason of providing a c api in the first place. One way to fix all these issues would be to change the way generics are handled by using unions instead of the void pointer and trying to avoid leaking these rust types in c as far as possible. This approach would require a lot less binding code (both on the c and rust side), but also would make the rust based c-header-gen-code harder, as it would then be required to turn a `Vec<String>` to a `char **` (and obviously a whole wrapper struct with size and string length), whilst turning a `Vec<char>` to a `char*` differentiating it from a `string_t`.
2023-12-28 09:28:58 +00:00
|err| panic! {"Failed to copy the c header ('generated.h') to the dist dir because of: `{}`", err},
2023-12-25 21:23:52 +00:00
);
feat(treewide): Add broken Vec<E>, Result<T,E> and Option<T> types to c api These are implemented right now by simply casting the generic arguments to void pointers and providing a `type_id` field in every struct denoting the original type. This implementation, whilst being extremely unwieldy to work with on the c side, also fails in a lot of fundamental ways: 1. The `type_id` enum *can* never really support user defined types because we would already need it to provide the c to rust value conversion. 2. Even without custom user types the type conversion is extremely hard to correctly implement in a somewhat performant way: A vector passed from c code to rust would need to completely reallocated *one element at a time*. And this only works if the c side has correctly cast the void pointer to the vectors data before accessing it, as any other way would have lead to possible unaligned data (which the rust side had to account for). 3. The c api is just simply bad in this state: You have to always look at the Trixy file to even be able to deal with the data the api returns (that is: There is no mention of a results generics in the c header). Additionally the question arises if these types should even be leaked into the c code because than c just becomes a worse version of rust, which undermines the whole reason of providing a c api in the first place. One way to fix all these issues would be to change the way generics are handled by using unions instead of the void pointer and trying to avoid leaking these rust types in c as far as possible. This approach would require a lot less binding code (both on the c and rust side), but also would make the rust based c-header-gen-code harder, as it would then be required to turn a `Vec<String>` to a `char **` (and obviously a whole wrapper struct with size and string length), whilst turning a `Vec<char>` to a `char*` differentiating it from a `string_t`.
2023-12-28 09:28:58 +00:00
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 \"generated.h\" \n\
#endif // TRIXY_INTERFACE_H \n\
"
);
("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);
if self.check_dist_dir {
if path.exists() {
panic! {
"The file ('{}') already exists in your dist dir ('{}')!
If you want to silence this check set `check_dist_dir` to false",
path.display(), dist_dir.display()
}
}
}
let header_path =
PathBuf::from(format!("{}/{}", dist_dir.display(), path.display()));
let mut file = File::create(&header_path).unwrap_or_else(|err| {
panic! {
"Failed to create the file at '{}' because of: '{}'",
header_path.display(),
err
}
});
write!(file, "{}", content).unwrap_or_else(|err| {
panic! {
"Failed to copy the c header ('{}') to the dist dir because of: `{}`",
path.display(),
err}
});
});
2023-12-25 21:23:52 +00:00
}
}
}