#![allow(dead_code)]
pub mod staticlib_header;
pub type CIdent = String;
#[derive(Debug, Clone, Default)]
#[allow(dead_code)]
pub enum CType {
#[default]
Void,
PointerTo {
is_const: bool,
inner: Box<CType>,
},
U8,
U16,
U32,
U64,
USize,
I8,
I16,
I32,
I64,
ISize,
Function {
arguments: Vec<CType>,
return_value: Option<Box<CType>>,
},
Array {
inner: Box<CType>,
},
TypeDef(String),
}
impl CType {
pub fn void_ptr() -> Self {
CType::PointerTo {
is_const: false,
inner: Box::new(CType::Void),
}
}
#[allow(dead_code)]
pub fn const_void_ptr() -> Self {
CType::PointerTo {
is_const: true,
inner: Box::new(CType::Void),
}
}
fn generate_c(&self, w: &mut String) {
match &self {
Self::Void => {
w.push_str("void");
}
Self::PointerTo { is_const, inner } => {
if *is_const {
w.push_str("const ");
}
inner.generate_c(w);
w.push('*');
}
Self::U8 => {
w.push_str("unsigned char");
}
Self::U16 => {
w.push_str("unsigned short");
}
Self::U32 => {
w.push_str("unsigned int");
}
Self::U64 => {
w.push_str("unsigned long long");
}
Self::USize => {
w.push_str("unsigned size_t");
}
Self::I8 => {
w.push_str("char");
}
Self::I16 => {
w.push_str("short");
}
Self::I32 => {
w.push_str("int");
}
Self::I64 => {
w.push_str("long long");
}
Self::ISize => {
w.push_str("size_t");
}
Self::Function {
arguments,
return_value,
} => {
#[allow(clippy::borrowed_box)]
let ret: CType = return_value
.as_ref()
.map(|i: &Box<CType>| (**i).clone())
.unwrap_or_default();
ret.generate_c(w);
w.push(' ');
w.push_str("(*)");
w.push('(');
match arguments.len() {
l if l > 1 => {
for arg in &arguments[..arguments.len() - 1] {
arg.generate_c(w);
w.push_str(", ");
}
arguments.last().unwrap().generate_c(w);
}
1 => {
arguments[0].generate_c(w);
}
_ => {}
}
w.push(')');
}
Self::Array { inner } => {
inner.generate_c(w);
w.push_str("[]");
}
Self::TypeDef(inner) => {
w.push_str(inner);
}
}
}
fn generate_c_with_name(&self, name: &str, w: &mut String) {
match &self {
Self::PointerTo { .. }
| Self::TypeDef { .. }
| Self::Void
| Self::U8
| Self::U16
| Self::U32
| Self::U64
| Self::USize
| Self::I8
| Self::I16
| Self::I32
| Self::I64
| Self::ISize => {
self.generate_c(w);
w.push(' ');
w.push_str(name);
}
Self::Function {
arguments,
return_value,
} => {
#[allow(clippy::borrowed_box)]
let ret: CType = return_value
.as_ref()
.map(|i: &Box<CType>| (**i).clone())
.unwrap_or_default();
ret.generate_c(w);
w.push(' ');
w.push_str(name);
w.push('(');
match arguments.len() {
l if l > 1 => {
for arg in &arguments[..arguments.len() - 1] {
arg.generate_c(w);
w.push_str(", ");
}
arguments.last().unwrap().generate_c(w);
}
1 => {
arguments[0].generate_c(w);
}
_ => {}
}
w.push(')');
}
Self::Array { inner } => {
inner.generate_c(w);
w.push(' ');
w.push_str(name);
w.push_str("[]");
}
}
}
}
#[derive(Debug, Clone)]
pub enum CStatement {
Declaration {
name: CIdent,
is_extern: bool,
is_const: bool,
ctype: CType,
definition: Option<Box<CStatement>>,
},
LiteralArray {
items: Vec<CStatement>,
},
LiteralConstant {
value: String,
},
Cast {
target_type: CType,
expression: Box<CStatement>,
},
TypeDef {
source_type: CType,
new_name: CIdent,
},
}
impl CStatement {
fn generate_c(&self, w: &mut String) {
match &self {
Self::Declaration {
name,
is_extern,
is_const,
ctype,
definition,
} => {
if *is_const {
w.push_str("const ");
}
if *is_extern {
w.push_str("extern ");
}
ctype.generate_c_with_name(name, w);
if let Some(def) = definition {
w.push_str(" = ");
def.generate_c(w);
}
w.push(';');
w.push('\n');
}
Self::LiteralArray { items } => {
w.push('{');
if !items.is_empty() {
w.push('\n');
}
for item in items {
w.push('\t');
item.generate_c(w);
w.push(',');
w.push('\n');
}
w.push('}');
}
Self::LiteralConstant { value } => {
w.push_str(value);
}
Self::Cast {
target_type,
expression,
} => {
w.push('(');
target_type.generate_c(w);
w.push(')');
w.push(' ');
expression.generate_c(w);
}
Self::TypeDef {
source_type,
new_name,
} => {
w.push_str("typedef ");
if let CType::Function { .. } = source_type {
source_type.generate_c_with_name(&format!("(*{})", new_name), w);
} else {
source_type.generate_c(w);
w.push(' ');
w.push_str(new_name);
}
w.push(';');
w.push('\n');
}
}
}
}
pub fn generate_c(statements: &[CStatement]) -> String {
let mut out = String::new();
for statement in statements {
statement.generate_c(&mut out);
}
out
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn generate_types() {
macro_rules! assert_c_type {
($ctype:expr, $expected:expr) => {
let mut w = String::new();
let ctype = $ctype;
ctype.generate_c(&mut w);
assert_eq!(w, $expected);
};
}
assert_c_type!(CType::Void, "void");
assert_c_type!(CType::void_ptr(), "void*");
assert_c_type!(CType::const_void_ptr(), "const void*");
assert_c_type!(CType::U8, "unsigned char");
assert_c_type!(CType::U16, "unsigned short");
assert_c_type!(CType::U32, "unsigned int");
assert_c_type!(CType::U64, "unsigned long long");
assert_c_type!(CType::USize, "unsigned size_t");
assert_c_type!(CType::I8, "char");
assert_c_type!(CType::I16, "short");
assert_c_type!(CType::I32, "int");
assert_c_type!(CType::I64, "long long");
assert_c_type!(CType::ISize, "size_t");
assert_c_type!(CType::TypeDef("my_type".to_string()), "my_type");
assert_c_type!(
CType::Function {
arguments: vec![CType::U8, CType::ISize],
return_value: None
},
"void (*)(unsigned char, size_t)"
);
assert_c_type!(
CType::Function {
arguments: vec![],
return_value: Some(Box::new(CType::ISize))
},
"size_t (*)()"
);
assert_c_type!(
CType::PointerTo {
is_const: true,
inner: Box::new(CType::PointerTo {
is_const: false,
inner: Box::new(CType::U32)
})
},
"const unsigned int**"
);
}
#[test]
fn generate_types_with_names() {
macro_rules! assert_c_type {
($ctype:expr, $name:literal, $expected:expr) => {
let mut w = String::new();
let ctype = $ctype;
ctype.generate_c_with_name($name, &mut w);
assert_eq!(w, $expected);
};
}
assert_c_type!(CType::Void, "main", "void main");
assert_c_type!(CType::void_ptr(), "data", "void* data");
assert_c_type!(CType::const_void_ptr(), "data", "const void* data");
assert_c_type!(CType::U8, "data", "unsigned char data");
assert_c_type!(CType::U16, "data", "unsigned short data");
assert_c_type!(CType::U32, "data", "unsigned int data");
assert_c_type!(CType::U64, "data", "unsigned long long data");
assert_c_type!(CType::USize, "data", "unsigned size_t data");
assert_c_type!(CType::I8, "data", "char data");
assert_c_type!(CType::I16, "data", "short data");
assert_c_type!(CType::I32, "data", "int data");
assert_c_type!(CType::I64, "data", "long long data");
assert_c_type!(CType::ISize, "data", "size_t data");
assert_c_type!(
CType::TypeDef("my_type".to_string()),
"data",
"my_type data"
);
assert_c_type!(
CType::Function {
arguments: vec![CType::U8, CType::ISize],
return_value: None
},
"my_func",
"void my_func(unsigned char, size_t)"
);
assert_c_type!(
CType::Function {
arguments: vec![],
return_value: Some(Box::new(CType::ISize))
},
"my_func",
"size_t my_func()"
);
assert_c_type!(
CType::PointerTo {
is_const: true,
inner: Box::new(CType::PointerTo {
is_const: false,
inner: Box::new(CType::U32)
})
},
"data",
"const unsigned int** data"
);
}
#[test]
fn generate_expressions_works() {
macro_rules! assert_c_expr {
($cexpr:expr, $expected:expr) => {
let mut w = String::new();
let cexpr = $cexpr;
cexpr.generate_c(&mut w);
assert_eq!(w, $expected);
};
}
assert_c_expr!(
CStatement::LiteralConstant {
value: "\"Hello, world!\"".to_string()
},
"\"Hello, world!\""
);
assert_c_expr!(
CStatement::TypeDef {
source_type: CType::Function {
arguments: vec![CType::I32, CType::I32],
return_value: None,
},
new_name: "my_func_ptr".to_string(),
},
"typedef void (*my_func_ptr)(int, int);\n"
);
assert_c_expr!(
CStatement::LiteralArray {
items: vec![
CStatement::LiteralConstant {
value: "1".to_string()
},
CStatement::LiteralConstant {
value: "2".to_string()
},
CStatement::LiteralConstant {
value: "3".to_string()
},
]
},
"{\n\t1,\n\t2,\n\t3,\n}"
);
assert_c_expr!(CStatement::LiteralArray { items: vec![] }, "{}");
assert_c_expr!(
CStatement::Declaration {
name: "my_array".to_string(),
is_extern: false,
is_const: true,
ctype: CType::Array {
inner: Box::new(CType::I32)
},
definition: Some(Box::new(CStatement::LiteralArray {
items: vec![
CStatement::LiteralConstant {
value: "1".to_string()
},
CStatement::LiteralConstant {
value: "2".to_string()
},
CStatement::LiteralConstant {
value: "3".to_string()
},
]
}))
},
"const int my_array[] = {\n\t1,\n\t2,\n\t3,\n};\n"
);
assert_c_expr!(
CStatement::Declaration {
name: "my_array".to_string(),
is_extern: true,
is_const: true,
ctype: CType::Array {
inner: Box::new(CType::I32)
},
definition: None,
},
"const extern int my_array[];\n"
);
}
}