use crate::import::{ImportEntry, ImportGroup};
use crate::lang::CodeLang;
use crate::spec::modifiers::{DeclarationContext, TypeKind, Visibility};
#[derive(Debug, Clone)]
pub struct CppLang {
pub indent: String,
pub extension: String,
}
impl Default for CppLang {
fn default() -> Self {
Self {
indent: " ".to_string(),
extension: "cpp".to_string(),
}
}
}
impl CppLang {
pub fn new() -> Self {
Self::default()
}
pub fn header() -> Self {
Self {
indent: " ".to_string(),
extension: "hpp".to_string(),
}
}
pub fn header_h() -> 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
}
}
#[rustfmt::skip]
const CPP_RESERVED: &[&str] = &[
"auto", "break", "case", "char", "const", "continue", "default", "do",
"double", "else", "enum", "extern", "float", "for", "goto", "if",
"inline", "int", "long", "register", "return", "short", "signed",
"sizeof", "static", "struct", "switch", "typedef", "union", "unsigned",
"void", "volatile", "while",
"alignas", "alignof", "and", "and_eq", "asm", "atomic_cancel",
"atomic_commit", "atomic_noexcept", "bitand", "bitor", "bool",
"catch", "char8_t", "char16_t", "char32_t", "class", "co_await",
"co_return", "co_yield", "compl", "concept", "const_cast", "consteval",
"constexpr", "constinit", "decltype", "delete", "dynamic_cast",
"explicit", "export", "false", "friend", "module", "mutable",
"namespace", "new", "noexcept", "not", "not_eq", "nullptr",
"operator", "or", "or_eq", "override", "private", "protected",
"public", "reflexpr", "reinterpret_cast", "requires", "static_assert",
"static_cast", "template", "this", "thread_local", "throw", "true",
"try", "typeid", "typename", "using", "virtual", "wchar_t", "xor",
"xor_eq",
];
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 CppLang {
fn file_extension(&self) -> &str {
&self.extension
}
fn reserved_words(&self) -> &[&str] {
CPP_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 {
let mut result = String::new();
for (i, line) in lines.iter().enumerate() {
if i > 0 {
result.push('\n');
}
if line.is_empty() {
result.push_str("///");
} else {
result.push_str("/// ");
result.push_str(line);
}
}
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::Class => "class",
TypeKind::Struct => "struct",
TypeKind::Enum => "enum class",
TypeKind::Interface | TypeKind::Trait => "class",
TypeKind::TypeAlias => "using",
TypeKind::Newtype => "struct",
}
}
fn methods_inside_type_body(&self, kind: TypeKind) -> bool {
match kind {
TypeKind::Class | TypeKind::Interface | TypeKind::Trait => true,
TypeKind::Struct | TypeKind::Enum | TypeKind::TypeAlias | TypeKind::Newtype => false,
}
}
fn optional_field_style(&self) -> crate::lang::config::OptionalFieldStyle {
crate::lang::config::OptionalFieldStyle::TypeWrap {
open: "std::optional<",
close: ">",
}
}
fn type_presentation(&self) -> crate::lang::config::TypePresentationConfig<'_> {
crate::lang::config::TypePresentationConfig {
array: crate::type_name::TypePresentation::GenericWrap {
name: "std::vector",
},
readonly_array: Some(crate::type_name::TypePresentation::GenericWrap {
name: "std::vector",
}),
optional: crate::type_name::TypePresentation::GenericWrap {
name: "std::optional",
},
map: crate::type_name::TypePresentation::GenericWrap { name: "std::map" },
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,
wrapper_open: "std::function<",
wrapper_close: ">",
..Default::default()
},
tuple: crate::type_name::TypePresentation::GenericWrap { name: "std::tuple" },
..Default::default()
}
}
fn generic_syntax(&self) -> crate::lang::config::GenericSyntaxConfig<'_> {
crate::lang::config::GenericSyntaxConfig {
constraint_keyword: "",
constraint_separator: "",
context_bound_keyword: "",
..Default::default()
}
}
fn module_separator(&self) -> Option<&str> {
Some("::")
}
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: " ",
abstract_keyword: "virtual ",
..Default::default()
}
}
fn type_decl_syntax(&self) -> crate::lang::config::TypeDeclSyntaxConfig<'_> {
crate::lang::config::TypeDeclSyntaxConfig {
type_before_name: true,
return_type_is_prefix: true,
super_type_keyword: " : public ",
super_type_separator: ", public ",
..Default::default()
}
}
fn enum_and_annotation(&self) -> crate::lang::config::EnumAndAnnotationConfig<'_> {
crate::lang::config::EnumAndAnnotationConfig {
annotation_prefix: "[[",
annotation_suffix: "]]",
..Default::default()
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_file_extension() {
let cpp = CppLang::new();
assert_eq!(cpp.file_extension(), "cpp");
}
#[test]
fn test_header_extension() {
let cpp = CppLang::header();
assert_eq!(cpp.file_extension(), "hpp");
}
#[test]
fn test_header_h_extension() {
let cpp = CppLang::header_h();
assert_eq!(cpp.file_extension(), "h");
}
#[test]
fn test_escape_reserved() {
let cpp = CppLang::new();
assert_eq!(cpp.escape_reserved("class"), "class_");
assert_eq!(cpp.escape_reserved("virtual"), "virtual_");
assert_eq!(cpp.escape_reserved("template"), "template_");
assert_eq!(cpp.escape_reserved("name"), "name");
}
#[test]
fn test_render_imports_system() {
let cpp = CppLang::new();
let imports = ImportGroup {
entries: vec![ImportEntry {
module: "iostream".into(),
name: "std::cout".into(),
alias: None,
is_type_only: false,
is_side_effect: false,
is_wildcard: false,
}],
};
assert_eq!(cpp.render_imports(&imports), "#include <iostream>");
}
#[test]
fn test_render_imports_local() {
let cpp = CppLang::new();
let imports = ImportGroup {
entries: vec![ImportEntry {
module: "./myclass.hpp".into(),
name: "MyClass".into(),
alias: None,
is_type_only: false,
is_side_effect: false,
is_wildcard: false,
}],
};
assert_eq!(cpp.render_imports(&imports), "#include \"myclass.hpp\"");
}
#[test]
fn test_render_imports_grouped() {
let cpp = CppLang::new();
let imports = ImportGroup {
entries: vec![
ImportEntry {
module: "iostream".into(),
name: "std::cout".into(),
alias: None,
is_type_only: false,
is_side_effect: false,
is_wildcard: false,
},
ImportEntry {
module: "vector".into(),
name: "std::vector".into(),
alias: None,
is_type_only: false,
is_side_effect: false,
is_wildcard: false,
},
ImportEntry {
module: "./myclass.hpp".into(),
name: "MyClass".into(),
alias: None,
is_type_only: false,
is_side_effect: false,
is_wildcard: false,
},
],
};
let output = cpp.render_imports(&imports);
let lines: Vec<&str> = output.lines().collect();
assert_eq!(lines[0], "#include <iostream>");
assert_eq!(lines[1], "#include <vector>");
assert_eq!(lines[2], "");
assert_eq!(lines[3], "#include \"myclass.hpp\"");
}
#[test]
fn test_render_imports_dedup() {
let cpp = CppLang::new();
let imports = ImportGroup {
entries: vec![
ImportEntry {
module: "vector".into(),
name: "std::vector".into(),
alias: None,
is_type_only: false,
is_side_effect: false,
is_wildcard: false,
},
ImportEntry {
module: "vector".into(),
name: "std::vector".into(),
alias: None,
is_type_only: false,
is_side_effect: false,
is_wildcard: false,
},
],
};
assert_eq!(cpp.render_imports(&imports), "#include <vector>");
}
#[test]
fn test_doc_comment_single() {
let cpp = CppLang::new();
assert_eq!(
cpp.render_doc_comment(&["A brief description."]),
"/// A brief description."
);
}
#[test]
fn test_doc_comment_multi() {
let cpp = CppLang::new();
let doc = cpp.render_doc_comment(&["Container class.", "", "Thread-safe."]);
assert_eq!(doc, "/// Container class.\n///\n/// Thread-safe.");
}
#[test]
fn test_string_literal() {
let cpp = CppLang::new();
assert_eq!(cpp.render_string_literal("hello"), "\"hello\"");
assert_eq!(cpp.render_string_literal("it\"s"), "\"it\\\"s\"");
assert_eq!(cpp.render_string_literal("new\nline"), "\"new\\nline\"");
}
#[test]
fn test_type_before_name() {
let cpp = CppLang::new();
assert!(cpp.type_decl_syntax().type_before_name);
}
#[test]
fn test_return_type_is_prefix() {
let cpp = CppLang::new();
assert!(cpp.type_decl_syntax().return_type_is_prefix);
}
#[test]
fn test_type_close_terminator() {
let cpp = CppLang::new();
assert_eq!(cpp.block_syntax().type_close_terminator, ";");
}
#[test]
fn test_type_keyword() {
let cpp = CppLang::new();
assert_eq!(cpp.type_keyword(TypeKind::Class), "class");
assert_eq!(cpp.type_keyword(TypeKind::Struct), "struct");
assert_eq!(cpp.type_keyword(TypeKind::Enum), "enum class");
assert_eq!(cpp.type_keyword(TypeKind::Interface), "class");
}
#[test]
fn test_abstract_keyword() {
let cpp = CppLang::new();
assert_eq!(cpp.function_syntax().abstract_keyword, "virtual ");
}
#[test]
fn test_super_type_keyword() {
let cpp = CppLang::new();
assert_eq!(cpp.type_decl_syntax().super_type_keyword, " : public ");
}
#[test]
fn test_super_type_separator() {
let cpp = CppLang::new();
assert_eq!(cpp.type_decl_syntax().super_type_separator, ", public ");
}
#[test]
fn test_methods_inside_type_body() {
let cpp = CppLang::new();
assert!(cpp.methods_inside_type_body(TypeKind::Class));
assert!(cpp.methods_inside_type_body(TypeKind::Interface));
assert!(!cpp.methods_inside_type_body(TypeKind::Struct));
assert!(!cpp.methods_inside_type_body(TypeKind::Enum));
}
#[test]
fn test_is_system_header() {
assert!(is_system_header("iostream"));
assert!(is_system_header("vector"));
assert!(is_system_header("string"));
assert!(!is_system_header("./myclass.hpp"));
assert!(!is_system_header("../utils/helper.h"));
}
#[test]
fn test_cpp_builder_fluent() {
let cpp = CppLang::new().with_indent(" ").with_extension("cxx");
assert_eq!(cpp.file_extension(), "cxx");
assert_eq!(cpp.block_syntax().indent_unit, " ");
}
#[test]
fn test_module_separator() {
let cpp = CppLang::new();
assert_eq!(cpp.module_separator(), Some("::"));
}
}