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