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 let mut has_mids = false;
57 for item in &file.items {
58 if let Some(m) = packet_item(item) {
59 if let Some(mid) = find_mid_attr(&m.attrs) {
60 if !has_mids {
61 out.push_str("/* Message IDs */\n");
62 has_mids = true;
63 }
64 let define_name = to_screaming_snake(&m.name);
65 let mid_str = literal_mid_str(mid, constants);
66 out.push_str(&format!("#define {}_MID {}\n", define_name, mid_str));
67 }
68 }
69 }
70 if has_mids {
71 out.push('\n');
72 }
73
74 let mut has_ccs = false;
75 for item in &file.items {
76 if let Item::Command(m) = item {
77 if let Some(cc) = find_cc_attr(&m.attrs) {
78 if !has_ccs {
79 out.push_str("/* Command Codes */\n");
80 has_ccs = true;
81 }
82 let define_name = to_screaming_snake(&m.name);
83 let cc_str = literal_cc_str(cc, constants);
84 out.push_str(&format!("#define {}_CC {}\n", define_name, cc_str));
85 }
86 }
87 }
88 if has_ccs {
89 out.push('\n');
90 }
91
92 let mut namespace = Vec::new();
94 for item in &file.items {
95 match item {
96 Item::Namespace(ns) => namespace = ns.name.clone(),
97 Item::Enum(e) => emit_enum(out, e, &namespace),
98 Item::Import(_)
99 | Item::Const(_)
100 | Item::Struct(_)
101 | Item::Table(_)
102 | Item::Command(_)
103 | Item::Telemetry(_)
104 | Item::Message(_) => {}
105 }
106 }
107
108 let mut namespace = Vec::new();
110 for item in &file.items {
111 match item {
112 Item::Namespace(ns) => namespace = ns.name.clone(),
113 Item::Import(_) | Item::Enum(_) => {}
114 Item::Const(c) => emit_const(out, c),
115 Item::Struct(s) | Item::Table(s) => emit_struct(out, s, &namespace),
116 Item::Command(m) | Item::Telemetry(m) | Item::Message(m) => {
117 emit_message(out, m, &namespace)
118 }
119 }
120 }
121}
122
123fn emit_const(out: &mut String, c: &ConstDecl) {
124 emit_doc_lines(out, &c.doc);
125 let val = typed_literal_str(&c.value, &c.ty);
126 out.push_str(&format!("#define {} {}\n\n", c.name, val));
127}
128
129fn emit_enum(out: &mut String, e: &EnumDef, namespace: &[String]) {
130 let Some(repr) = e.repr else {
131 return;
132 };
133
134 let type_name = c_decl_type_name(&e.name, namespace);
135 emit_doc_lines(out, &e.doc);
136 out.push_str(&format!("typedef {} {};\n", primitive_str(repr), type_name));
137
138 let enum_prefix = c_enum_variant_prefix(&e.name, namespace);
139 for variant in &e.variants {
140 emit_doc_lines(out, &variant.doc);
141 let value = variant
142 .value
143 .expect("represented enum variants validated before emission");
144 out.push_str(&format!(
145 "#define {}_{} (({}){})\n",
146 enum_prefix,
147 to_screaming_snake(&variant.name),
148 type_name,
149 value
150 ));
151 }
152 out.push('\n');
153}
154
155fn emit_struct(out: &mut String, s: &StructDef, namespace: &[String]) {
156 emit_doc_lines(out, &s.doc);
157 out.push_str("typedef struct {\n");
158 for f in &s.fields {
159 emit_c_field(out, f, namespace);
160 }
161 out.push_str(&format!("}} {};\n\n", c_decl_type_name(&s.name, namespace)));
162}
163
164fn emit_message(out: &mut String, m: &MessageDef, namespace: &[String]) {
165 let header_type = if packet_is_command(m) {
166 "CFE_MSG_CommandHeader_t"
167 } else {
168 "CFE_MSG_TelemetryHeader_t"
169 };
170
171 emit_doc_lines(out, &m.doc);
172
173 out.push_str("typedef struct {\n");
174 out.push_str(&format!(" {} Header;\n", header_type));
175 for f in &m.fields {
176 emit_c_field(out, f, namespace);
177 }
178 out.push_str(&format!("}} {};\n\n", c_decl_type_name(&m.name, namespace)));
179}
180
181fn non_fixed_type_str(ty: &TypeExpr, namespace: &[String]) -> String {
182 if ty.base == BaseType::String {
183 return match &ty.array {
184 None | Some(ArraySuffix::Dynamic) => "const char*".to_string(),
185 Some(ArraySuffix::Fixed(_)) => unreachable!("handled by emit_c_field"),
186 Some(ArraySuffix::Bounded(n)) => format!("char[{}]", n),
187 };
188 }
189
190 let base = base_type_str(&ty.base, namespace);
191 match &ty.array {
192 None => base,
193 Some(ArraySuffix::Fixed(_)) => unreachable!("handled by caller"),
194 Some(ArraySuffix::Dynamic) => format!("CFE_Span_t /* {} */", base),
195 Some(ArraySuffix::Bounded(n)) => format!("CFE_Span_t /* {} max {} */", base, n),
196 }
197}
198
199fn base_type_str(base: &BaseType, namespace: &[String]) -> String {
200 match base {
201 BaseType::String => "const char*".to_string(),
202 BaseType::Primitive(p) => primitive_str(*p).to_string(),
203 BaseType::Ref(segments) => c_ref_type_name(segments, namespace),
204 }
205}
206
207fn emit_c_field(out: &mut String, f: &FieldDef, namespace: &[String]) {
208 emit_indented_doc_lines(out, &f.doc);
209 match (&f.ty.base, &f.ty.array) {
210 (BaseType::String, Some(ArraySuffix::Fixed(n) | ArraySuffix::Bounded(n))) => {
211 out.push_str(&format!(" char {}[{}];\n", f.name, n));
212 }
213 (_, Some(ArraySuffix::Fixed(n))) => {
214 out.push_str(&format!(
215 " {} {}[{}];\n",
216 base_type_str(&f.ty.base, namespace),
217 f.name,
218 n
219 ));
220 }
221 _ => {
222 out.push_str(&format!(
223 " {} {};\n",
224 non_fixed_type_str(&f.ty, namespace),
225 f.name
226 ));
227 }
228 }
229}
230
231fn c_decl_type_name(name: &str, namespace: &[String]) -> String {
232 let mut segments = namespace.to_vec();
233 segments.push(name.to_string());
234 format!("{}_t", segments.join("_"))
235}
236
237pub(crate) fn c_enum_variant_prefix(name: &str, namespace: &[String]) -> String {
238 let mut segments = namespace.to_vec();
239 segments.push(name.to_string());
240 segments
241 .iter()
242 .map(|segment| to_screaming_snake(segment))
243 .collect::<Vec<_>>()
244 .join("_")
245}
246
247fn c_ref_type_name(segments: &[String], namespace: &[String]) -> String {
248 let resolved = if segments.len() == 1 && !namespace.is_empty() {
249 let mut resolved = namespace.to_vec();
250 resolved.push(segments[0].clone());
251 resolved
252 } else {
253 segments.to_vec()
254 };
255 if resolved.is_empty() {
256 return "_t".to_string();
257 }
258 format!("{}_t", resolved.join("_"))
259}
260
261fn primitive_str(p: PrimitiveType) -> &'static str {
262 match p {
263 PrimitiveType::F32 => "float",
264 PrimitiveType::F64 => "double",
265 PrimitiveType::I8 => "int8_t",
266 PrimitiveType::I16 => "int16_t",
267 PrimitiveType::I32 => "int32_t",
268 PrimitiveType::I64 => "int64_t",
269 PrimitiveType::U8 => "uint8_t",
270 PrimitiveType::U16 => "uint16_t",
271 PrimitiveType::U32 => "uint32_t",
272 PrimitiveType::U64 => "uint64_t",
273 PrimitiveType::Bool => "bool",
274 PrimitiveType::Bytes => "uint8_t*",
275 }
276}