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
17pub fn generate_c(file: &SynFile) -> String {
19 try_generate_c(file).expect("parsed Synapse file is not supported by cFS C codegen")
20}
21
22pub fn try_generate_c(file: &SynFile) -> Result<String, CodegenError> {
24 try_generate_c_with_constants(file, &ResolvedConstants::new())
25}
26
27pub 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}