Skip to main content

synapse_codegen_cfs/
c.rs

1use synapse_parser::ast::{
2    ArraySuffix, BaseType, ConstDecl, EnumDef, FieldDef, Item, MessageDef, PrimitiveType,
3    StructDef, SynFile, TypeExpr,
4};
5
6use crate::{
7    constants::{ConstContext, const_context},
8    error::CodegenError,
9    types::{PREAMBLE, ResolvedConstants},
10    util::{
11        emit_doc_lines, emit_indented_doc_lines, find_cc_attr, import_c_header, literal_cc_str,
12        packet_is_command, to_screaming_snake, typed_literal_str,
13    },
14    validate::validate_supported,
15};
16
17/// Generate a NASA cFS C header from a parsed Synapse file.
18pub fn generate_c(file: &SynFile) -> String {
19    try_generate_c(file).expect("parsed Synapse file is not supported by cFS C codegen")
20}
21
22/// Try to generate a NASA cFS C header from a parsed Synapse file.
23pub fn try_generate_c(file: &SynFile) -> Result<String, CodegenError> {
24    try_generate_c_with_constants(file, &ResolvedConstants::new())
25}
26
27/// Try to generate a C header with additional imported constants available for attributes.
28pub fn try_generate_c_with_constants(
29    file: &SynFile,
30    imported_constants: &ResolvedConstants,
31) -> Result<String, CodegenError> {
32    let constants = const_context(file, imported_constants);
33    validate_supported(file, &constants)?;
34    let mut out = String::from(PREAMBLE);
35    emit_c_imports(file, &mut out);
36    emit_items(file, &mut out, &constants);
37    Ok(out)
38}
39
40fn emit_c_imports(file: &SynFile, out: &mut String) {
41    let mut emitted = false;
42    for item in &file.items {
43        if let Item::Import(import) = item {
44            out.push_str(&format!("#include \"{}\"\n", import_c_header(&import.path)));
45            emitted = true;
46        }
47    }
48    if emitted {
49        out.push('\n');
50    }
51}
52
53fn emit_items(file: &SynFile, out: &mut String, constants: &ConstContext<'_>) {
54    emit_command_code_defines(file, out, constants);
55    emit_enum_aliases(file, out);
56    emit_c_types(file, out);
57}
58
59fn emit_command_code_defines(file: &SynFile, out: &mut String, constants: &ConstContext<'_>) {
60    let defines: Vec<_> = file
61        .items
62        .iter()
63        .filter_map(|item| command_code_define(item, constants))
64        .collect();
65
66    if defines.is_empty() {
67        return;
68    }
69
70    out.push_str("/* Command Codes */\n");
71    for define in defines {
72        out.push_str(&define);
73    }
74    out.push('\n');
75}
76
77fn command_code_define(item: &Item, constants: &ConstContext<'_>) -> Option<String> {
78    let Item::Command(packet) = item else {
79        return None;
80    };
81    let cc = find_cc_attr(&packet.attrs)?;
82    let define_name = to_screaming_snake(&packet.name);
83    let cc_str = literal_cc_str(cc, constants);
84    Some(format!("#define {}_CC   {}\n", define_name, cc_str))
85}
86
87fn emit_enum_aliases(file: &SynFile, out: &mut String) {
88    let mut namespace = Vec::new();
89    for item in &file.items {
90        match item {
91            Item::Namespace(ns) => namespace = ns.name.clone(),
92            Item::Enum(e) => emit_enum(out, e, &namespace),
93            Item::Import(_)
94            | Item::Const(_)
95            | Item::Struct(_)
96            | Item::Table(_)
97            | Item::Command(_)
98            | Item::Telemetry(_)
99            | Item::Message(_) => {}
100        }
101    }
102}
103
104fn emit_c_types(file: &SynFile, out: &mut String) {
105    let mut namespace = Vec::new();
106    for item in &file.items {
107        if update_namespace(&mut namespace, item) {
108            continue;
109        }
110        emit_c_type(out, item, &namespace);
111    }
112}
113
114fn update_namespace(namespace: &mut Vec<String>, item: &Item) -> bool {
115    if let Item::Namespace(ns) = item {
116        *namespace = ns.name.clone();
117        true
118    } else {
119        false
120    }
121}
122
123fn emit_c_type(out: &mut String, item: &Item, namespace: &[String]) {
124    match item {
125        Item::Const(c) => emit_const(out, c),
126        Item::Struct(s) | Item::Table(s) => emit_struct(out, s, namespace),
127        Item::Command(m) | Item::Telemetry(m) | Item::Message(m) => emit_message(out, m, namespace),
128        Item::Namespace(_) | Item::Import(_) | Item::Enum(_) => {}
129    }
130}
131
132fn emit_const(out: &mut String, c: &ConstDecl) {
133    emit_doc_lines(out, &c.doc);
134    let val = typed_literal_str(&c.value, &c.ty);
135    out.push_str(&format!("#define {}  {}\n\n", c.name, val));
136}
137
138fn emit_enum(out: &mut String, e: &EnumDef, namespace: &[String]) {
139    let Some(repr) = e.repr else {
140        return;
141    };
142
143    let type_name = c_decl_type_name(&e.name, namespace);
144    emit_doc_lines(out, &e.doc);
145    out.push_str(&format!("typedef {} {};\n", primitive_str(repr), type_name));
146
147    let enum_prefix = c_enum_variant_prefix(&e.name, namespace);
148    for variant in &e.variants {
149        emit_doc_lines(out, &variant.doc);
150        let value = variant
151            .value
152            .expect("represented enum variants validated before emission");
153        out.push_str(&format!(
154            "#define {}_{}  (({}){})\n",
155            enum_prefix,
156            to_screaming_snake(&variant.name),
157            type_name,
158            value
159        ));
160    }
161    out.push('\n');
162}
163
164fn emit_struct(out: &mut String, s: &StructDef, namespace: &[String]) {
165    emit_doc_lines(out, &s.doc);
166    out.push_str("typedef struct {\n");
167    for f in &s.fields {
168        emit_c_field(out, f, namespace);
169    }
170    out.push_str(&format!("}} {};\n\n", c_decl_type_name(&s.name, namespace)));
171}
172
173fn emit_message(out: &mut String, m: &MessageDef, namespace: &[String]) {
174    let header_type = if packet_is_command(m) {
175        "CFE_MSG_CommandHeader_t"
176    } else {
177        "CFE_MSG_TelemetryHeader_t"
178    };
179
180    emit_doc_lines(out, &m.doc);
181
182    out.push_str("typedef struct {\n");
183    out.push_str(&format!("    {} Header;\n", header_type));
184    for f in &m.fields {
185        emit_c_field(out, f, namespace);
186    }
187    out.push_str(&format!("}} {};\n\n", c_decl_type_name(&m.name, namespace)));
188}
189
190fn non_fixed_type_str(ty: &TypeExpr, namespace: &[String]) -> String {
191    if ty.base == BaseType::String {
192        return non_fixed_string_type_str(&ty.array);
193    }
194
195    let base = base_type_str(&ty.base, namespace);
196    non_fixed_array_type_str(base, &ty.array)
197}
198
199fn non_fixed_string_type_str(array: &Option<ArraySuffix>) -> String {
200    match array {
201        None | Some(ArraySuffix::Dynamic) => "const char*".to_string(),
202        Some(ArraySuffix::Fixed(_)) => unreachable!("handled by emit_c_field"),
203        Some(ArraySuffix::Bounded(n)) => format!("char[{}]", n),
204    }
205}
206
207fn non_fixed_array_type_str(base: String, array: &Option<ArraySuffix>) -> String {
208    match array {
209        None => base,
210        Some(ArraySuffix::Fixed(_)) => unreachable!("handled by caller"),
211        Some(ArraySuffix::Dynamic) => format!("CFE_Span_t /* {} */", base),
212        Some(ArraySuffix::Bounded(n)) => format!("CFE_Span_t /* {} max {} */", base, n),
213    }
214}
215
216fn base_type_str(base: &BaseType, namespace: &[String]) -> String {
217    match base {
218        BaseType::String => "const char*".to_string(),
219        BaseType::Primitive(p) => primitive_str(*p).to_string(),
220        BaseType::Ref(segments) => c_ref_type_name(segments, namespace),
221    }
222}
223
224fn emit_c_field(out: &mut String, f: &FieldDef, namespace: &[String]) {
225    emit_indented_doc_lines(out, &f.doc);
226    match (&f.ty.base, &f.ty.array) {
227        (BaseType::String, Some(ArraySuffix::Fixed(n) | ArraySuffix::Bounded(n))) => {
228            out.push_str(&format!("    char {}[{}];\n", f.name, n));
229        }
230        (_, Some(ArraySuffix::Fixed(n))) => {
231            out.push_str(&format!(
232                "    {} {}[{}];\n",
233                base_type_str(&f.ty.base, namespace),
234                f.name,
235                n
236            ));
237        }
238        _ => {
239            out.push_str(&format!(
240                "    {} {};\n",
241                non_fixed_type_str(&f.ty, namespace),
242                f.name
243            ));
244        }
245    }
246}
247
248fn c_decl_type_name(name: &str, namespace: &[String]) -> String {
249    let mut segments = namespace.to_vec();
250    segments.push(name.to_string());
251    format!("{}_t", segments.join("_"))
252}
253
254pub(crate) fn c_enum_variant_prefix(name: &str, namespace: &[String]) -> String {
255    let mut segments = namespace.to_vec();
256    segments.push(name.to_string());
257    segments
258        .iter()
259        .map(|segment| to_screaming_snake(segment))
260        .collect::<Vec<_>>()
261        .join("_")
262}
263
264fn c_ref_type_name(segments: &[String], namespace: &[String]) -> String {
265    let resolved = if segments.len() == 1 && !namespace.is_empty() {
266        let mut resolved = namespace.to_vec();
267        resolved.push(segments[0].clone());
268        resolved
269    } else {
270        segments.to_vec()
271    };
272    if resolved.is_empty() {
273        return "_t".to_string();
274    }
275    format!("{}_t", resolved.join("_"))
276}
277
278fn primitive_str(p: PrimitiveType) -> &'static str {
279    const C_TYPES: &[(PrimitiveType, &str)] = &[
280        (PrimitiveType::F32, "float"),
281        (PrimitiveType::F64, "double"),
282        (PrimitiveType::I8, "int8_t"),
283        (PrimitiveType::I16, "int16_t"),
284        (PrimitiveType::I32, "int32_t"),
285        (PrimitiveType::I64, "int64_t"),
286        (PrimitiveType::U8, "uint8_t"),
287        (PrimitiveType::U16, "uint16_t"),
288        (PrimitiveType::U32, "uint32_t"),
289        (PrimitiveType::U64, "uint64_t"),
290        (PrimitiveType::Bool, "bool"),
291        (PrimitiveType::Bytes, "uint8_t*"),
292    ];
293
294    C_TYPES
295        .iter()
296        .find_map(|(ty, name)| (*ty == p).then_some(*name))
297        .expect("all primitive types have C names")
298}