Skip to content

Commit ccec325

Browse files
committed
ctest: Add a C declaration generator
Introduce the `cdecl` module, which turns a basic C type tree representation into a string representation.
1 parent e634372 commit ccec325

File tree

2 files changed

+356
-0
lines changed

2 files changed

+356
-0
lines changed

ctest-next/src/cdecl.rs

Lines changed: 355 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,355 @@
1+
//! Conversion from a basic C type tree to string declarations.
2+
3+
use std::fmt::Write;
4+
5+
type BoxStr = Box<str>;
6+
7+
#[cfg_attr(not(test), expect(dead_code))]
8+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
9+
pub(crate) enum Constness {
10+
Const,
11+
Mut,
12+
}
13+
use Constness::{Const, Mut};
14+
15+
/// A basic representation of C's types.
16+
#[cfg_attr(not(test), expect(dead_code))]
17+
#[derive(Clone, Debug)]
18+
pub(crate) enum CTy {
19+
/// `int`, `struct foo`, etc. There is only ever one basic type per decl.
20+
Named {
21+
name: BoxStr,
22+
constness: Constness,
23+
},
24+
Ptr {
25+
ty: Box<Self>,
26+
constness: Constness,
27+
},
28+
Array {
29+
ty: Box<Self>,
30+
len: Option<BoxStr>,
31+
},
32+
/// Functions as a declaration. If a function pointer is needed, it must be composed with `Ptr`.
33+
Fn {
34+
args: Vec<Self>,
35+
ret: Box<Self>,
36+
},
37+
}
38+
39+
impl CTy {
40+
/// Validate that we aren't returning an array or a function without indirection, which isn't
41+
/// allowed in C.
42+
fn check_ret_ty(&self) -> Result<(), InvalidReturn> {
43+
let Self::Fn { ret, .. } = self else {
44+
return Ok(());
45+
};
46+
match **ret {
47+
CTy::Named { .. } | CTy::Ptr { .. } => Ok(()),
48+
CTy::Array { .. } | CTy::Fn { .. } => Err(InvalidReturn),
49+
}
50+
}
51+
52+
/// True if this type is added to the RHS in a cdecl (arrays, function pointers).
53+
fn is_rhs(&self) -> bool {
54+
match self {
55+
CTy::Named { .. } | CTy::Ptr { .. } => false,
56+
CTy::Array { .. } | CTy::Fn { .. } => true,
57+
}
58+
}
59+
60+
/// Add parentheses if we are adding something with lower precedence (on the left) after
61+
/// something with higher precedence (on the right).
62+
fn parens_if_needed(&self, s: &mut String, prev: Option<&CTy>) {
63+
let Some(prev) = prev else {
64+
return;
65+
};
66+
if self.is_rhs() && !prev.is_rhs() {
67+
s.insert(0, '(');
68+
s.push(')');
69+
}
70+
}
71+
}
72+
73+
/// Attempting to return an array or function pointer.
74+
#[derive(Clone, Copy, Debug)]
75+
pub(crate) struct InvalidReturn;
76+
77+
/// Create a C declaration for a type.
78+
///
79+
/// Given a type `cty` (e.g. array of pointers to int) and a `name` (e.g. "foo"), turn `name` into
80+
/// a valid declaration for that type (e.g. `int *foo[]`). `name` is taken as an owned string by
81+
/// value to allow reusing allocations.
82+
///
83+
/// If needed, `name` can be empty (e.g. for function arguments).
84+
#[cfg_attr(not(test), expect(dead_code))]
85+
pub(crate) fn cdecl(cty: &CTy, mut name: String) -> Result<String, InvalidReturn> {
86+
cdecl_impl(cty, &mut name, None)?;
87+
Ok(name)
88+
}
89+
90+
/// C declarations are read from the declaration out, left to right, switching directions when a `)`
91+
/// is hit. So, to reverse this, we build from the declaration out adding `*`, `[]`, or `()` on
92+
/// their natural side, and adding `(...)` when we need to something to the left after having added
93+
/// something to the right.
94+
///
95+
/// Helpful description of the rules:
96+
/// <https://web.archive.org/web/20210523053011/http://cseweb.ucsd.edu/~ricko/rt_lt.rule.html>.
97+
fn cdecl_impl(cty: &CTy, s: &mut String, prev: Option<&CTy>) -> Result<(), InvalidReturn> {
98+
cty.check_ret_ty()?;
99+
cty.parens_if_needed(s, prev);
100+
match cty {
101+
CTy::Named { name, constness } => {
102+
let sp = if s.is_empty() { "" } else { " " };
103+
let c = if *constness == Const { "const " } else { "" };
104+
let to_insert = format!("{c}{name}{sp}");
105+
s.insert_str(0, &to_insert);
106+
}
107+
CTy::Ptr { ty, constness } => {
108+
match constness {
109+
Const => s.insert_str(0, "*const "),
110+
Mut => s.insert(0, '*'),
111+
}
112+
cdecl_impl(ty, s, Some(cty))?;
113+
}
114+
CTy::Array { ty, len } => {
115+
let len = len.as_ref().map(BoxStr::as_ref).unwrap_or_default();
116+
write!(s, "[{len}]").unwrap();
117+
cdecl_impl(ty, s, Some(cty))?;
118+
}
119+
CTy::Fn { args, ret } => {
120+
// Functions act as a RHS `(args...)`, then the return type is applied as normal.
121+
let mut tmp = String::new();
122+
s.push('(');
123+
let mut args = args.iter().peekable();
124+
while let Some(arg) = args.next() {
125+
cdecl_impl(arg, &mut tmp, None)?; // each arg is an unnamed decl
126+
s.push_str(&tmp);
127+
if args.peek().is_some() {
128+
s.push_str(", ");
129+
tmp.clear();
130+
}
131+
}
132+
s.push(')');
133+
cdecl_impl(ret, s, Some(cty))?;
134+
}
135+
}
136+
Ok(())
137+
}
138+
139+
/// Checked with <https://cdecl.org/>.
140+
#[cfg(test)]
141+
mod tests {
142+
use super::*;
143+
144+
/// Check that a decl named "foo" matches `expected`.
145+
#[track_caller]
146+
fn assert_decl(ty: &CTy, expected: &str) {
147+
assert_eq!(cdecl(ty, "foo".to_owned()).unwrap(), expected);
148+
}
149+
150+
/* Helpful constructors */
151+
152+
fn mut_int() -> CTy {
153+
named("int", Mut)
154+
}
155+
156+
fn const_int() -> CTy {
157+
named("int", Const)
158+
}
159+
160+
fn named(name: &str, constness: Constness) -> CTy {
161+
CTy::Named {
162+
name: name.into(),
163+
constness,
164+
}
165+
}
166+
167+
fn ptr(inner: CTy, constness: Constness) -> CTy {
168+
CTy::Ptr {
169+
ty: Box::new(inner),
170+
constness,
171+
}
172+
}
173+
174+
fn array(inner: CTy, len: Option<&str>) -> CTy {
175+
CTy::Array {
176+
ty: Box::new(inner),
177+
len: len.map(Into::into),
178+
}
179+
}
180+
181+
/// Function type (not a pointer)
182+
fn func(args: Vec<CTy>, ret: CTy) -> CTy {
183+
CTy::Fn {
184+
args,
185+
ret: Box::new(ret),
186+
}
187+
}
188+
189+
/// Function pointer
190+
fn func_ptr(args: Vec<CTy>, ret: CTy) -> CTy {
191+
ptr(
192+
CTy::Fn {
193+
args,
194+
ret: Box::new(ret),
195+
},
196+
Mut,
197+
)
198+
}
199+
200+
#[test]
201+
fn basic() {
202+
assert_decl(&const_int(), "const int foo");
203+
assert_decl(&mut_int(), "int foo");
204+
}
205+
206+
#[test]
207+
fn test_ptr() {
208+
assert_decl(&ptr(const_int(), Mut), "const int *foo");
209+
assert_decl(&ptr(const_int(), Const), "const int *const foo");
210+
assert_decl(&ptr(mut_int(), Mut), "int *foo");
211+
assert_decl(&ptr(mut_int(), Const), "int *const foo");
212+
assert_decl(&ptr(ptr(mut_int(), Mut), Mut), "int **foo");
213+
assert_decl(&ptr(ptr(mut_int(), Const), Mut), "int *const *foo");
214+
assert_decl(&ptr(ptr(mut_int(), Mut), Const), "int **const foo");
215+
assert_decl(&ptr(ptr(mut_int(), Const), Const), "int *const *const foo");
216+
assert_decl(
217+
&ptr(ptr(const_int(), Const), Const),
218+
"const int *const *const foo",
219+
);
220+
}
221+
222+
#[test]
223+
fn test_array() {
224+
assert_decl(&array(const_int(), None), "const int foo[]");
225+
assert_decl(&array(const_int(), Some("20")), "const int foo[20]");
226+
let ty = array(
227+
array(
228+
array(
229+
array(
230+
array(array(mut_int(), Some("BLASTOFF")), Some("1")),
231+
Some("2"),
232+
),
233+
Some("3"),
234+
),
235+
Some("4"),
236+
),
237+
Some("5"),
238+
);
239+
assert_decl(&ty, "int foo[5][4][3][2][1][BLASTOFF]");
240+
}
241+
242+
#[test]
243+
fn test_func() {
244+
// Function types (not pointers)
245+
assert_decl(&func(vec![], mut_int()), "int foo()");
246+
assert_decl(
247+
&func(vec![const_int()], const_int()),
248+
"const int foo(const int)",
249+
);
250+
assert_decl(
251+
&func(vec![const_int(), mut_int()], mut_int()),
252+
"int foo(const int, int)",
253+
);
254+
}
255+
256+
#[test]
257+
fn test_func_invalid_ret() {
258+
// Can't return an array
259+
assert!(cdecl(&func(vec![], array(mut_int(), None)), "foo".to_owned(),).is_err(),);
260+
// Can't return a function
261+
assert!(cdecl(&func(vec![], func(vec![], mut_int()),), "foo".to_owned(),).is_err(),);
262+
}
263+
264+
#[test]
265+
fn test_func_ptr() {
266+
assert_decl(&func_ptr(vec![mut_int()], mut_int()), "int (*foo)(int)");
267+
assert_decl(&func_ptr(vec![mut_int()], mut_int()), "int (*foo)(int)");
268+
assert_decl(&array(const_int(), Some("20")), "const int foo[20]");
269+
270+
// declare foo as pointer to function (pointer to function (pointer to function (pointer
271+
// to function (char) returning char) returning pointer to function (short) returning short) returning
272+
// pointer to function (long) returning long, pointer to function (long long) returning long long)
273+
// returning pointer to function (int) returning int
274+
let make_func_ptr = |ty: &str| func_ptr(vec![named(ty, Mut)], named(ty, Mut));
275+
let inception = func_ptr(
276+
vec![
277+
func_ptr(
278+
vec![func_ptr(
279+
vec![make_func_ptr("char")],
280+
make_func_ptr("short"),
281+
)],
282+
make_func_ptr("long"),
283+
),
284+
make_func_ptr("long long"),
285+
],
286+
make_func_ptr("int"),
287+
);
288+
assert_decl(
289+
&inception,
290+
"int (*(*foo)(long (*(*)(short (*(*)(\
291+
char (*)(char)))(short)))(long), \
292+
long long (*)(long long)\
293+
))(int)",
294+
);
295+
}
296+
297+
/// Check that parens are added where needed
298+
#[test]
299+
fn test_precedence() {
300+
// pointer to an array of ints
301+
assert_decl(&ptr(array(mut_int(), None), Mut), "int (*foo)[]");
302+
// array of pointers of ints
303+
assert_decl(&array(ptr(mut_int(), Mut), None), "int *foo[]");
304+
// pointer to a function returning an int
305+
assert_decl(&func_ptr(vec![], named("int", Mut)), "int (*foo)()");
306+
}
307+
308+
#[test]
309+
fn test_unnamed() {
310+
// Function args are usually unnamed
311+
assert_eq!(cdecl(&mut_int(), String::new()).unwrap(), "int");
312+
assert_eq!(
313+
cdecl(&ptr(array(mut_int(), None), Mut), String::new()).unwrap(),
314+
"int (*)[]"
315+
);
316+
assert_eq!(
317+
cdecl(&array(ptr(mut_int(), Mut), None), String::new()).unwrap(),
318+
"int *[]"
319+
);
320+
}
321+
322+
#[test]
323+
fn test_compose() {
324+
assert_decl(&array(ptr(const_int(), Mut), None), "const int *foo[]");
325+
let ty = ptr(
326+
func(
327+
vec![
328+
array(named("int", Mut), Some("ARR_LEN")),
329+
ptr(named("short", Const), Mut),
330+
],
331+
ptr(named("long", Const), Mut),
332+
),
333+
Mut,
334+
);
335+
assert_decl(&ty, "const long *(*foo)(int [ARR_LEN], const short *)");
336+
337+
// function returning a pointer to a function returning an int
338+
let ty = func(vec![], func_ptr(vec![], named("int", Mut)));
339+
assert_decl(&ty, "int (*foo())()");
340+
341+
let ty = array(
342+
func_ptr(vec![], ptr(array(named("char", Mut), Some("5")), Mut)),
343+
Some("3"),
344+
);
345+
assert_decl(&ty, "char (*(*foo[3])())[5]");
346+
347+
// declare foo as pointer to function (pointer to const void) returning pointer to array
348+
// 3 of int
349+
let ty = func_ptr(
350+
vec![ptr(named("void", Const), Mut)],
351+
ptr(array(named("int", Mut), Some("3")), Mut),
352+
);
353+
assert_decl(&ty, "int (*(*foo)(const void *))[3]");
354+
}
355+
}

ctest-next/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
mod tests;
1313

1414
mod ast;
15+
mod cdecl;
1516
mod ffi_items;
1617
mod generator;
1718
mod macro_expansion;

0 commit comments

Comments
 (0)