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