1use super::cpp::{cxx_name, module_names};
37use super::cpp_psm::{in_namespaces, native_path, psm_path};
38use crate::adapter::c::ordered_types;
39use crate::adapter::cst::MaterialiseError;
40use crate::default_common::resolve_alias;
41use zerodds_ast::{Ast, CaseLabel, CharWidth, MemberNode, PrimitiveKind, TypeKind, TypeNode};
42use zerodds_ast_core::Id;
43
44pub const BEGIN_MARKER: &str = "ZD-CLASSIC-BEGIN";
46pub const END_MARKER: &str = "ZD-CLASSIC-END";
48
49fn no_form(construct: &str, at: &str) -> MaterialiseError {
50 MaterialiseError::NoLanguageForm {
51 construct: construct.to_string(),
52 language: "C++ (classic mapping)",
53 at: at.to_string(),
54 }
55}
56
57const MAX_DEPTH: usize = 64;
58
59struct Spelled {
62 type_: String,
63 dims: String,
64}
65
66impl Spelled {
67 fn plain(type_: String) -> Self {
68 Self {
69 type_,
70 dims: String::new(),
71 }
72 }
73
74 fn declare(&self, name: &str) -> String {
76 format!("{} {name}{}", self.type_, self.dims)
77 }
78}
79
80const fn primitive(kind: PrimitiveKind) -> &'static str {
81 match kind {
82 PrimitiveKind::Boolean => "::CORBA::Boolean",
83 PrimitiveKind::Char => "::CORBA::Char",
84 PrimitiveKind::WChar => "::CORBA::WChar",
85 PrimitiveKind::Octet => "::CORBA::Octet",
86 PrimitiveKind::UInt8 => "::CORBA::UInt8",
87 PrimitiveKind::Int8 => "::CORBA::Int8",
88 PrimitiveKind::Int16 => "::CORBA::Short",
89 PrimitiveKind::UInt16 => "::CORBA::UShort",
90 PrimitiveKind::Int32 => "::CORBA::Long",
91 PrimitiveKind::UInt32 => "::CORBA::ULong",
92 PrimitiveKind::Int64 => "::CORBA::LongLong",
93 PrimitiveKind::UInt64 => "::CORBA::ULongLong",
94 PrimitiveKind::Float32 => "::CORBA::Float",
95 PrimitiveKind::Float64 => "::CORBA::Double",
96 PrimitiveKind::Float128 => "::CORBA::LongDouble",
97 }
98}
99
100fn spell(ast: &Ast, id: Id<TypeNode>, at: &str, key: bool) -> Result<Spelled, MaterialiseError> {
105 spell_at(ast, id, at, key, 0)
106}
107
108fn spell_at(
110 ast: &Ast,
111 id: Id<TypeNode>,
112 at: &str,
113 key: bool,
114 depth: usize,
115) -> Result<Spelled, MaterialiseError> {
116 if depth > MAX_DEPTH {
117 return Err(MaterialiseError::Unevaluated {
118 construct: format!("type spelling — it nests past {MAX_DEPTH}"),
119 at: at.to_string(),
120 });
121 }
122 let Ok(node) = ast.type_(id) else {
123 return Err(no_form("a type the model would not hand over", at));
124 };
125 let inner =
126 |r: &zerodds_ast_core::Ref<TypeNode>, key: bool| -> Result<String, MaterialiseError> {
127 let Some(target) = r.resolved() else {
128 return Err(no_form("an unresolved element type", at));
129 };
130 let spelled = spell_at(ast, target, at, key, depth + 1)?;
131 if spelled.dims.is_empty() {
132 Ok(spelled.type_)
133 } else {
134 Err(no_form(
135 "an array as the element of a sequence or a map, which has no slice type here",
136 at,
137 ))
138 }
139 };
140 Ok(match &node.kind {
141 TypeKind::Primitive(kind) => Spelled::plain(primitive(*kind).to_string()),
142 TypeKind::String { .. } if key => Spelled::plain("std::string".to_string()),
145 TypeKind::String {
146 width: CharWidth::WChar,
147 ..
148 } => Spelled::plain("::CORBA::WString_var".to_string()),
149 TypeKind::String { .. } => Spelled::plain("::CORBA::String_var".to_string()),
150 TypeKind::Fixed { .. } => {
151 return Err(no_form(
152 "a fixed-point type; the classic mapping's CORBA::Fixed is not provided",
153 at,
154 ));
155 }
156 TypeKind::Sequence { element, .. } => Spelled::plain(format!(
157 "::zerodds::classic::Sequence< {} >",
158 inner(element, false)?
159 )),
160 TypeKind::Array { element, dims } => {
161 let element = inner(element, false)?;
162 let mut suffix = String::new();
163 for dim in dims {
164 let length =
165 ast.eval_integral(*dim)
166 .map_err(|_| MaterialiseError::Unevaluated {
167 construct: "array length".to_string(),
168 at: at.to_string(),
169 })?;
170 suffix.push_str(&format!("[{length}]"));
171 }
172 Spelled {
173 type_: element,
174 dims: suffix,
175 }
176 }
177 TypeKind::Map {
178 key: k,
179 value,
180 bound,
181 } => {
182 let bound = match bound.expr() {
183 Some(expr) => format!(
184 ", {}",
185 ast.eval_integral(expr)
186 .map_err(|_| MaterialiseError::Unevaluated {
187 construct: "map bound".to_string(),
188 at: at.to_string(),
189 })?
190 ),
191 None => String::new(),
192 };
193 Spelled::plain(format!(
194 "::zerodds::Map< {}, {}{bound} >",
195 inner(k, true)?,
196 inner(value, false)?
197 ))
198 }
199 TypeKind::Alias { aliased } if node.name.is_empty() => {
200 let Some(target) = aliased.resolved() else {
201 return Err(no_form("an unresolved typedef", at));
202 };
203 spell_at(ast, target, at, key, depth + 1)?
204 }
205 TypeKind::Structure { .. }
206 | TypeKind::Union { .. }
207 | TypeKind::Enumeration { .. }
208 | TypeKind::Bitmask { .. }
209 | TypeKind::Alias { .. } => Spelled::plain(psm_path(ast, id)),
210 _ => return Err(no_form("this construct", at)),
211 })
212}
213
214struct Member {
216 name: String,
217 spelled: Spelled,
218 init: String,
220}
221
222fn enumerator_path(ast: &Ast, enumeration: Id<TypeNode>, enumerator: &str) -> Option<String> {
223 let node = ast.type_(enumeration).ok()?;
224 let chain = module_names(ast, node.module).ok()?;
225 let mut path = String::new();
226 for module in chain {
227 path.push_str(&format!("::{module}"));
228 }
229 Some(format!("{path}::{}", cxx_name(enumerator)))
230}
231
232fn member(ast: &Ast, member: &MemberNode, owner: &str) -> Result<Member, MaterialiseError> {
233 let Some(target) = member.type_.resolved() else {
234 return Err(MaterialiseError::Unevaluated {
235 construct: "member type".to_string(),
236 at: format!("{owner}::{}", member.name),
237 });
238 };
239 let name = cxx_name(&member.name);
240 let inner = spell(ast, target, &member.name, false)?;
241 let held = super::cpp::held(ast, member);
244 let (spelled, init) = if held == super::cpp::Held::Shared {
245 (
246 Spelled::plain(format!("::zerodds::classic::Var< {} >", inner.type_)),
247 format!("{name}()"),
248 )
249 } else if held == super::cpp::Held::Plain {
250 (
251 Spelled::plain(format!("::zerodds::classic::Optional< {} >", inner.type_)),
252 format!("{name}()"),
253 )
254 } else {
255 let resolved = resolve_alias(ast, target);
257 let first = matches!(
258 ast.type_(resolved).map(|node| &node.kind),
259 Ok(TypeKind::Enumeration { .. })
260 )
261 .then(|| crate::emit::default_enumerator_name(ast, resolved))
262 .flatten()
263 .and_then(|first| enumerator_path(ast, resolved, &first));
264 let init = match (first, inner.dims.is_empty()) {
265 (Some(first), true) => format!("{name}({first})"),
266 _ => format!("{name}()"),
267 };
268 (inner, init)
269 };
270 Ok(Member {
271 name,
272 spelled,
273 init,
274 })
275}
276
277fn structure(
279 ast: &Ast,
280 id: Id<TypeNode>,
281 node: &TypeNode,
282) -> Result<(String, String), MaterialiseError> {
283 let name = cxx_name(&node.name);
284 let base = ast
285 .base_of(id)
286 .filter(|_| matches!(node.kind, TypeKind::Structure { .. }));
287 let mut own = Vec::new();
288 for handle in ast.members_of(id) {
289 let Ok(m) = ast.member(*handle) else {
290 return Err(MaterialiseError::NoNode {
291 kind: "member",
292 at: node.name.clone(),
293 });
294 };
295 own.push(member(ast, m, &node.name)?);
296 }
297 let mut text = format!(
298 "/// `{}` as the classic mapping has it: public members.\nstruct {name}{} {{\n",
299 node.name,
300 base.map_or(String::new(), |b| format!(" : public {}", psm_path(ast, b))),
301 );
302 for m in &own {
303 text.push_str(&format!(" {};\n", m.spelled.declare(&m.name)));
304 }
305 if own.is_empty() {
306 text.push_str(&format!(" {name}() {{}}\n"));
307 } else {
308 let inits: Vec<&str> = own.iter().map(|m| m.init.as_str()).collect();
309 text.push_str(&format!(" {name}() : {} {{}}\n", inits.join(", ")));
310 }
311 text.push_str(&format!(
312 "}};\ntypedef ::zerodds::classic::Var< {name} > {name}_var;\n"
313 ));
314
315 let path = psm_path(ast, id);
317 let native = native_path(ast, id);
318 let mut all = Vec::new();
319 for handle in ast.all_members_of(id) {
320 let Ok(m) = ast.member(handle) else {
321 return Err(MaterialiseError::NoNode {
322 kind: "member",
323 at: node.name.clone(),
324 });
325 };
326 all.push(member(ast, m, &node.name)?);
327 }
328 let mut conv = format!(
329 "template <>\nstruct Conv<{path}> {{\n typedef {native} Native;\n static Native to_native(const {path}& v) {{\n Native n;\n"
330 );
331 for m in &all {
332 conv.push_str(&format!(
333 " to_native_member(n.{0}, v.{0});\n",
334 m.name
335 ));
336 }
337 conv.push_str(&format!(
338 " return n;\n }}\n static {path} from_native(const Native& n) {{\n {path} v;\n"
339 ));
340 for m in &all {
341 conv.push_str(&format!(
342 " from_native_member(v.{0}, n.{0});\n",
343 m.name
344 ));
345 }
346 conv.push_str(" return v;\n }\n};\n");
347 Ok((text, conv))
348}
349
350fn union_class(
352 ast: &Ast,
353 id: Id<TypeNode>,
354 node: &TypeNode,
355 discriminator: &zerodds_ast_core::Ref<TypeNode>,
356) -> Result<(String, String), MaterialiseError> {
357 let name = cxx_name(&node.name);
358 let Some(disc) = discriminator.resolved() else {
359 return Err(MaterialiseError::Unevaluated {
360 construct: "union discriminator".to_string(),
361 at: node.name.clone(),
362 });
363 };
364 let disc_type = spell(ast, disc, &node.name, false)?.type_;
365 struct Case {
366 name: String,
367 type_: String,
368 label: Option<i128>,
369 }
370 let mut cases: Vec<Case> = Vec::new();
371 for case in ast.cases_of(id) {
372 let Ok(case_node) = ast.case(*case) else {
373 return Err(MaterialiseError::NoNode {
374 kind: "case",
375 at: node.name.clone(),
376 });
377 };
378 let Ok(m) = ast.member(case_node.member) else {
379 return Err(MaterialiseError::NoNode {
380 kind: "member",
381 at: node.name.clone(),
382 });
383 };
384 let mut label = None;
385 for candidate in &case_node.labels {
386 if let CaseLabel::Value(expr) = candidate {
387 label =
388 Some(
389 ast.eval_integral(*expr)
390 .map_err(|_| MaterialiseError::Unevaluated {
391 construct: "union case label".to_string(),
392 at: node.name.clone(),
393 })?,
394 );
395 break;
396 }
397 }
398 let built = member(ast, m, &node.name)?;
399 if !built.spelled.dims.is_empty() {
400 return Err(no_form("an array as the case of a union", &m.name));
401 }
402 cases.push(Case {
403 name: built.name,
404 type_: built.spelled.type_,
405 label,
406 });
407 }
408 let mut inits = vec!["d_()".to_string()];
409 inits.extend(cases.iter().map(|c| format!("{}_()", c.name)));
410 let mut text = format!(
411 "/// `{}` as the classic mapping has it: `_d()` and an accessor and a modifier per case.\nclass {name} {{\npublic:\n {name}() : {} {{}}\n\n {disc_type} _d() const {{ return d_; }}\n void _d({disc_type} value) {{ d_ = value; }}\n\n",
412 node.name,
413 inits.join(", ")
414 );
415 for case in &cases {
416 let select = case.label.map_or(String::new(), |value| {
417 format!(" d_ = static_cast< {disc_type} >({value});")
418 });
419 text.push_str(&format!(
420 " const {0}& {1}() const {{ return {1}_; }}\n {0}& {1}() {{ return {1}_; }}\n void {1}(const {0}& value) {{ {1}_ = value;{select} }}\n",
421 case.type_, case.name
422 ));
423 }
424 text.push_str(&format!("\nprivate:\n {disc_type} d_;\n"));
425 for case in &cases {
426 text.push_str(&format!(" {} {}_;\n", case.type_, case.name));
427 }
428 text.push_str(&format!(
429 "}};\ntypedef ::zerodds::classic::Var< {name} > {name}_var;\n"
430 ));
431
432 let path = psm_path(ast, id);
433 let native = native_path(ast, id);
434 let mut conv = format!(
435 "template <>\nstruct Conv<{path}> {{\n typedef {native} Native;\n static Native to_native(const {path}& v) {{\n Native n;\n"
436 );
437 for case in &cases {
439 conv.push_str(&format!(
440 " {{\n typedef std::remove_cv<std::remove_reference<decltype(n.{0}())>::type>::type Member;\n Member member{{}};\n to_native_member(member, v.{0}());\n n.{0}(member);\n }}\n",
441 case.name
442 ));
443 }
444 conv.push_str(&format!(
445 " n._d(Conv<{disc_type}>::to_native(v._d()));\n return n;\n }}\n static {path} from_native(const Native& n) {{\n {path} v;\n"
446 ));
447 for case in &cases {
448 conv.push_str(&format!(
449 " {{\n {1} member;\n from_native_member(member, n.{0}());\n v.{0}(member);\n }}\n",
450 case.name, case.type_
451 ));
452 }
453 conv.push_str(&format!(
454 " v._d(Conv<{disc_type}>::from_native(n._d()));\n return v;\n }}\n}};\n"
455 ));
456 Ok((text, conv))
457}
458
459pub fn classic_layer(ast: &Ast) -> Result<String, MaterialiseError> {
465 let order = ordered_types(ast);
466 let pair = crate::emit::type_pair_cpp::has_pair_types(ast);
467 let runtime_defined = |node: &TypeNode| -> Result<bool, MaterialiseError> {
468 Ok(pair
469 && crate::emit::type_pair_cpp::is_runtime_defined(
470 &module_names(ast, node.module)?,
471 &node.name,
472 ))
473 };
474 let mut forward = String::new();
475 let mut types = String::new();
476 let mut conversions = String::new();
477
478 for id in &order {
479 let Ok(node) = ast.type_(*id) else {
480 return Err(no_form(
481 "a type the model would not hand over",
482 "forward declarations",
483 ));
484 };
485 if node.name.is_empty() || runtime_defined(node)? {
486 continue;
487 }
488 if matches!(
489 node.kind,
490 TypeKind::Structure { .. } | TypeKind::Union { .. }
491 ) {
492 let chain = module_names(ast, node.module)?;
493 let keyword = if matches!(node.kind, TypeKind::Union { .. }) {
494 "class"
495 } else {
496 "struct"
497 };
498 forward.push_str(&in_namespaces(
499 &chain,
500 &format!("{keyword} {};", cxx_name(&node.name)),
501 ));
502 }
503 }
504 for id in order {
505 let Ok(node) = ast.type_(id) else {
506 return Err(no_form(
507 "a type the model would not hand over",
508 "type declarations",
509 ));
510 };
511 if node.name.is_empty() || runtime_defined(node)? {
512 continue;
513 }
514 let chain = module_names(ast, node.module)?;
515 let name = cxx_name(&node.name);
516 let (text, conv) = match &node.kind {
517 TypeKind::Structure { .. } => {
518 let (class, conv) = structure(ast, id, node)?;
519 (class, Some(conv))
520 }
521 TypeKind::Union { discriminator, .. } => {
522 let (class, conv) = union_class(ast, id, node, discriminator)?;
523 (class, Some(conv))
524 }
525 TypeKind::Enumeration { .. } => {
526 let mut values = Vec::new();
527 for enumerator in ast.enumerators_of(id) {
528 let Ok(e) = ast.enumerator(*enumerator) else {
529 return Err(MaterialiseError::NoNode {
530 kind: "enumerator",
531 at: node.name.clone(),
532 });
533 };
534 let value = ast.effective_enumerator_value(*enumerator).ok_or_else(|| {
535 MaterialiseError::Unevaluated {
536 construct: "enumerator value".to_string(),
537 at: e.name.clone(),
538 }
539 })?;
540 if i32::try_from(value).is_err() {
541 return Err(no_form(
542 "an enumerator beyond 32 signed bits, which a C++03 enumeration does not hold",
543 &e.name,
544 ));
545 }
546 values.push(format!(" {} = {value}", cxx_name(&e.name)));
547 }
548 let native = native_path(ast, id);
549 let path = psm_path(ast, id);
550 let conv = format!(
551 "template <>\nstruct Conv<{path}> {{\n typedef {native} Native;\n typedef std::underlying_type<Native>::type Underlying;\n static Native to_native({path} v) {{ return static_cast<Native>(static_cast<Underlying>(v)); }}\n static {path} from_native(Native n) {{ return static_cast<{path}>(static_cast<Underlying>(n)); }}\n}};\n"
552 );
553 (
554 format!(
555 "/// An enumeration, with the numbering of the IDL.\nenum {name} {{\n{}\n}};\n",
556 values.join(",\n")
557 ),
558 Some(conv),
559 )
560 }
561 TypeKind::Bitmask { bits } => {
562 let holder = match ast.effective_bit_bound(id) {
563 0..=8 => "::CORBA::Octet",
564 9..=16 => "::CORBA::UShort",
565 17..=32 => "::CORBA::ULong",
566 _ => "::CORBA::ULongLong",
567 };
568 let mut text = format!(
569 "/// A bitmask, as the unsigned integer of its bound.\ntypedef {holder} {name};\n"
570 );
571 for bit in bits {
572 let Ok(b) = ast.bit_value(*bit) else {
573 return Err(MaterialiseError::NoNode {
574 kind: "bit",
575 at: node.name.clone(),
576 });
577 };
578 let position = ast.effective_bit_position(*bit).ok_or_else(|| {
579 MaterialiseError::Unevaluated {
580 construct: "bit position".to_string(),
581 at: b.name.clone(),
582 }
583 })?;
584 text.push_str(&format!(
585 "const {name} {} ZD_CLASSIC_CONSTANT = static_cast< {name} >({}ULL << {position});\n",
586 cxx_name(&b.name),
587 1
588 ));
589 }
590 (text, None)
591 }
592 TypeKind::Alias { aliased } => {
593 let Some(target) = aliased.resolved() else {
594 return Err(no_form("an unresolved typedef", &node.name));
595 };
596 let spelled = spell(ast, target, &node.name, false)?;
597 (format!("typedef {};\n", spelled.declare(&name)), None)
598 }
599 TypeKind::Bitset { .. } => {
600 return Err(no_form("a bit set has no classic mapping", &node.name));
601 }
602 TypeKind::Native
603 | TypeKind::ValueBox { .. }
604 | TypeKind::Valuetype { .. }
605 | TypeKind::ValueBase
606 | TypeKind::ObjectRef { .. } => {
607 return Err(no_form(
608 "a native type, value box, value type or interface has no classic mapping here",
609 &node.name,
610 ));
611 }
612 TypeKind::Primitive(_)
615 | TypeKind::Fixed { .. }
616 | TypeKind::String { .. }
617 | TypeKind::Sequence { .. }
618 | TypeKind::Array { .. }
619 | TypeKind::Map { .. }
620 | TypeKind::Any
621 | TypeKind::AnnotationType { .. } => continue,
622 };
623 let mut text = text;
626 if matches!(
627 node.kind,
628 TypeKind::Structure { .. }
629 | TypeKind::Union { .. }
630 | TypeKind::Enumeration { .. }
631 | TypeKind::Bitmask { .. }
632 | TypeKind::Alias { .. }
633 ) {
634 for (label, key) in [
635 ("UInt32", "::CORBA::ULong"),
636 ("UInt64", "::CORBA::ULongLong"),
637 ("String", "std::string"),
638 ] {
639 text.push_str(&format!(
640 "typedef ::zerodds::Map< {key}, {name} > {label}{name}Map;\n"
641 ));
642 }
643 }
644 types.push_str(&in_namespaces(&chain, &text));
645 types.push('\n');
646 if let Some(conv) = conv {
647 conversions.push_str(&conv);
648 conversions.push('\n');
649 }
650 }
651 Ok(format!(
652 "// ---- The classic layer: IDL to C++ 1.3 over the native types above. ----\n\
653 // The native types are in `zd_native`; these are what an application writes.\n\
654 // `zerodds::classic::Conv` hands them to the codec.\n\n\
655 // {BEGIN_MARKER} (C++03 from here)\n\
656 #include <string>\n\
657 #include \"zerodds/classic.hpp\"\n\
658 #include \"zerodds/map.hpp\"\n\
659 // A constant at namespace scope has internal linkage; a translation unit that does not use\n\
660 // it is not warned about it.\n\
661 #if defined(__GNUC__)\n\
662 #define ZD_CLASSIC_CONSTANT __attribute__((unused))\n\
663 #else\n\
664 #define ZD_CLASSIC_CONSTANT\n\
665 #endif\n\
666 {forward}\n{types}\
667 // {END_MARKER}\n\n\
668 #include \"zerodds/classic/convert.hpp\"\n\
669 namespace zerodds {{\nnamespace classic {{\n\n{conversions}}} // namespace classic\n}} // namespace zerodds\n"
670 ))
671}