Skip to content

Commit 1d766de

Browse files
committed
ctest: Add translation of Rust types.
1 parent 9cc3dab commit 1d766de

File tree

3 files changed

+259
-47
lines changed

3 files changed

+259
-47
lines changed

ctest-next/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ mod ast;
1515
mod ffi_items;
1616
mod generator;
1717
mod macro_expansion;
18+
mod translator;
1819

1920
pub use ast::{Abi, Const, Field, Fn, Parameter, Static, Struct, Type, Union};
2021
pub use generator::TestGenerator;

ctest-next/src/tests.rs

Lines changed: 49 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::ffi_items::FfiItems;
1+
use crate::{ffi_items::FfiItems, translator::Translator};
22

33
use syn::visit::Visit;
44

@@ -28,64 +28,66 @@ extern "C" {
2828
}
2929
"#;
3030

31+
macro_rules! collect_idents {
32+
($items:expr) => {
33+
$items.iter().map(|a| a.ident()).collect::<Vec<_>>()
34+
};
35+
}
36+
3137
#[test]
3238
fn test_extraction_ffi_items() {
3339
let ast = syn::parse_file(ALL_ITEMS).unwrap();
3440

3541
let mut ffi_items = FfiItems::new();
3642
ffi_items.visit_file(&ast);
3743

38-
assert_eq!(
39-
ffi_items
40-
.aliases()
41-
.iter()
42-
.map(|a| a.ident())
43-
.collect::<Vec<_>>(),
44-
["Foo"]
45-
);
44+
assert_eq!(collect_idents!(ffi_items.aliases()), ["Foo"]);
45+
assert_eq!(collect_idents!(ffi_items.constants()), ["bar"]);
46+
assert_eq!(collect_idents!(ffi_items.foreign_functions()), ["malloc"]);
47+
assert_eq!(collect_idents!(ffi_items.foreign_statics()), ["baz"]);
48+
assert_eq!(collect_idents!(ffi_items.structs()), ["Array"]);
49+
assert_eq!(collect_idents!(ffi_items.unions()), ["Word"]);
50+
}
4651

47-
assert_eq!(
48-
ffi_items
49-
.constants()
50-
.iter()
51-
.map(|a| a.ident())
52-
.collect::<Vec<_>>(),
53-
["bar"]
54-
);
52+
#[test]
53+
fn test_translation_type_path() {
54+
let translator = Translator {};
55+
let ty: syn::Type = syn::parse_str("std::option::Option<u8>").unwrap();
5556

56-
assert_eq!(
57-
ffi_items
58-
.foreign_functions()
59-
.iter()
60-
.map(|a| a.ident())
61-
.collect::<Vec<_>>(),
62-
["malloc"]
63-
);
57+
assert_eq!(translator.translate_type(&ty), "uint8_t");
58+
}
6459

65-
assert_eq!(
66-
ffi_items
67-
.foreign_statics()
68-
.iter()
69-
.map(|a| a.ident())
70-
.collect::<Vec<_>>(),
71-
["baz"]
72-
);
60+
#[test]
61+
fn test_translation_type_ptr() {
62+
let translator = Translator {};
63+
let ty: syn::Type = syn::parse_str("*const *mut i32").unwrap();
7364

74-
assert_eq!(
75-
ffi_items
76-
.structs()
77-
.iter()
78-
.map(|a| a.ident())
79-
.collect::<Vec<_>>(),
80-
["Array"]
81-
);
65+
assert_eq!(translator.translate_type(&ty), " int32_t* const*");
66+
}
67+
68+
#[test]
69+
fn test_translation_type_reference() {
70+
let translator = Translator {};
71+
let ty: syn::Type = syn::parse_str("&u8").unwrap();
72+
73+
assert_eq!(translator.translate_type(&ty), "uint8_t*");
74+
}
75+
76+
#[test]
77+
fn test_translation_type_bare_fn() {
78+
let translator = Translator {};
79+
let ty: syn::Type = syn::parse_str("fn(*mut u8, i16) -> &str").unwrap();
8280

8381
assert_eq!(
84-
ffi_items
85-
.unions()
86-
.iter()
87-
.map(|a| a.ident())
88-
.collect::<Vec<_>>(),
89-
["Word"]
82+
translator.translate_type(&ty),
83+
"char*(*)( uint8_t*, int16_t)"
9084
);
9185
}
86+
87+
#[test]
88+
fn test_translation_type_array() {
89+
let translator = Translator {};
90+
let ty: syn::Type = syn::parse_str("[&u8; 2 + 2]").unwrap();
91+
92+
assert_eq!(translator.translate_type(&ty), "uint8_t*[2 + 2]");
93+
}

ctest-next/src/translator.rs

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
use std::ops::Deref;
2+
3+
#[derive(Debug, Default)]
4+
/// A Rust to C/Cxx translator.
5+
pub(crate) struct Translator {}
6+
7+
impl Translator {
8+
/// Create a new translator.
9+
#[expect(unused)]
10+
pub(crate) fn new() -> Self {
11+
Self::default()
12+
}
13+
14+
/// Return whether a type is a Rust primitive type.
15+
#[expect(unused)]
16+
fn is_rust_primitive(&self, ty: &str) -> bool {
17+
let rustc_types = [
18+
"usize", "u8", "u16", "u32", "u64", "isize", "i8", "i16", "i32", "i64", "f32", "f64",
19+
];
20+
ty.starts_with("c_") || rustc_types.contains(&ty)
21+
}
22+
23+
/// Translate mutability from Rust to C.
24+
#[expect(unused)]
25+
fn translate_mut(&self, mutability: Option<syn::Token![mut]>) -> String {
26+
mutability.map(|_| "const ").unwrap_or("").to_string()
27+
}
28+
29+
/// Translate a Rust type into its equivalent C type.
30+
pub(crate) fn translate_type(&self, ty: &syn::Type) -> String {
31+
match ty {
32+
syn::Type::Ptr(ptr) => self.translate_ptr(ptr),
33+
syn::Type::Path(path) => self.translate_path(path),
34+
syn::Type::Tuple(tuple) if tuple.elems.is_empty() => "void".to_string(),
35+
syn::Type::Array(array) => self.translate_array(array),
36+
syn::Type::Reference(reference) => self.translate_reference(reference),
37+
syn::Type::BareFn(function) => self.translate_bare_fn(function),
38+
syn::Type::Never(_) => "void".to_string(),
39+
_ => unimplemented!(),
40+
}
41+
}
42+
43+
/// Translate a Rust reference to its C equivalent.
44+
fn translate_reference(&self, reference: &syn::TypeReference) -> String {
45+
let path = match reference.elem.deref() {
46+
syn::Type::Path(path) => path.path.segments.last().unwrap(),
47+
syn::Type::Array(array) => {
48+
return format!(
49+
"{}{}*",
50+
self.translate_mut(reference.mutability),
51+
self.translate_type(&array.elem),
52+
)
53+
}
54+
_ => panic!("Unknown type! {:?}", reference.elem),
55+
};
56+
57+
let ident = path.ident.to_string();
58+
match ident.as_str() {
59+
"str" => {
60+
if reference.mutability.is_some() {
61+
panic!("Unknown type, &mut str");
62+
}
63+
64+
"char*".to_string()
65+
}
66+
c if self.is_rust_primitive(c) => format!(
67+
"{}{}*",
68+
self.translate_mut(reference.mutability),
69+
self.translate_primitive_type(&path.ident)
70+
),
71+
_ => unimplemented!("References to non primitive types are not implemented."),
72+
}
73+
}
74+
75+
/// Translate a Rust function pointer type to its C equivalent.
76+
fn translate_bare_fn(&self, function: &syn::TypeBareFn) -> String {
77+
assert!(function.lifetimes.is_none(), "No lifetimes allowed.");
78+
assert!(function.variadic.is_none(), "No variadics allowed.");
79+
80+
let mut parameters = function
81+
.inputs
82+
.iter()
83+
.map(|arg| self.translate_type(&arg.ty))
84+
.collect::<Vec<_>>();
85+
let return_type = match &function.output {
86+
syn::ReturnType::Default => "void".to_string(),
87+
syn::ReturnType::Type(_, ty) => self.translate_type(ty),
88+
};
89+
90+
if parameters.is_empty() {
91+
parameters.push("void".to_string());
92+
}
93+
94+
if return_type.contains("(*)") {
95+
return_type.replace("(*)", &format!("(*(*)({}))", parameters.join(", ")))
96+
} else {
97+
format!("{}(*)({})", return_type, parameters.join(", "))
98+
}
99+
}
100+
101+
/// Translate a Rust primitve type into its C equivalent.
102+
fn translate_primitive_type(&self, ty: &syn::Ident) -> String {
103+
let ty = ty.to_string();
104+
match ty.as_str() {
105+
"usize" => "size_t".to_string(),
106+
"isize" => "ssize_t".to_string(),
107+
"u8" => "uint8_t".to_string(),
108+
"u16" => "uint16_t".to_string(),
109+
"u32" => "uint32_t".to_string(),
110+
"u64" => "uint64_t".to_string(),
111+
"i8" => "int8_t".to_string(),
112+
"i16" => "int16_t".to_string(),
113+
"i32" => "int32_t".to_string(),
114+
"i64" => "int64_t".to_string(),
115+
"f32" => "float".to_string(),
116+
"f64" => "double".to_string(),
117+
"()" => "void".to_string(),
118+
119+
"c_longdouble" | "c_long_double" => "long double".to_string(),
120+
ty if ty.starts_with("c_") => {
121+
let ty = &ty[2..].replace("long", " long")[..];
122+
match ty {
123+
"short" => "short".to_string(),
124+
s if s.starts_with('u') => format!("unsigned {}", &s[1..]),
125+
s if s.starts_with('s') => format!("signed {}", &s[1..]),
126+
s => s.to_string(),
127+
}
128+
}
129+
// Overriding type names not yet implemented.
130+
s => s.to_string(),
131+
}
132+
}
133+
134+
/// Translate a Rust path into its C equivalent.
135+
fn translate_path(&self, path: &syn::TypePath) -> String {
136+
// Paths should be fully qualified otherwise they won't properly be translated.
137+
let last = path.path.segments.last().unwrap();
138+
if last.ident == "Option" {
139+
if let syn::PathArguments::AngleBracketed(p) = &last.arguments {
140+
if let syn::GenericArgument::Type(ty) = p.args.first().unwrap() {
141+
self.translate_type(ty)
142+
} else {
143+
unimplemented!("Only simple generic types are supported!")
144+
}
145+
} else {
146+
unreachable!("Option<T> cannot have parentheses.")
147+
}
148+
} else {
149+
self.translate_primitive_type(&last.ident)
150+
}
151+
}
152+
153+
/// Translate a Rust array declaration into its C equivalent.
154+
fn translate_array(&self, array: &syn::TypeArray) -> String {
155+
format!(
156+
"{}[{}]",
157+
self.translate_type(array.elem.deref()),
158+
self.translate_expr(&array.len)
159+
)
160+
}
161+
162+
/// Translate a Rust pointer into its equivalent C pointer.
163+
fn translate_ptr(&self, ptr: &syn::TypePtr) -> String {
164+
let modifier = ptr.mutability.map(|_| "").unwrap_or("const");
165+
let inner = ptr.elem.deref();
166+
match inner {
167+
syn::Type::BareFn(_) => self.translate_type(inner),
168+
syn::Type::Ptr(_) => format!("{} {}*", self.translate_type(inner), modifier),
169+
syn::Type::Array(arr) => {
170+
let len = self.translate_expr(&arr.len);
171+
let ty = self.translate_type(inner);
172+
format!("{} {} [{}]", modifier, ty, len)
173+
}
174+
_ => format!("{} {}*", modifier, self.translate_type(inner)),
175+
}
176+
}
177+
178+
/// Translate a simple Rust expression to C.
179+
///
180+
/// This method is only used for translating expressions inside of
181+
/// array brackets, and will fail for expressions not allowed inside of
182+
/// those brackets.
183+
fn translate_expr(&self, expr: &syn::Expr) -> String {
184+
match expr {
185+
syn::Expr::Lit(l) => match &l.lit {
186+
syn::Lit::Int(i) => i.to_string(),
187+
_ => panic!("Invalid Syntax! Cannot have non integer literal in array expression."),
188+
},
189+
syn::Expr::Path(p) => p.path.segments.last().unwrap().ident.to_string(),
190+
syn::Expr::Cast(c) => self.translate_expr(c.expr.deref()),
191+
syn::Expr::Binary(b) => {
192+
let left = self.translate_expr(b.left.deref());
193+
let right = self.translate_expr(b.right.deref());
194+
195+
match b.op {
196+
syn::BinOp::Add(_) => format!("{} + {}", left, right),
197+
syn::BinOp::Sub(_) => format!("{} - {}", left, right),
198+
// Some operators have not been implemented, such as
199+
// shift left, shift right etc. Some other operators cannot be
200+
// placed inside array brackets.
201+
_ => unimplemented!("Unknown Operator! {:?}", b.op),
202+
}
203+
}
204+
// Some expressions have not been implemented, such as
205+
// braces eg: [u8; { expr }], constant functions, etc.
206+
_ => unimplemented!("Unknown Expression! {:?}", expr),
207+
}
208+
}
209+
}

0 commit comments

Comments
 (0)