use crate::import::{ImportEntry, ImportGroup};
use crate::lang::CodeLang;
use crate::spec::modifiers::{DeclarationContext, TypeKind, Visibility};
#[derive(Debug, Clone)]
pub struct CLang {
pub indent: String,
pub extension: String,
}
impl Default for CLang {
fn default() -> Self {
Self {
indent: " ".to_string(),
extension: "c".to_string(),
}
}
}
impl CLang {
pub fn new() -> Self {
Self::default()
}
pub fn header() -> Self {
Self {
indent: " ".to_string(),
extension: "h".to_string(),
}
}
pub fn with_indent(mut self, s: &str) -> Self {
self.indent = s.to_string();
self
}
pub fn with_extension(mut self, s: &str) -> Self {
self.extension = s.to_string();
self
}
}
const C_RESERVED: &[&str] = &[
"auto",
"break",
"case",
"char",
"const",
"continue",
"default",
"do",
"double",
"else",
"enum",
"extern",
"float",
"for",
"goto",
"if",
"inline",
"int",
"long",
"register",
"restrict",
"return",
"short",
"signed",
"sizeof",
"static",
"struct",
"switch",
"typedef",
"union",
"unsigned",
"void",
"volatile",
"while",
"_Alignas",
"_Alignof",
"_Atomic",
"_Bool",
"_Complex",
"_Generic",
"_Imaginary",
"_Noreturn",
"_Static_assert",
"_Thread_local",
];
fn is_system_header(module: &str) -> bool {
!module.starts_with("./") && !module.starts_with("../")
}
fn strip_local_prefix(module: &str) -> &str {
module.strip_prefix("./").unwrap_or(module)
}
impl CodeLang for CLang {
fn file_extension(&self) -> &str {
&self.extension
}
fn reserved_words(&self) -> &[&str] {
C_RESERVED
}
fn render_imports(&self, imports: &ImportGroup) -> String {
if imports.entries.is_empty() {
return String::new();
}
let mut seen = std::collections::BTreeSet::new();
let mut system_headers: Vec<&ImportEntry> = Vec::new();
let mut local_headers: Vec<&ImportEntry> = Vec::new();
for entry in &imports.entries {
if seen.contains(&entry.module) {
continue;
}
seen.insert(&entry.module);
if is_system_header(&entry.module) {
system_headers.push(entry);
} else {
local_headers.push(entry);
}
}
system_headers.sort_by_key(|e| &e.module);
local_headers.sort_by_key(|e| &e.module);
let mut lines: Vec<String> = Vec::new();
for entry in &system_headers {
lines.push(format!("#include <{}>", entry.module));
}
if !system_headers.is_empty() && !local_headers.is_empty() {
lines.push(String::new());
}
for entry in &local_headers {
lines.push(format!(
"#include \"{}\"",
strip_local_prefix(&entry.module)
));
}
lines.join("\n")
}
fn render_string_literal(&self, s: &str) -> String {
format!(
"\"{}\"",
s.replace('\\', "\\\\")
.replace('"', "\\\"")
.replace('\n', "\\n")
.replace('\t', "\\t")
.replace('\r', "\\r")
.replace('\0', "\\0")
)
}
fn render_doc_comment(&self, lines: &[&str]) -> String {
if lines.len() == 1 {
format!("/* {} */", lines[0])
} else {
let mut result = String::from("/*");
for line in lines {
result.push('\n');
if line.is_empty() {
result.push_str(" *");
} else {
result.push_str(" * ");
result.push_str(line);
}
}
result.push_str("\n */");
result
}
}
fn line_comment_prefix(&self) -> &str {
"//"
}
fn render_visibility(&self, _vis: Visibility, _ctx: DeclarationContext) -> &str {
""
}
fn function_keyword(&self, _ctx: DeclarationContext) -> &str {
""
}
fn type_keyword(&self, kind: TypeKind) -> &str {
match kind {
TypeKind::Struct | TypeKind::Class => "struct",
TypeKind::Enum => "enum",
TypeKind::Interface | TypeKind::Trait => "struct",
TypeKind::TypeAlias => "typedef",
TypeKind::Newtype => "typedef",
}
}
fn methods_inside_type_body(&self, _kind: TypeKind) -> bool {
false
}
fn render_newtype_line(&self, _vis: &str, name: &str, inner: &str) -> String {
format!("typedef {inner} {name};")
}
fn optional_field_style(&self) -> crate::lang::config::OptionalFieldStyle {
crate::lang::config::OptionalFieldStyle::TypePrefix("*")
}
fn type_presentation(&self) -> crate::lang::config::TypePresentationConfig<'_> {
crate::lang::config::TypePresentationConfig {
pointer: crate::type_name::TypePresentation::Postfix { suffix: "*" },
reference: crate::type_name::TypePresentation::Surround {
prefix: "const ",
suffix: "*",
},
reference_mut: crate::type_name::TypePresentation::Postfix { suffix: "*" },
function: crate::type_name::FunctionPresentation {
arrow: "",
return_first: true,
..Default::default()
},
..Default::default()
}
}
fn generic_syntax(&self) -> crate::lang::config::GenericSyntaxConfig<'_> {
crate::lang::config::GenericSyntaxConfig {
constraint_keyword: "",
constraint_separator: "",
context_bound_keyword: "",
..Default::default()
}
}
fn block_syntax(&self) -> crate::lang::config::BlockSyntaxConfig<'_> {
crate::lang::config::BlockSyntaxConfig {
indent_unit: &self.indent,
field_terminator: ";",
type_close_terminator: ";",
..Default::default()
}
}
fn function_syntax(&self) -> crate::lang::config::FunctionSyntaxConfig<'_> {
crate::lang::config::FunctionSyntaxConfig {
return_type_separator: " ",
..Default::default()
}
}
fn type_decl_syntax(&self) -> crate::lang::config::TypeDeclSyntaxConfig<'_> {
crate::lang::config::TypeDeclSyntaxConfig {
type_before_name: true,
return_type_is_prefix: true,
type_alias_target_first: true,
..Default::default()
}
}
fn enum_and_annotation(&self) -> crate::lang::config::EnumAndAnnotationConfig<'_> {
crate::lang::config::EnumAndAnnotationConfig {
annotation_prefix: "__attribute__((",
annotation_suffix: "))",
..Default::default()
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_file_extension() {
let c = CLang::new();
assert_eq!(c.file_extension(), "c");
}
#[test]
fn test_header_extension() {
let c = CLang::header();
assert_eq!(c.file_extension(), "h");
}
#[test]
fn test_escape_reserved() {
let c = CLang::new();
assert_eq!(c.escape_reserved("int"), "int_");
assert_eq!(c.escape_reserved("struct"), "struct_");
assert_eq!(c.escape_reserved("name"), "name");
}
#[test]
fn test_render_imports_system() {
let c = CLang::new();
let imports = ImportGroup {
entries: vec![ImportEntry {
module: "stdio.h".into(),
name: "printf".into(),
alias: None,
is_type_only: false,
is_side_effect: false,
is_wildcard: false,
}],
};
assert_eq!(c.render_imports(&imports), "#include <stdio.h>");
}
#[test]
fn test_render_imports_local() {
let c = CLang::new();
let imports = ImportGroup {
entries: vec![ImportEntry {
module: "./config.h".into(),
name: "Config".into(),
alias: None,
is_type_only: false,
is_side_effect: false,
is_wildcard: false,
}],
};
assert_eq!(c.render_imports(&imports), "#include \"config.h\"");
}
#[test]
fn test_render_imports_grouped() {
let c = CLang::new();
let imports = ImportGroup {
entries: vec![
ImportEntry {
module: "stdio.h".into(),
name: "printf".into(),
alias: None,
is_type_only: false,
is_side_effect: false,
is_wildcard: false,
},
ImportEntry {
module: "stdlib.h".into(),
name: "malloc".into(),
alias: None,
is_type_only: false,
is_side_effect: false,
is_wildcard: false,
},
ImportEntry {
module: "./config.h".into(),
name: "Config".into(),
alias: None,
is_type_only: false,
is_side_effect: false,
is_wildcard: false,
},
],
};
let output = c.render_imports(&imports);
let lines: Vec<&str> = output.lines().collect();
assert_eq!(lines[0], "#include <stdio.h>");
assert_eq!(lines[1], "#include <stdlib.h>");
assert_eq!(lines[2], "");
assert_eq!(lines[3], "#include \"config.h\"");
}
#[test]
fn test_render_imports_dedup() {
let c = CLang::new();
let imports = ImportGroup {
entries: vec![
ImportEntry {
module: "stdio.h".into(),
name: "printf".into(),
alias: None,
is_type_only: false,
is_side_effect: false,
is_wildcard: false,
},
ImportEntry {
module: "stdio.h".into(),
name: "fprintf".into(),
alias: None,
is_type_only: false,
is_side_effect: false,
is_wildcard: false,
},
],
};
assert_eq!(c.render_imports(&imports), "#include <stdio.h>");
}
#[test]
fn test_doc_comment_single() {
let c = CLang::new();
assert_eq!(
c.render_doc_comment(&["A brief description."]),
"/* A brief description. */"
);
}
#[test]
fn test_doc_comment_multi() {
let c = CLang::new();
let doc = c.render_doc_comment(&["Configuration struct.", "", "Thread-safe."]);
assert!(doc.starts_with("/*"));
assert!(doc.ends_with(" */"));
assert!(doc.contains(" * Configuration struct."));
assert!(doc.contains(" *\n"));
assert!(doc.contains(" * Thread-safe."));
}
#[test]
fn test_string_literal() {
let c = CLang::new();
assert_eq!(c.render_string_literal("hello"), "\"hello\"");
assert_eq!(c.render_string_literal("it\"s"), "\"it\\\"s\"");
assert_eq!(c.render_string_literal("new\nline"), "\"new\\nline\"");
}
#[test]
fn test_type_before_name() {
let c = CLang::new();
assert!(c.type_decl_syntax().type_before_name);
}
#[test]
fn test_return_type_is_prefix() {
let c = CLang::new();
assert!(c.type_decl_syntax().return_type_is_prefix);
}
#[test]
fn test_type_close_terminator() {
let c = CLang::new();
assert_eq!(c.block_syntax().type_close_terminator, ";");
}
#[test]
fn test_is_system_header() {
assert!(is_system_header("stdio.h"));
assert!(is_system_header("stdlib.h"));
assert!(is_system_header("string.h"));
assert!(!is_system_header("./config.h"));
assert!(!is_system_header("../utils/helper.h"));
}
#[test]
fn test_c_builder_fluent() {
let c = CLang::new().with_indent("\t").with_extension("h");
assert_eq!(c.file_extension(), "h");
assert_eq!(c.block_syntax().indent_unit, "\t");
}
#[test]
fn test_module_separator() {
let c = CLang::new();
assert_eq!(c.module_separator(), None);
}
}