use crate::adapter::constant;
use crate::adapter::cst::{MaterialiseError, no_spelling_collision};
use crate::apply::Applied;
use crate::verbatim;
use seesaw_tgg::ident::GhostId;
use zerodds_ast::{Ast, CharWidth, PrimitiveKind, TypeKind, TypeNode};
use zerodds_ast_core::{Id, NodeId};
use zerodds_lang_cst::c;
use zerodds_lang_cst::common::{Doc, Ident, Literal};
#[must_use]
pub fn prelude() -> Vec<c::Decl> {
let mut out = vec![
c::Decl::Typedef(c::Typedef {
doc: Doc::lines(&[
"A `string`. The bytes are the caller's and are not owned here:",
"nothing in the write path allocates.",
]),
type_: c::Type::Pointer(Box::new(c::Type::Const(Box::new(c::Type::Primitive(
c::Primitive::Char,
))))),
name: Ident::new(STRING),
dims: Vec::new(),
}),
c::Decl::Typedef(c::Typedef {
doc: Doc::lines(&[
"A `wstring`, held as UTF-8. The runtime converts to UTF-16 on",
"the way out, so the surrogate rule of XTypes 1.3 7.4.1.1.2 is",
"implemented in C and not taken from the generator.",
]),
type_: c::Type::Pointer(Box::new(c::Type::Const(Box::new(c::Type::Primitive(
c::Primitive::Char,
))))),
name: Ident::new(WSTRING),
dims: Vec::new(),
}),
];
out.extend(struct_typedef(
MEMBER_NAME,
vec![
c::Member {
doc: Doc::line("The identifier as this header spells it."),
type_: c::Type::Named(Ident::new(STRING)),
name: Ident::new("field"),
dims: Vec::new(),
bits: None,
},
c::Member {
doc: Doc::line("The identifier as the IDL source spelled it."),
type_: c::Type::Named(Ident::new(STRING)),
name: Ident::new("idl"),
dims: Vec::new(),
bits: None,
},
],
Doc::lines(&[
"One member's two spellings.",
"",
"Load-bearing, not documentation. A member's ID under @autoid(HASH)",
"and under @hashid is the first four octets of the MD5 of its IDL",
"name (XTypes 1.3 7.3.1.2.1.1), so a caller holding only the C",
"spelling — which a keyword collision moved — cannot compute the ID",
"of the member it is writing.",
"",
"The entries come out of the `ZD_IDL_MEMBERS_<type>` macro beside",
"each declaration, in IDL declaration order:",
"",
" static const zd_member_name names[] = ZD_IDL_MEMBERS_a_Sample;",
]),
));
out
}
pub const STRING: &str = "zd_string";
pub const WSTRING: &str = "zd_wstring";
pub const MEMBER_NAME: &str = "zd_member_name";
pub const IDL_NAME_PREFIX: &str = "ZD_IDL_NAME_";
pub const IDL_MEMBERS_PREFIX: &str = "ZD_IDL_MEMBERS_";
pub const FLOAT128: &str = "zdw_f128";
pub fn c_items(ast: &Ast, applied: &Applied) -> Result<Vec<c::Decl>, MaterialiseError> {
let mut out = prelude();
let (begin_file, end_file) = verbatim::file_slots(ast, verbatim::aliases::C);
out.extend(begin_file.into_iter().map(c::Decl::Verbatim));
let order = ordered_types(ast);
no_bits_name_collision(ast, &order)?;
for id in &order {
let Ok(node) = ast.type_(*id) else { continue };
if matches!(
node.kind,
TypeKind::Structure { .. } | TypeKind::Valuetype { .. } | TypeKind::Union { .. }
) {
let name = c_type_name(ast, *id);
out.push(c::Decl::Typedef(c::Typedef {
doc: Doc::none(),
type_: c::Type::StructTag(Ident::new(&name)),
name: Ident::new(&name),
dims: Vec::new(),
}));
}
}
let mut emitted: Vec<String> = Vec::new();
let mut deferred: Vec<c::Decl> = Vec::new();
let lifecycle_order = order.clone();
for id in order {
let start = out.len();
declare(ast, applied, id, &mut out, &mut deferred, &mut emitted)?;
weave_verbatim(ast, id, start, &mut out);
}
out.append(&mut deferred);
let mut seen_typedefs: Vec<String> = Vec::new();
out.retain(|decl| {
if let c::Decl::Typedef(typedef) = decl {
if let c::Type::StructTag(tag) = &typedef.type_ {
if tag.as_str() == typedef.name.as_str() {
let name = typedef.name.as_str().to_string();
if seen_typedefs.contains(&name) {
return false;
}
seen_typedefs.push(name);
}
}
}
true
});
for (id, node) in ast.constants() {
if ghost_created_for(applied, node.node_id, "CConst").is_none() {
continue;
}
let _ = id;
let value = ast
.eval_const(node.expr)
.map_err(|_| MaterialiseError::Unevaluated {
construct: "constant's value".to_string(),
at: node.name.clone(),
})?;
let type_ = node
.type_
.resolved()
.map_or(c::Type::Primitive(c::Primitive::Int32), |target| {
c_value_type(ast, target)
});
let literal = c_constant_literal(&value, &type_, &node.name)?;
out.push(c::Decl::Define(c::Define {
doc: Doc::none(),
name: Ident::new(&c::escape_keyword(&node.name)),
value: Some(c::Expr::Paren(Box::new(c::Expr::Cast {
type_: Box::new(type_),
value: Box::new(c::Expr::Literal(literal)),
}))),
}));
}
for (id, node) in ast.exceptions() {
if ghost_created_for(applied, node.node_id, "CError").is_none() {
continue;
}
let _ = id;
let mut members = Vec::new();
let mut names = Vec::new();
for member in &node.members {
let Ok(member_node) = ast.member(*member) else {
continue;
};
let Some(target) = member_node.type_.resolved() else {
return Err(MaterialiseError::Unevaluated {
construct: "exception member type".to_string(),
at: member_node.name.clone(),
});
};
let field = c::escape_keyword(&member_node.name);
names.push((field.clone(), member_node.name.clone()));
members.push(c::Member {
doc: Doc::none(),
type_: c_value_type(ast, target),
name: Ident::new(&field),
dims: Vec::new(),
bits: None,
});
}
let exception_name = c::escape_keyword(&node.name);
out.extend(struct_typedef(
&exception_name,
members,
Doc::line("An exception, as the structure its members make."),
));
out.extend(idl_members(&exception_name, &names)?);
out.push(idl_name(&exception_name, &node.name));
}
out.extend(super::c_lifecycle::lifecycle(ast, &lifecycle_order)?);
out.extend(super::c_map::maps(ast, &lifecycle_order, &emitted)?);
out.extend(end_file.into_iter().map(c::Decl::Verbatim));
Ok(out)
}
fn weave_verbatim(ast: &Ast, id: Id<TypeNode>, start: usize, out: &mut Vec<c::Decl>) {
verbatim::weave(
verbatim::declaration_slots(ast, id, verbatim::aliases::C),
&c_type_name(ast, id),
start,
out,
);
}
impl verbatim::Carrier for c::Decl {
fn verbatim(text: String) -> Self {
Self::Verbatim(text)
}
fn take_into_body(
&mut self,
name: &str,
begin: &mut Vec<String>,
end: &mut Vec<String>,
) -> bool {
let Self::Struct(node) = self else {
return false;
};
if node.name.as_ref().map(Ident::as_str) != Some(name) {
return false;
}
node.body_text.begin.append(begin);
node.body_text.end.append(end);
true
}
}
#[must_use]
pub fn ordered_types(ast: &Ast) -> Vec<Id<TypeNode>> {
let all: Vec<Id<TypeNode>> = ast.types().map(|(id, _)| id).collect();
let order = ordered_types_from(ast, &all);
order
.iter()
.copied()
.filter(|id| {
let no_data_form = ast
.type_(*id)
.is_ok_and(|node| matches!(node.kind, TypeKind::Any | TypeKind::ValueBase));
!no_data_form
|| order
.iter()
.any(|other| other != id && dependencies(ast, *other).contains(id))
})
.collect()
}
#[must_use]
pub fn ordered_types_from(ast: &Ast, roots: &[Id<TypeNode>]) -> Vec<Id<TypeNode>> {
ordered_types_from_pruning(ast, roots, &[])
}
#[must_use]
pub fn ordered_types_from_pruning(
ast: &Ast,
roots: &[Id<TypeNode>],
pruned: &[Id<TypeNode>],
) -> Vec<Id<TypeNode>> {
let mut order = Vec::new();
let mut seen: Vec<Id<TypeNode>> = pruned.to_vec();
for root in roots {
visit(ast, *root, &mut order, &mut seen, 0);
}
order
}
fn visit(
ast: &Ast,
id: Id<TypeNode>,
order: &mut Vec<Id<TypeNode>>,
seen: &mut Vec<Id<TypeNode>>,
depth: usize,
) {
if depth > MAX_DEPTH || seen.contains(&id) {
return;
}
seen.push(id);
for dependency in dependencies(ast, id) {
visit(ast, dependency, order, seen, depth + 1);
}
order.push(id);
}
pub(crate) const MAX_DEPTH: usize = 64;
pub(crate) fn dependencies(ast: &Ast, id: Id<TypeNode>) -> Vec<Id<TypeNode>> {
let mut out = Vec::new();
let Ok(node) = ast.type_(id) else {
return out;
};
match &node.kind {
TypeKind::Sequence { element, .. } | TypeKind::Array { element, .. } => {
out.extend(element.resolved());
}
TypeKind::Map { key, value, .. } => {
out.extend(key.resolved());
out.extend(value.resolved());
}
TypeKind::Alias { aliased } => out.extend(aliased.resolved()),
TypeKind::ValueBox { boxed } => out.extend(boxed.resolved()),
TypeKind::Structure { .. } | TypeKind::Valuetype { .. } => {
if let TypeKind::Structure {
base: Some(base), ..
} = &node.kind
{
out.extend(base.resolved());
}
for member in ast.all_members_of(id) {
if let Ok(member_node) = ast.member(member) {
out.extend(member_node.type_.resolved());
}
}
}
TypeKind::Union { discriminator, .. } => {
out.extend(discriminator.resolved());
for case in ast.cases_of(id) {
if let Ok(case_node) = ast.case(*case) {
if let Ok(member) = ast.member(case_node.member) {
out.extend(member.type_.resolved());
}
}
}
}
TypeKind::Primitive(_)
| TypeKind::String { .. }
| TypeKind::Fixed { .. }
| TypeKind::Enumeration { .. }
| TypeKind::Bitmask { .. }
| TypeKind::Bitset { .. }
| TypeKind::Native
| TypeKind::Any
| TypeKind::ValueBase
| TypeKind::ObjectRef { .. }
| TypeKind::AnnotationType { .. } => {}
}
out
}
fn c_member_type(ast: &Ast, target: Id<TypeNode>, at: &str) -> Result<c::Type, MaterialiseError> {
let construct = match ast.type_(target).map(|node| &node.kind) {
Ok(TypeKind::Any) => Some("any"),
Ok(TypeKind::ValueBase) => Some("ValueBase"),
Ok(TypeKind::ObjectRef { .. }) => Some("Object"),
_ => None,
};
if let Some(construct) = construct {
return Err(MaterialiseError::NoLanguageForm {
construct: construct.to_string(),
language: "C",
at: at.to_string(),
});
}
Ok(c_value_type(ast, target))
}
fn c_held_member_type(
ast: &Ast,
enclosing: Id<TypeNode>,
member: &zerodds_ast::MemberNode,
target: Id<TypeNode>,
at: &str,
) -> Result<c::Type, MaterialiseError> {
let held = c_member_type(ast, target, at)?;
if member_held_behind_pointer(ast, enclosing, member, target) {
return Ok(c::Type::Pointer(Box::new(held)));
}
Ok(held)
}
#[must_use]
pub fn value_is_pointer(ast: &Ast, id: Id<TypeNode>) -> bool {
let mut current = id;
for _ in 0..MAX_DEPTH {
match ast.type_(current).map(|node| &node.kind) {
Ok(TypeKind::String { .. } | TypeKind::Fixed { .. }) => return true,
Ok(TypeKind::Alias { aliased }) => match aliased.resolved() {
Some(target) => current = target,
None => return false,
},
_ => return false,
}
}
false
}
#[must_use]
pub fn pointer_member(
ast: &Ast,
enclosing: Id<TypeNode>,
target: Id<TypeNode>,
annotated: bool,
) -> bool {
zerodds_ast_layout::member_held_inline_closes_a_cycle(ast, enclosing, target)
|| (annotated && !value_is_pointer(ast, target))
}
#[must_use]
pub fn pointer_annotated(member: &zerodds_ast::MemberNode) -> bool {
crate::adapter::annotated_external(member) || member.facets.is_optional()
}
pub fn named_member_held_behind_pointer(
ast: &Ast,
enclosing: Id<TypeNode>,
member_name: &str,
target: Id<TypeNode>,
) -> Result<bool, String> {
let structure_member = ast
.all_members_of(enclosing)
.into_iter()
.filter_map(|member| ast.member(member).ok())
.find(|member| member.name == member_name);
let branch_member = ast
.cases_of(enclosing)
.iter()
.filter_map(|case| ast.case(*case).ok())
.filter_map(|case| ast.member(case.member).ok())
.find(|member| member.name == member_name);
let member = structure_member.or(branch_member).ok_or_else(|| {
format!(
"`{}` declares no member or branch `{member_name}`",
c_type_name(ast, enclosing)
)
})?;
Ok(pointer_member(
ast,
enclosing,
target,
pointer_annotated(member),
))
}
#[must_use]
pub fn member_held_behind_pointer(
ast: &Ast,
enclosing: Id<TypeNode>,
member: &zerodds_ast::MemberNode,
target: Id<TypeNode>,
) -> bool {
pointer_member(ast, enclosing, target, pointer_annotated(member))
}
pub const PARENT: &str = "parent";
#[must_use]
pub fn struct_base(ast: &Ast, id: Id<TypeNode>) -> Option<Id<TypeNode>> {
match ast.type_(id).map(|node| &node.kind) {
Ok(TypeKind::Structure {
base: Some(base), ..
}) => base.resolved(),
_ => None,
}
}
#[derive(Debug, Clone)]
pub struct CMember {
pub handle: Id<zerodds_ast::MemberNode>,
pub path: String,
pub owner: Id<TypeNode>,
}
#[must_use]
pub fn c_members(ast: &Ast, id: Id<TypeNode>) -> Vec<CMember> {
let mut chain = vec![id];
while chain.len() < 64 {
match struct_base(ast, *chain.last().unwrap_or(&id)) {
Some(base) if !chain.contains(&base) => chain.push(base),
_ => break,
}
}
let mut out = Vec::new();
for (depth, owner) in chain.iter().enumerate().rev() {
let path = format!("{PARENT}.").repeat(depth);
for member in ast.members_of(*owner) {
out.push(CMember {
handle: *member,
path: path.clone(),
owner: *owner,
});
}
}
out
}
pub(crate) fn bound_tag(ast: &Ast, bound: &zerodds_ast::Bound) -> String {
match bound.expr() {
None => String::new(),
Some(expr) => match ast.eval_integral(expr) {
Ok(limit) => format!("_b{limit}"),
Err(_) => "_bx".to_string(),
},
}
}
#[must_use]
pub fn c_type_name(ast: &Ast, id: Id<TypeNode>) -> String {
type_name_at(ast, id, 0)
}
pub(crate) fn type_name_at(ast: &Ast, id: Id<TypeNode>, depth: usize) -> String {
if depth > MAX_DEPTH {
return format!("zd_deep_{}", id.slot());
}
let Ok(node) = ast.type_(id) else {
return "void".to_string();
};
match &node.kind {
TypeKind::Primitive(PrimitiveKind::Float128) => FLOAT128.to_string(),
TypeKind::Primitive(kind) => c_primitive(*kind).as_str().to_string(),
TypeKind::String {
width: CharWidth::Char,
..
} => STRING.to_string(),
TypeKind::String {
width: CharWidth::WChar,
..
} => WSTRING.to_string(),
TypeKind::Fixed { digits, scale } => format!("zd_fixed_{digits}_{scale}"),
TypeKind::Sequence { element, bound } => match element.resolved() {
Some(element) => format!(
"zd_seq{}_{}",
bound_tag(ast, bound),
type_tag_at(ast, element, depth + 1)
),
None => "void".to_string(),
},
TypeKind::Array { element, dims } => match element.resolved() {
Some(element) => {
let mut name = format!("zd_arr_{}", type_tag_at(ast, element, depth + 1));
for dim in dims {
match ast.eval_integral(*dim) {
Ok(length) => name.push_str(&format!("_{length}")),
Err(_) => name.push_str("_x"),
}
}
name
}
None => "void".to_string(),
},
TypeKind::Map { key, value, bound } => match (key.resolved(), value.resolved()) {
(Some(key), Some(value)) => super::c_map::map_type_name(ast, key, value, bound, depth)
.unwrap_or_else(|| {
format!(
"zd_map{}_{}_{}",
bound_tag(ast, bound),
type_tag_at(ast, key, depth + 1),
type_tag_at(ast, value, depth + 1)
)
}),
_ => "void".to_string(),
},
TypeKind::Alias { aliased } if node.name.is_empty() => match aliased.resolved() {
Some(target) => type_name_at(ast, target, depth + 1),
None => "void".to_string(),
},
_ => flattened(ast, id),
}
}
#[must_use]
pub fn type_tag(ast: &Ast, id: Id<TypeNode>) -> String {
type_tag_at(ast, id, 0)
}
pub(crate) fn type_tag_at(ast: &Ast, id: Id<TypeNode>, depth: usize) -> String {
if depth > MAX_DEPTH {
return format!("zd_deep_{}", id.slot());
}
let Ok(node) = ast.type_(id) else {
return type_name_at(ast, id, depth + 1);
};
match &node.kind {
TypeKind::Primitive(kind) => kind.as_str().replace(' ', "_"),
TypeKind::String {
width: CharWidth::Char,
bound,
} => format!("{STRING}{}", bound_tag(ast, bound)),
TypeKind::String {
width: CharWidth::WChar,
bound,
} => format!("{WSTRING}{}", bound_tag(ast, bound)),
TypeKind::Alias { aliased } if node.name.is_empty() => match aliased.resolved() {
Some(target) => type_tag_at(ast, target, depth + 1),
None => type_name_at(ast, id, depth + 1),
},
_ => type_name_at(ast, id, depth + 1),
}
}
#[must_use]
pub fn c_codec_tag(ast: &Ast, id: Id<TypeNode>) -> String {
type_tag(ast, id)
}
#[must_use]
pub fn c_map_entry_name(ast: &Ast, id: Id<TypeNode>) -> String {
super::c_map::entry_name(&c_type_name(ast, id))
}
#[must_use]
pub fn flattened(ast: &Ast, id: Id<TypeNode>) -> String {
let Ok(any) = ast.any_of_type(id) else {
return "zd_unnamed".to_string();
};
match ast.scoped_name_of(any) {
Some(scoped) => c::escape_keyword(&scoped.segments().join("_")),
None => "zd_unnamed".to_string(),
}
}
#[must_use]
pub fn module_prefix(ast: &Ast, id: Id<TypeNode>) -> String {
let Ok(any) = ast.any_of_type(id) else {
return String::new();
};
let Some(scoped) = ast.scoped_name_of(any) else {
return String::new();
};
let segments = scoped.segments();
if segments.len() <= 1 {
return String::new();
}
let mut prefix = segments[..segments.len() - 1].join("_");
prefix.push('_');
prefix
}
#[must_use]
pub const fn c_primitive(kind: PrimitiveKind) -> c::Primitive {
match kind {
PrimitiveKind::Boolean => c::Primitive::Bool,
PrimitiveKind::Octet | PrimitiveKind::UInt8 => c::Primitive::UInt8,
PrimitiveKind::Int8 => c::Primitive::Int8,
PrimitiveKind::Char => c::Primitive::Char,
PrimitiveKind::WChar => c::Primitive::UInt16,
PrimitiveKind::Int16 => c::Primitive::Int16,
PrimitiveKind::UInt16 => c::Primitive::UInt16,
PrimitiveKind::Int32 => c::Primitive::Int32,
PrimitiveKind::UInt32 => c::Primitive::UInt32,
PrimitiveKind::Int64 => c::Primitive::Int64,
PrimitiveKind::UInt64 => c::Primitive::UInt64,
PrimitiveKind::Float32 => c::Primitive::Float,
PrimitiveKind::Float64 | PrimitiveKind::Float128 => c::Primitive::Double,
}
}
#[must_use]
pub const fn c_holder(bits: u16) -> c::Primitive {
match bits {
0..=8 => c::Primitive::UInt8,
9..=16 => c::Primitive::UInt16,
17..=32 => c::Primitive::UInt32,
_ => c::Primitive::UInt64,
}
}
fn type_by_name(name: &str) -> c::Type {
c::Type::Named(Ident::new(name))
}
#[must_use]
pub fn c_value_type(ast: &Ast, id: Id<TypeNode>) -> c::Type {
let Ok(node) = ast.type_(id) else {
return c::Type::Void;
};
match &node.kind {
TypeKind::Primitive(PrimitiveKind::Float128) => type_by_name(FLOAT128),
TypeKind::Primitive(kind) => c::Type::Primitive(c_primitive(*kind)),
_ => type_by_name(&c_type_name(ast, id)),
}
}
fn no_bits_name_collision(ast: &Ast, order: &[Id<TypeNode>]) -> Result<(), MaterialiseError> {
let mut spelled: Vec<(String, String)> = Vec::new();
let mut primitives: Vec<String> = Vec::new();
for id in order {
let Ok(node) = ast.type_(*id) else { continue };
if node.name.is_empty() {
continue;
}
let name = c_type_name(ast, *id);
if matches!(node.kind, TypeKind::Primitive(_)) {
if !primitives.contains(&name) {
primitives.push(name.clone());
spelled.push((name, node.name.clone()));
}
continue;
}
if matches!(node.kind, TypeKind::Bitmask { .. }) {
spelled.push((name.clone(), node.name.clone()));
spelled.push((
format!("{name}Bits"),
format!("{}Bits (the flag type of bitmask {})", node.name, node.name),
));
} else {
spelled.push((name, node.name.clone()));
}
}
no_spelling_collision(
spelled
.iter()
.map(|(spelled, declared)| (spelled.as_str(), declared.as_str())),
"C",
"the types of one header",
)
}
#[allow(clippy::too_many_lines)]
fn declare(
ast: &Ast,
applied: &Applied,
id: Id<TypeNode>,
out: &mut Vec<c::Decl>,
deferred: &mut Vec<c::Decl>,
emitted: &mut Vec<String>,
) -> Result<(), MaterialiseError> {
let Ok(node) = ast.type_(id) else {
return Ok(());
};
let name = c_type_name(ast, id);
if emitted.contains(&name) {
return Ok(());
}
match &node.kind {
TypeKind::Primitive(_) => return Ok(()),
TypeKind::String { .. } => return Ok(()),
TypeKind::Fixed { digits, scale } => {
emitted.push(name.clone());
out.push(c::Decl::Typedef(c::Typedef {
doc: Doc::line(&format!(
"`fixed<{digits},{scale}>`, held as the decimal it was written as. \
A double cannot carry one, which is the point of the type."
)),
type_: c::Type::Pointer(Box::new(c::Type::Const(Box::new(c::Type::Primitive(
c::Primitive::Char,
))))),
name: Ident::new(&name),
dims: Vec::new(),
}));
}
TypeKind::Sequence { element, .. } => {
let Some(element) = element.resolved() else {
return Err(MaterialiseError::Unevaluated {
construct: "sequence element".to_string(),
at: name,
});
};
emitted.push(name.clone());
out.extend(sequence_decl(&name, c_member_type(ast, element, &name)?));
}
TypeKind::Array { element, dims } => {
let Some(element) = element.resolved() else {
return Err(MaterialiseError::Unevaluated {
construct: "array element".to_string(),
at: name,
});
};
let mut lengths = Vec::new();
for dim in dims {
let length =
ast.eval_integral(*dim)
.map_err(|_| MaterialiseError::Unevaluated {
construct: "array length".to_string(),
at: name.clone(),
})?;
lengths.push(c::Expr::Literal(Literal::int(length)));
}
emitted.push(name.clone());
out.push(c::Decl::Typedef(c::Typedef {
doc: Doc::line("An array. The element count is in the type and not on the wire."),
type_: c_member_type(ast, element, &name)?,
name: Ident::new(&name),
dims: lengths,
}));
}
TypeKind::Map { key, value, .. } => {
let (Some(key), Some(value)) = (key.resolved(), value.resolved()) else {
return Err(MaterialiseError::Unevaluated {
construct: "map key or value".to_string(),
at: name,
});
};
let entry = c_map_entry_name(ast, id);
emitted.push(name.clone());
out.push(c::Decl::Typedef(c::Typedef {
doc: Doc::none(),
type_: c::Type::StructTag(Ident::new(&entry)),
name: Ident::new(&entry),
dims: Vec::new(),
}));
deferred.push(c::Decl::Struct(c::Struct {
body_text: zerodds_lang_cst::common::BodyText::none(),
doc: Doc::line(
"One entry of a map. XTypes 1.3 7.4.1.1.4 defines a map as a \
sequence of these, and the rules write the two halves with no \
framing between them.",
),
name: Some(Ident::new(&entry)),
members: vec![
c::Member {
doc: Doc::none(),
type_: c_member_type(ast, key, &name)?,
name: Ident::new("key"),
dims: Vec::new(),
bits: None,
},
c::Member {
doc: Doc::none(),
type_: c_member_type(ast, value, &name)?,
name: Ident::new("value"),
dims: Vec::new(),
bits: None,
},
],
}));
out.extend(super::c_map::map_decl(&name, &entry));
}
TypeKind::Alias { aliased } => {
if node.name.is_empty() {
return Ok(());
}
if ghost_created_for(applied, node.node_id, "CAlias").is_none() {
return Ok(());
}
let Some(target) = aliased.resolved() else {
return Err(MaterialiseError::Unevaluated {
construct: "typedef target".to_string(),
at: name,
});
};
emitted.push(name.clone());
out.push(c::Decl::Typedef(c::Typedef {
doc: Doc::none(),
type_: c_value_type(ast, target),
name: Ident::new(&name),
dims: Vec::new(),
}));
}
TypeKind::Enumeration { enumerators } => {
if ghost_created_for(applied, node.node_id, "CEnum").is_none() {
return Ok(());
}
emitted.push(name.clone());
let prefix = module_prefix(ast, id);
let mut values = Vec::new();
let mut names = Vec::new();
for enumerator in enumerators {
let Ok(enum_node) = ast.enumerator(*enumerator) else {
continue;
};
if ghost_created_for(applied, enum_node.node_id, "CEnumerator").is_none() {
continue;
}
let value = ast.effective_enumerator_value(*enumerator).ok_or_else(|| {
MaterialiseError::Unevaluated {
construct: "enumerator value".to_string(),
at: enum_node.name.clone(),
}
})?;
let constant = format!("{prefix}{}", c::escape_keyword(&enum_node.name));
names.push((constant.clone(), enum_node.name.clone()));
values.push((constant, value));
}
out.push(c::Decl::Typedef(c::Typedef {
doc: Doc::line(
"An enumeration. The holder is signed because XTypes 1.3 \
7.4.3.5.3 makes it so; the width on the wire is the \
@bit_bound's and is applied by the runtime.",
),
type_: c::Type::Primitive(c::Primitive::Int32),
name: Ident::new(&name),
dims: Vec::new(),
}));
for (constant, value) in values {
out.push(c::Decl::Define(c::Define {
doc: Doc::none(),
name: Ident::new(&constant),
value: Some(c::Expr::Paren(Box::new(c::Expr::Cast {
type_: Box::new(type_by_name(&name)),
value: Box::new(c::Expr::Literal(Literal::int(value))),
}))),
}));
}
out.extend(idl_members(&name, &names)?);
}
TypeKind::Bitmask { bits } => {
if ghost_created_for(applied, node.node_id, "CBitmask").is_none() {
return Ok(());
}
emitted.push(name.clone());
let bound = ast.effective_bit_bound(id);
let prefix = module_prefix(ast, id);
let mut names = Vec::new();
let bits_name = format!("{name}Bits");
out.push(c::Decl::Typedef(c::Typedef {
doc: Doc::line(
"The enumeration of the flags of a bitmask (XTypes 1.3 \
7.5.1.2.2): one constant per flag, valued 1 << position.",
),
type_: c::Type::Primitive(c_holder(bound)),
name: Ident::new(&bits_name),
dims: Vec::new(),
}));
out.push(c::Decl::Typedef(c::Typedef {
doc: Doc::line(
"A bitmask: a collection of flags, as the unsigned integer \
Table 44 of XTypes 1.3 7.5.1.2.2 gives for its bound.",
),
type_: c::Type::Primitive(c_holder(bound)),
name: Ident::new(&name),
dims: Vec::new(),
}));
for bit in bits {
let Ok(bit_node) = ast.bit_value(*bit) else {
continue;
};
if ghost_created_for(applied, bit_node.node_id, "CBitConst").is_none() {
continue;
}
let position = ast.effective_bit_position(*bit).ok_or_else(|| {
MaterialiseError::Unevaluated {
construct: "bit position".to_string(),
at: bit_node.name.clone(),
}
})?;
let constant = format!("{prefix}{}", c::escape_keyword(&bit_node.name));
names.push((constant.clone(), bit_node.name.clone()));
out.push(c::Decl::Define(c::Define {
doc: Doc::none(),
name: Ident::new(&constant),
value: Some(c::Expr::Paren(Box::new(c::Expr::Binary {
op: "<<",
lhs: Box::new(c::Expr::Cast {
type_: Box::new(type_by_name(&bits_name)),
value: Box::new(c::Expr::Literal(Literal::int(1))),
}),
rhs: Box::new(c::Expr::Literal(Literal::int(i128::from(position)))),
}))),
}));
}
out.extend(idl_members(&name, &names)?);
}
TypeKind::Bitset { .. } => {
if ghost_created_for(applied, node.node_id, "CBitset").is_none() {
return Ok(());
}
emitted.push(name.clone());
let mut members = Vec::new();
let mut names = Vec::new();
for field in &ast.all_bitfields_of(id) {
let Ok(field_node) = ast.bitfield(*field) else {
continue;
};
let Some(field_name) = field_node.name.as_ref() else {
continue;
};
let escaped = c::escape_keyword(field_name);
names.push((escaped.clone(), field_name.clone()));
members.push(c::Member {
doc: Doc::none(),
type_: c::Type::Primitive(c_holder(field_node.bits)),
name: Ident::new(&escaped),
dims: Vec::new(),
bits: None,
});
}
out.extend(struct_typedef(
&name,
members,
Doc::line(
"A bitset. The fields are separate integers here; the packing into \
the anchor is the codec's, because C leaves bitfield allocation \
order to the implementation.",
),
));
out.extend(idl_members(&name, &names)?);
}
TypeKind::Structure { .. } | TypeKind::Valuetype { .. } => {
let created = if matches!(node.kind, TypeKind::Valuetype { .. }) {
"CValue"
} else {
"CStruct"
};
if ghost_created_for(applied, node.node_id, created).is_none() {
return Ok(());
}
emitted.push(name.clone());
let mut members = Vec::new();
let mut names = Vec::new();
if let Some(base) = struct_base(ast, id) {
members.push(c::Member {
doc: Doc::line(
"The base: an instance of the base type is the first member \
(XTypes 1.3 7.5.1.4.1.3).",
),
type_: type_by_name(&c_type_name(ast, base)),
name: Ident::new(PARENT),
dims: Vec::new(),
bits: None,
});
}
let own = ast.members_of(id).to_vec();
for CMember {
handle: member,
path,
..
} in c_members(ast, id)
{
let Ok(member_node) = ast.member(member) else {
continue;
};
let field = c::escape_keyword(&member_node.name);
names.push((format!("{path}{field}"), member_node.name.clone()));
if !own.contains(&member) {
continue;
}
let Some(target) = member_node.type_.resolved() else {
return Err(MaterialiseError::Unevaluated {
construct: "member type".to_string(),
at: member_node.name.clone(),
});
};
members.push(c::Member {
doc: if member_node.facets.is_optional() {
Doc::line(
"An @optional member: NULL when it is absent. In a `@mutable` \
type an absent member is not written at all; outside one its \
absence is written — a zero `<is_present>` byte under XCDR2 \
(rule 20) or a parameter header declaring a length of zero \
under XCDR1 (§7.4.1.2).",
)
} else {
Doc::none()
},
type_: c_held_member_type(ast, id, member_node, target, &member_node.name)?,
name: Ident::new(&field),
dims: Vec::new(),
bits: None,
});
}
if members.is_empty() {
return Err(MaterialiseError::NoLanguageForm {
construct: format!(
"`{name}`, an aggregate with no members — C23 §6.7.2.1p8 \
leaves a structure with no named member undefined"
),
language: "C",
at: name,
});
}
out.extend(struct_typedef(&name, members, Doc::none()));
out.extend(idl_members(&name, &names)?);
}
TypeKind::Union { discriminator, .. } => {
if ghost_created_for(applied, node.node_id, "CUnion").is_none() {
return Ok(());
}
emitted.push(name.clone());
let Some(disc) = discriminator.resolved() else {
return Err(MaterialiseError::Unevaluated {
construct: "union discriminator".to_string(),
at: name,
});
};
let mut arms = Vec::new();
let mut names = Vec::new();
for case in ast.cases_of(id) {
let Ok(case_node) = ast.case(*case) else {
continue;
};
if ghost_created_for(applied, case_node.node_id, "CCase").is_none() {
continue;
}
let Ok(member) = ast.member(case_node.member) else {
continue;
};
let Some(target) = member.type_.resolved() else {
return Err(MaterialiseError::Unevaluated {
construct: "union case type".to_string(),
at: member.name.clone(),
});
};
let field = c::escape_keyword(&member.name);
names.push((field.clone(), member.name.clone()));
arms.push(c::Member {
doc: Doc::none(),
type_: c_held_member_type(ast, id, member, target, &member.name)?,
name: Ident::new(&field),
dims: Vec::new(),
bits: None,
});
}
let inner = format!("{name}_u");
out.push(c::Decl::Union(c::Union {
doc: Doc::line("The branches. Which one is live is what `_d` says."),
name: Some(Ident::new(&inner)),
members: arms,
}));
out.extend(struct_typedef(
&name,
vec![
c::Member {
doc: Doc::none(),
type_: c_member_type(ast, disc, &name)?,
name: Ident::new("_d"),
dims: Vec::new(),
bits: None,
},
c::Member {
doc: Doc::none(),
type_: c::Type::UnionTag(Ident::new(&inner)),
name: Ident::new("_u"),
dims: Vec::new(),
bits: None,
},
],
Doc::none(),
));
out.extend(idl_members(&name, &names)?);
}
TypeKind::Native => {
if ghost_created_for(applied, node.node_id, "CNative").is_none() {
return Ok(());
}
emitted.push(name.clone());
let mapping = ast
.any_of_type(id)
.ok()
.and_then(|any| ast.facets(any))
.and_then(|facets| facets.corba.as_ref())
.and_then(|corba| corba.native_mapping.clone());
out.push(c::Decl::Typedef(c::Typedef {
doc: Doc::line("A native type. Opaque unless a facet named its mapping."),
type_: match mapping {
Some(spelling) => c::Type::Named(Ident::new(&spelling)),
None => c::Type::Pointer(Box::new(c::Type::Void)),
},
name: Ident::new(&name),
dims: Vec::new(),
}));
}
TypeKind::ObjectRef { .. } | TypeKind::AnnotationType { .. } => {
return Ok(());
}
TypeKind::Any | TypeKind::ValueBase => {
return Ok(());
}
TypeKind::ValueBox { boxed } => {
let Some(target) = boxed.resolved() else {
return Ok(());
};
emitted.push(name.clone());
out.push(c::Decl::Typedef(c::Typedef {
doc: Doc::line("A boxed value: a pointer, absent when null."),
type_: c::Type::Pointer(Box::new(c_member_type(ast, target, &name)?)),
name: Ident::new(&name),
dims: Vec::new(),
}));
}
}
if has_idl_name(&node.kind) && !node.name.is_empty() {
out.push(idl_name(&name, &node.name));
}
Ok(())
}
fn idl_members(
name: &str,
pairs: &[(String, String)],
) -> Result<Option<c::Decl>, MaterialiseError> {
no_spelling_collision(
pairs
.iter()
.map(|(spelled, declared)| (spelled.as_str(), declared.as_str())),
"C",
name,
)?;
if pairs.is_empty() {
return Ok(None);
}
let entries = pairs
.iter()
.map(|(field, idl)| {
c::Expr::Initializer(vec![
c::Expr::Literal(Literal::Str(field.clone())),
c::Expr::Literal(Literal::Str(idl.clone())),
])
})
.collect();
Ok(Some(c::Decl::Define(c::Define {
doc: Doc::lines(&[
"The IDL spelling of each member, beside the spelling this header",
"uses. `zd_member_name` is the record an entry fits.",
]),
name: Ident::new(&format!("{IDL_MEMBERS_PREFIX}{name}")),
value: Some(c::Expr::Initializer(entries)),
})))
}
fn idl_name(name: &str, idl: &str) -> c::Decl {
c::Decl::Define(c::Define {
doc: Doc::line("The IDL spelling of the declaration above."),
name: Ident::new(&format!("{IDL_NAME_PREFIX}{name}")),
value: Some(c::Expr::Literal(Literal::Str(idl.to_string()))),
})
}
const fn has_idl_name(kind: &TypeKind) -> bool {
matches!(
kind,
TypeKind::Structure { .. }
| TypeKind::Valuetype { .. }
| TypeKind::Union { .. }
| TypeKind::Enumeration { .. }
| TypeKind::Bitmask { .. }
| TypeKind::Bitset { .. }
| TypeKind::Alias { .. }
| TypeKind::Native
| TypeKind::ValueBox { .. }
)
}
fn struct_typedef(name: &str, members: Vec<c::Member>, doc: Doc) -> Vec<c::Decl> {
vec![
c::Decl::Typedef(c::Typedef {
doc: Doc::none(),
type_: c::Type::StructTag(Ident::new(name)),
name: Ident::new(name),
dims: Vec::new(),
}),
c::Decl::Struct(c::Struct {
body_text: zerodds_lang_cst::common::BodyText::none(),
doc,
name: Some(Ident::new(name)),
members,
}),
]
}
fn sequence_decl(name: &str, element: c::Type) -> Vec<c::Decl> {
struct_typedef(
name,
vec![
c::Member {
doc: Doc::none(),
type_: c::Type::Primitive(c::Primitive::UInt32),
name: Ident::new("_maximum"),
dims: Vec::new(),
bits: None,
},
c::Member {
doc: Doc::none(),
type_: c::Type::Primitive(c::Primitive::UInt32),
name: Ident::new("_length"),
dims: Vec::new(),
bits: None,
},
c::Member {
doc: Doc::none(),
type_: c::Type::Pointer(Box::new(element)),
name: Ident::new("_buffer"),
dims: Vec::new(),
bits: None,
},
c::Member {
doc: Doc::none(),
type_: c::Type::Primitive(c::Primitive::Bool),
name: Ident::new("_release"),
dims: Vec::new(),
bits: None,
},
],
Doc::line(
"A sequence: a count and the elements. The buffer is the caller's — \
nothing in the write path allocates.",
),
)
}
fn ghost_created_for(applied: &Applied, node: NodeId, type_name: &str) -> Option<GhostId> {
let anchor = applied
.origin
.iter()
.find(|(_, model)| **model == node)
.map(|(ghost, _)| *ghost)?;
let typ = applied.graph.types.lookup(type_name)?;
applied
.graph
.nodes_of_type(typ)
.map(|created| created.id)
.find(|created| {
applied.graph.connected(&anchor, created)
|| applied
.graph
.parts(&anchor)
.any(|part| applied.graph.connected(&part.other, created))
})
}
fn c_constant_literal(
value: &zerodds_ast::ConstValue,
type_: &c::Type,
at: &str,
) -> Result<Literal, MaterialiseError> {
use zerodds_ast::ConstValue;
let refuse = |construct: &str| MaterialiseError::NoLanguageForm {
construct: construct.to_string(),
language: "C",
at: at.to_string(),
};
Ok(match value {
ConstValue::Boolean(flag) => Literal::Bool(*flag),
ConstValue::Integer(number) => Literal::Int(zerodds_lang_render::c::integer_text(*number)),
ConstValue::Char(character) => Literal::Char(*character),
ConstValue::WChar(point) => Literal::int(i128::from(*point)),
ConstValue::Float { mantissa, exponent } => {
if matches!(type_, c::Type::Named(name) if name.as_str() == FLOAT128) {
return Err(refuse("a `long double` constant, which has no C literal"));
}
let mut text = constant::float_text(mantissa, exponent.as_deref());
if matches!(type_, c::Type::Primitive(c::Primitive::Float)) {
text.push('f');
}
Literal::Float(text)
}
ConstValue::Fixed { digits, scale } => {
Literal::Str(constant::fixed_decimal(digits, *scale))
}
ConstValue::String(text) => Literal::Str(text.clone()),
ConstValue::WString(points) => Literal::Str(
constant::wstring_utf8(points)
.ok_or_else(|| refuse("a `wstring` constant holding a lone surrogate"))?,
),
_ => return Err(refuse("a constant form this binding has no literal for")),
})
}