From cdc13238de107bd604059f0197f0225a55a06b41 Mon Sep 17 00:00:00 2001 From: rakdecoder <22bcs015@iiitdmj.ac.in> Date: Thu, 12 Jun 2025 15:57:57 +0530 Subject: [PATCH] Add basic C/C++ support --- .DS_Store | 0 cli/src/args.rs | 2 + cli/src/main.rs | 2 + cli/src/parse.rs | 2 + core/src/language/c.rs | 143 +++++++++++++++++++++++++++++++++++++++ core/src/language/cpp.rs | 137 +++++++++++++++++++++++++++++++++++++ core/src/language/mod.rs | 8 +++ lib/src/lib.rs | 3 + tests/c_cpp_test.rs | 24 +++++++ 9 files changed, 321 insertions(+) create mode 100644 .DS_Store create mode 100644 core/src/language/c.rs create mode 100644 core/src/language/cpp.rs create mode 100644 tests/c_cpp_test.rs diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 00000000..e69de29b diff --git a/cli/src/args.rs b/cli/src/args.rs index 05b79619..703b59e0 100644 --- a/cli/src/args.rs +++ b/cli/src/args.rs @@ -12,6 +12,8 @@ pub enum AvailableLanguage { Go, #[cfg(feature = "python")] Python, + C, + Cpp, } #[derive(clap::Parser)] diff --git a/cli/src/main.rs b/cli/src/main.rs index 7a702b4e..c40d988c 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -233,6 +233,8 @@ fn language( SupportedLanguage::Python => { panic!("python support is currently experimental and must be enabled as a feature flag for typeshare-cli") } + SupportedLanguage::C => Box::new(C::default()), + SupportedLanguage::Cpp => Box::new(Cpp::default()), } } diff --git a/cli/src/parse.rs b/cli/src/parse.rs index 2ae38fe2..2cc6dc4b 100644 --- a/cli/src/parse.rs +++ b/cli/src/parse.rs @@ -59,6 +59,8 @@ fn output_file_name(language_type: SupportedLanguage, crate_name: &CrateName) -> SupportedLanguage::Swift => pascal_case(), SupportedLanguage::TypeScript => snake_case(), SupportedLanguage::Python => snake_case(), + SupportedLanguage::C => snake_case(), + SupportedLanguage::Cpp => snake_case(), } } diff --git a/core/src/language/c.rs b/core/src/language/c.rs new file mode 100644 index 00000000..63308fa5 --- /dev/null +++ b/core/src/language/c.rs @@ -0,0 +1,143 @@ +use std::io::Write; +use crate::language::Language; +use crate::parser::ParsedData; +use std::collections::HashMap; +use crate::rust_types::RustTypeFormatError; +use crate::rust_types::SpecialRustType; + +#[derive(Default)] +pub struct C { + pub type_mappings: HashMap, +} + +impl Language for C { + fn generate_types( + &mut self, + w: &mut dyn Write, + _imports: &super::CrateTypes, + data: ParsedData, + ) -> std::io::Result<()> { + self.begin_file(w, &data)?; + + let ParsedData { + structs, + enums, + aliases, + consts, + .. + } = data; + + for s in structs { + self.write_struct(w, &s)?; + } + + for e in enums { + self.write_enum(w, &e)?; + } + + for a in aliases { + self.write_type_alias(w, &a)?; + } + + for c in consts { + self.write_const(w, &c)?; + } + + self.end_file(w) + } + + fn type_map(&mut self) -> &HashMap { + &self.type_mappings + } + + fn format_special_type( + &mut self, + special_ty: &SpecialRustType, + generic_types: &[String], + ) -> Result { + Ok(match special_ty { + SpecialRustType::Vec(rtype) => format!("{}*", self.format_type(rtype, generic_types)?), + SpecialRustType::Array(rtype, len) => { + format!("{}[{}]", self.format_type(rtype, generic_types)?, len) + } + }) + } + + fn begin_file(&mut self, w: &mut dyn Write, _parsed_data: &ParsedData) -> std::io::Result<()> { + writeln!(w, "// This code is generated by typeshare")?; + Ok(()) + } + + fn write_imports( + &mut self, + _writer: &mut dyn Write, + _imports: super::ScopedCrateTypes<'_>, + ) -> std::io::Result<()> { + Ok(()) + } +} + SpecialRustType::Slice(rtype) => format!("{}*", self.format_type(rtype, generic_types)?), + SpecialRustType::Option(rtype) => format!("{}*", self.format_type(rtype, generic_types)?), + SpecialRustType::HashMap(rtype1, rtype2) => format!( + "std::map<{}, {}>", + self.format_type(rtype1, generic_types)?, + self.format_type(rtype2, generic_types)? + ), + SpecialRustType::Unit => "void".into(), + SpecialRustType::String => "char*".into(), + SpecialRustType::Char => "char".into(), + SpecialRustType::I8 => "int8_t".into(), + SpecialRustType::U8 => "uint8_t".into(), + SpecialRustType::I16 => "int16_t".into(), + SpecialRustType::U16 => "uint16_t".into(), + SpecialRustType::I32 => "int32_t".into(), + SpecialRustType::U32 => "uint32_t".into(), + SpecialRustType::I64 => "int64_t".into(), + SpecialRustType::U64 => "uint64_t".into(), + SpecialRustType::ISize => "intptr_t".into(), + SpecialRustType::USize => "uintptr_t".into(), + SpecialRustType::Bool => "bool".into(), + SpecialRustType::F32 => "float".into(), + SpecialRustType::F64 => "double".into(), + SpecialRustType::DateTime => "time_t".into(), + }) + } + + fn write_imports( + &mut self, + _writer: &mut dyn Write, + _imports: super::ScopedCrateTypes<'_>, + ) -> std::io::Result<()> { + Ok(()) + } + + fn write_struct(&mut self, w: &mut dyn Write, rs: &crate::rust_types::RustStruct) -> std::io::Result<()> { + writeln!(w, "typedef struct {} {{", rs.id.original)?; + + for field in &rs.fields { + let type_name = self.format_type(&field.ty, &[]).unwrap(); + writeln!(w, " {} {};", type_name, field.id.original)?; + } + + writeln!(w, "}} {};", rs.id.original)?; + Ok(()) + } + + fn write_type_alias(&mut self, w: &mut dyn Write, ta: &crate::rust_types::RustTypeAlias) -> std::io::Result<()> { + let type_name = self.format_type(&ta.r#type, &[]).unwrap(); + writeln!(w, "typedef {} {};", type_name, ta.id.original)?; + Ok(()) + } + + fn write_const(&mut self, w: &mut dyn Write, c: &crate::rust_types::RustConst) -> std::io::Result<()> { + let type_name = self.format_type(&c.r#type, &[]).unwrap(); + writeln!(w, "const {} {} = {};", type_name, c.id.original, c.expr)?; + Ok(()) + } + + fn write_const(&mut self, w: &mut dyn Write, c: &crate::rust_types::RustConst) -> std::io::Result<()> { + let type_name = self.format_type(&c.r#type, &[]).unwrap(); + writeln!(w, "const {} {} = {};", type_name, c.id.original, c.expr)?; + Ok(()) + } +} \ No newline at end of file diff --git a/core/src/language/cpp.rs b/core/src/language/cpp.rs new file mode 100644 index 00000000..08b33fcf --- /dev/null +++ b/core/src/language/cpp.rs @@ -0,0 +1,137 @@ +use std::io::Write; +use crate::language::Language; +use crate::parser::ParsedData; +use std::collections::HashMap; +use crate::rust_types::RustTypeFormatError; +use crate::rust_types::SpecialRustType; + +#[derive(Default)] +pub struct Cpp { + pub type_mappings: HashMap, +} + +impl Language for Cpp { + fn generate_types( + &mut self, + w: &mut dyn Write, + _imports: &super::CrateTypes, + data: ParsedData, + ) -> std::io::Result<()> { + self.begin_file(w, &data)?; + + let ParsedData { + structs, + enums, + aliases, + consts, + .. + } = data; + + for s in structs { + self.write_struct(w, &s)?; + } + + for e in enums { + self.write_enum(w, &e)?; + } + + for a in aliases { + self.write_type_alias(w, &a)?; + } + + for c in consts { + self.write_const(w, &c)?; + } + + self.end_file(w) + } + + fn type_map(&mut self) -> &HashMap { + &self.type_mappings + } + + fn format_special_type( + &mut self, + special_ty: &SpecialRustType, + generic_types: &[String], + ) -> Result { + Ok(match special_ty { + SpecialRustType::Vec(rtype) => format!("std::vector<{}>", self.format_type(rtype, generic_types)?), + SpecialRustType::Array(rtype, len) => { + format!("{}[{}]", self.format_type(rtype, generic_types)?, len) + } + }) + } + + fn begin_file(&mut self, w: &mut dyn Write, _parsed_data: &ParsedData) -> std::io::Result<()> { + writeln!(w, "// This code is generated by typeshare")?; + Ok(()) + } + + fn write_imports( + &mut self, + _writer: &mut dyn Write, + _imports: super::ScopedCrateTypes<'_>, + ) -> std::io::Result<()> { + Ok(()) + } +} + SpecialRustType::Slice(rtype) => format!("std::vector<{}>", self.format_type(rtype, generic_types)?), + SpecialRustType::Option(rtype) => format!("std::optional<{}>", self.format_type(rtype, generic_types)?), + SpecialRustType::HashMap(rtype1, rtype2) => format!( + "std::map<{}, {}>", + self.format_type(rtype1, generic_types)?, + self.format_type(rtype2, generic_types)? + ), + SpecialRustType::Unit => "void".into(), + SpecialRustType::String => "std::string".into(), + SpecialRustType::Char => "char".into(), + SpecialRustType::I8 => "int8_t".into(), + SpecialRustType::U8 => "uint8_t".into(), + SpecialRustType::I16 => "int16_t".into(), + SpecialRustType::U16 => "uint16_t".into(), + SpecialRustType::I32 => "int32_t".into(), + SpecialRustType::U32 => "uint32_t".into(), + SpecialRustType::I64 => "int64_t".into(), + SpecialRustType::U64 => "uint64_t".into(), + SpecialRustType::ISize => "intptr_t".into(), + SpecialRustType::USize => "uintptr_t".into(), + SpecialRustType::Bool => "bool".into(), + SpecialRustType::F32 => "float".into(), + SpecialRustType::F64 => "double".into(), + SpecialRustType::DateTime => "time_t".into(), + }) + } + + fn write_imports( + &mut self, + _writer: &mut dyn Write, + _imports: super::ScopedCrateTypes<'_>, + ) -> std::io::Result<()> { + Ok(()) + } + + fn write_struct(&mut self, w: &mut dyn Write, rs: &crate::rust_types::RustStruct) -> std::io::Result<()> { + writeln!(w, "struct {} {{", rs.id.original)?; + + for field in &rs.fields { + let type_name = self.format_type(&field.ty, &[]).unwrap(); + writeln!(w, " {} {};", type_name, field.id.original)?; + } + + writeln!(w, "}};")?; + Ok(()) + } + + fn write_type_alias(&mut self, w: &mut dyn Write, ta: &crate::rust_types::RustTypeAlias) -> std::io::Result<()> { + let type_name = self.format_type(&ta.r#type, &[]).unwrap(); + writeln!(w, "using {} = {};", ta.id.original, type_name)?; + Ok(()) + } + + fn write_const(&mut self, w: &mut dyn Write, c: &crate::rust_types::RustConst) -> std::io::Result<()> { + let type_name = self.format_type(&c.r#type, &[]).unwrap(); + writeln!(w, "const {} {} = {};", type_name, c.id.original, c.expr)?; + Ok(()) + } +} \ No newline at end of file diff --git a/core/src/language/mod.rs b/core/src/language/mod.rs index 83c6e82b..a4021e25 100644 --- a/core/src/language/mod.rs +++ b/core/src/language/mod.rs @@ -25,10 +25,14 @@ mod python; mod scala; mod swift; mod typescript; +mod c; +mod cpp; pub use go::Go; pub use kotlin::Kotlin; pub use python::Python; +pub use c::C; +pub use cpp::Cpp; pub use scala::Scala; pub use swift::GenericConstraints; pub use swift::Swift; @@ -103,6 +107,8 @@ pub enum SupportedLanguage { Swift, TypeScript, Python, + C, + Cpp, } impl SupportedLanguage { @@ -121,6 +127,8 @@ impl SupportedLanguage { SupportedLanguage::Swift => "swift", SupportedLanguage::TypeScript => "ts", SupportedLanguage::Python => "py", + SupportedLanguage::C => "c", + SupportedLanguage::Cpp => "cpp", } } } diff --git a/lib/src/lib.rs b/lib/src/lib.rs index aa463446..cc1f8e80 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -9,3 +9,6 @@ pub use integer::{usize_from_u53_saturated, I54, U53}; // TODO: Expose and use this module's functionality. #[allow(dead_code)] mod json_date; + +#[cfg(test)] +mod c_cpp_test; diff --git a/tests/c_cpp_test.rs b/tests/c_cpp_test.rs new file mode 100644 index 00000000..a10554dc --- /dev/null +++ b/tests/c_cpp_test.rs @@ -0,0 +1,24 @@ +#[cfg(test)] +mod tests { + use typeshare::typeshare; + + #[test] + fn test_c_cpp_struct_generation() { + #[typeshare] + struct MyStruct { + field1: i32, + field2: String, + } + + // Generate C code + let c_code = typeshare::codegen::(&MyStruct::definition()).unwrap(); + println!("C code:\\n{}", c_code); + + // Generate C++ code + let cpp_code = typeshare::codegen::(&MyStruct::definition()).unwrap(); + println!("C++ code:\\n{}", cpp_code); + + assert!(c_code.contains("typedef struct MyStruct")); + assert!(cpp_code.contains("struct MyStruct")); + } +} \ No newline at end of file