use crate::adapter::constant;
use crate::adapter::cst::{MaterialiseError, no_spelling_collision};
use crate::adapter::interface;
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::common::{Doc, Ident, Literal};
use zerodds_lang_cst::nim;
const LANGUAGE: &str = "Nim";
const INVENTED_PREFIX: &str = "ZdMapEntry_";
pub const DISCRIMINATOR: &str = "d";
const MAX_MODULE_DEPTH: usize = 64;
const MAX_TYPE_DEPTH: usize = 64;
#[derive(Debug, Default)]
struct Build {
types: Vec<nim::TypeDecl>,
consts: Vec<nim::ConstDecl>,
emitted: Vec<(String, String)>,
}
pub fn nim_sections(ast: &Ast, applied: &Applied) -> Result<Vec<nim::Section>, MaterialiseError> {
let mut build = Build::default();
let mut unattached: Vec<String> = Vec::new();
for (id, _) in ast.types() {
let start = build.types.len();
declare(ast, applied, id, &mut build)?;
attach_verbatim(
verbatim::declaration_slots(ast, id, verbatim::aliases::NIM),
&nim_type_name(ast, id),
build.types.get_mut(start..).unwrap_or_default(),
&mut unattached,
);
}
for (_, node) in ast.constants() {
if ghost_created_for(applied, node.node_id, "NimConst").is_none() {
continue;
}
let value = ast
.eval_const(node.expr)
.map_err(|_| MaterialiseError::Unevaluated {
construct: "constant's value".to_string(),
at: node.name.clone(),
})?;
let declared = node.type_.resolved();
let type_ = match declared {
Some(target) => nim_value_type(ast, target)?,
None => nim::Type::Primitive(nim::Primitive::Int32),
};
let name = declared_name(ast, &node.name, node.module);
declare_name(&mut build, &name, &node.name)?;
let value = nim_constant(&value, &type_, &node.name)?;
build.consts.push(nim::ConstDecl {
doc: idl_note(&name, &node.name),
name: Ident::new(&name),
exported: true,
type_: Some(type_),
value,
});
}
for (_, node) in ast.exceptions() {
if ghost_created_for(applied, node.node_id, "NimError").is_none() {
continue;
}
let name = declared_name(ast, &node.name, node.module);
declare_name(&mut build, &name, &node.name)?;
let mut pairs = Vec::new();
for member in &node.members {
if let Ok(member_node) = ast.member(*member) {
pairs.push((field_key(&member_node.name), member_node.name.clone()));
}
}
no_spelling_collision(as_pairs(&pairs), LANGUAGE, &name)?;
let mut fields = 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 (type_, doc) = member_shape(ast, member_node, target)?;
fields.push(nim::Field {
doc,
name: Ident::new(&field_name(&member_node.name)),
exported: true,
type_,
});
}
build.types.push(nim::TypeDecl {
text: zerodds_lang_cst::nim::TypeText::default(),
doc: with_idl_note(
Doc::line("An exception, as the object its members make."),
&name,
&node.name,
),
name: Ident::new(&name),
exported: true,
body: nim::TypeBody::Object(fields),
});
}
for (id, node) in ast.interfaces() {
if ghost_created_for(applied, node.node_id, "NimInterface").is_none() {
continue;
}
let name = declared_name(ast, &node.name, node.module);
declare_name(&mut build, &name, &node.name)?;
let mut fields = Vec::new();
let mut signatures = Vec::new();
let operations = interface::all_operations_flattened(ast, id)
.map_err(|error| interface::interface_error_to_materialise(error, &node.name))?;
for op in &operations {
let field_ident = match op.origin {
interface::Origin::AttributeGetter => field_name(op.attribute_name()),
interface::Origin::AttributeSetter => {
let attr = op.attribute_name();
format!("set{}", capitalise(&field_name(attr)))
}
interface::Origin::Declared => field_name(&op.name),
};
signatures.push((nim::normalise(&field_ident), op.name.clone()));
let mut params = Vec::new();
let mut param_names = Vec::new();
for param in &op.params {
param_names.push((field_key(¶m.name), param.name.clone()));
params.push(nim::Param {
name: Ident::new(&field_name(¶m.name)),
type_: nim_member_type(ast, param.type_, ¶m.name)?,
});
}
no_spelling_collision(as_pairs(¶m_names), LANGUAGE, &op.name)?;
let result = match op.returns {
Some(returns) => Some(Box::new(nim_member_type(ast, returns, &op.name)?)),
None => None,
};
let mut doc = repair_note(&op.name);
if let Some(line) = op.raises_line() {
doc.push(&line);
}
fields.push(nim::Field {
doc,
name: Ident::new(&field_ident),
exported: true,
type_: nim::Type::Proc { params, result },
});
}
no_spelling_collision(as_pairs(&signatures), LANGUAGE, &name)?;
build.types.push(nim::TypeDecl {
text: zerodds_lang_cst::nim::TypeText::default(),
doc: with_idl_note(
Doc::lines(&[
"An interface, as an object of procedural fields — one per",
"operation, and one (or, `readonly` aside, two) per attribute. Nim",
"has no interface construct; `concept` is experimental, so the",
"shape a consumer can rely on is a field per member. A derived",
"interface's bases are flattened in — the object inherits from",
"nothing — so this is every field the interface has, own and",
"inherited. The transport is the RPC arc's business; the shape is",
"the IDL's. `raises`/`getraises`/`setraises` are documented on the",
"field they belong to.",
]),
&name,
&node.name,
),
name: Ident::new(&name),
exported: true,
body: nim::TypeBody::Object(fields),
});
}
let (begin_file, end_file) = verbatim::file_slots(ast, verbatim::aliases::NIM);
let mut sections: Vec<nim::Section> =
begin_file.into_iter().map(nim::Section::Verbatim).collect();
if !build.types.is_empty() {
sections.push(nim::Section::Type(build.types));
}
sections.extend(unattached.into_iter().map(nim::Section::Verbatim));
if !build.consts.is_empty() {
sections.push(nim::Section::Const(build.consts));
}
sections.extend(end_file.into_iter().map(nim::Section::Verbatim));
Ok(sections)
}
fn attach_verbatim(
slots: verbatim::Slots,
name: &str,
declared: &mut [nim::TypeDecl],
unattached: &mut Vec<String>,
) {
if slots.is_empty() {
return;
}
let mut begin = slots.begin;
let mut end = slots.end;
if let Some(object) = declared
.iter_mut()
.find(|decl| decl.name.as_str() == name && matches!(decl.body, nim::TypeBody::Object(_)))
{
object.text.body.begin.append(&mut begin);
object.text.body.end.append(&mut end);
}
if declared.is_empty() {
unattached.extend(slots.before);
unattached.extend(begin);
unattached.extend(end);
unattached.extend(slots.after);
return;
}
if let Some(first) = declared.first_mut() {
let mut before = slots.before;
before.append(&mut begin);
before.append(&mut first.text.before);
first.text.before = before;
}
if let Some(last) = declared.last_mut() {
last.text.after.append(&mut end);
last.text.after.extend(slots.after);
}
}
pub fn nim_file(ast: &Ast, applied: &Applied) -> Result<nim::File, MaterialiseError> {
let sections = nim_sections(ast, applied)?;
let mut imports = Vec::new();
if sections.iter().any(section_needs_options) {
imports.push(nim::Import::new("std/options"));
}
Ok(nim::File {
doc: Doc::lines(&[
"Generated by ZeroDDS from an IDL source. Do not edit.",
"",
"Every declaration carries Nim's `*` export marker, which is what makes",
"it reachable from another module. The marker renames nothing, so the",
"IDL spelling of a declaration is the spelling in this file unless a",
"comment above it says otherwise.",
]),
imports,
sections,
})
}
fn section_needs_options(section: &nim::Section) -> bool {
let nim::Section::Type(decls) = section else {
return false;
};
decls.iter().any(|decl| match &decl.body {
nim::TypeBody::Object(fields) => fields.iter().any(|field| field.type_.needs_options()),
nim::TypeBody::Alias(type_) => type_.needs_options(),
nim::TypeBody::Enum(_) => false,
})
}
#[allow(clippy::too_many_lines)]
fn declare(
ast: &Ast,
applied: &Applied,
id: Id<TypeNode>,
build: &mut Build,
) -> Result<(), MaterialiseError> {
let Ok(node) = ast.type_(id) else {
return Ok(());
};
match &node.kind {
TypeKind::Primitive(_)
| TypeKind::String { .. }
| TypeKind::Fixed { .. }
| TypeKind::Sequence { .. }
| TypeKind::Array { .. } => {}
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: nim_type_name(ast, id),
});
};
let name = map_entry_name_at(ast, id, 0)?;
if build
.emitted
.iter()
.any(|(key, _)| *key == nim::normalise(&name))
{
return Ok(());
}
declare_name(build, &name, &name)?;
build.types.push(nim::TypeDecl {
text: zerodds_lang_cst::nim::TypeText::default(),
doc: Doc::lines(&[
"One entry of a map. DDS-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: Ident::new(&name),
exported: true,
body: nim::TypeBody::Object(vec![
nim::Field {
doc: Doc::none(),
name: Ident::new("key"),
exported: true,
type_: nim_member_type(ast, key, &name)?,
},
nim::Field {
doc: Doc::none(),
name: Ident::new("value"),
exported: true,
type_: nim_member_type(ast, value, &name)?,
},
]),
});
}
TypeKind::Alias { aliased } => {
if node.name.is_empty() {
return Ok(());
}
if ghost_created_for(applied, node.node_id, "NimAlias").is_none() {
return Ok(());
}
let Some(target) = aliased.resolved() else {
return Err(MaterialiseError::Unevaluated {
construct: "typedef target".to_string(),
at: node.name.clone(),
});
};
let name = nim_type_name(ast, id);
declare_name(build, &name, &node.name)?;
build.types.push(nim::TypeDecl {
text: zerodds_lang_cst::nim::TypeText::default(),
doc: idl_note(&name, &node.name),
name: Ident::new(&name),
exported: true,
body: nim::TypeBody::Alias(nim_member_type(ast, target, &node.name)?),
});
}
TypeKind::Enumeration { enumerators } => {
if ghost_created_for(applied, node.node_id, "NimEnum").is_none() {
return Ok(());
}
let name = nim_type_name(ast, id);
declare_name(build, &name, &node.name)?;
let prefix = module_prefix(ast, id);
let mut fields = Vec::new();
let mut previous: Option<i128> = None;
for enumerator in enumerators {
let Ok(enum_node) = ast.enumerator(*enumerator) else {
continue;
};
if ghost_created_for(applied, enum_node.node_id, "NimEnumField").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(),
}
})?;
if previous.is_some_and(|last| value <= last) {
return Err(MaterialiseError::NoLanguageForm {
construct: "an enumerator whose value does not exceed its predecessor's, \
which a Nim enum cannot order"
.to_string(),
language: LANGUAGE,
at: enum_node.name.clone(),
});
}
previous = Some(value);
let field = escape_joined(&[prefix.as_str(), enum_node.name.as_str()]);
declare_name(build, &field, &enum_node.name)?;
fields.push(nim::EnumField {
doc: idl_note(&field, &enum_node.name),
name: Ident::new(&field),
value: Some(nim::Expr::int(value)),
});
}
build.types.push(nim::TypeDecl {
text: zerodds_lang_cst::nim::TypeText::default(),
doc: with_idl_note(
Doc::lines(&[
"An enumeration. Every ordinal is written out, because @value admits",
"gaps and a field with none takes its predecessor's plus one. A Nim",
"enum with gaps is a `holey` enum: it holds and compares exactly as a",
"contiguous one does, and only `..` iteration and array indexing are",
"withdrawn. The width on the wire is the @bit_bound's and is applied",
"by the runtime.",
]),
&name,
&node.name,
),
name: Ident::new(&name),
exported: true,
body: nim::TypeBody::Enum(fields),
});
}
TypeKind::Bitmask { bits } => {
if ghost_created_for(applied, node.node_id, "NimBitmask").is_none() {
return Ok(());
}
let name = nim_type_name(ast, id);
declare_name(build, &name, &node.name)?;
let bound = ast.effective_bit_bound(id);
build.types.push(nim::TypeDecl {
text: zerodds_lang_cst::nim::TypeText::default(),
doc: with_idl_note(
Doc::lines(&[
"A bitmask, as the unsigned integer Table 33 of DDS-XTypes 1.3",
"7.4.1.1.3.2 gives for its bound.",
]),
&name,
&node.name,
),
name: Ident::new(&name),
exported: true,
body: nim::TypeBody::Alias(nim::Type::Primitive(nim_holder(bound))),
});
let prefix = module_prefix(ast, id);
for bit in bits {
let Ok(bit_node) = ast.bit_value(*bit) else {
continue;
};
if ghost_created_for(applied, bit_node.node_id, "NimBitConst").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 = escape_joined(&[prefix.as_str(), bit_node.name.as_str()]);
declare_name(build, &constant, &bit_node.name)?;
build.consts.push(nim::ConstDecl {
doc: idl_note(&constant, &bit_node.name),
name: Ident::new(&constant),
exported: true,
type_: None,
value: nim::Expr::Binary {
op: "shl",
lhs: Box::new(nim::Expr::Convert {
type_: Box::new(nim::Type::named(&name)),
value: Box::new(nim::Expr::int(1)),
}),
rhs: Box::new(nim::Expr::int(i128::from(position))),
},
});
}
}
TypeKind::Bitset { .. } => {
if ghost_created_for(applied, node.node_id, "NimBitset").is_none() {
return Ok(());
}
let name = nim_type_name(ast, id);
declare_name(build, &name, &node.name)?;
let mut pairs = Vec::new();
for field in &ast.all_bitfields_of(id) {
let Ok(field_node) = ast.bitfield(*field) else {
continue;
};
let Some(field_ident) = field_node.name.as_ref() else {
continue;
};
pairs.push((field_key(field_ident), field_ident.clone()));
}
no_spelling_collision(as_pairs(&pairs), LANGUAGE, &name)?;
let mut fields = Vec::new();
for field in &ast.all_bitfields_of(id) {
let Ok(field_node) = ast.bitfield(*field) else {
continue;
};
let Some(field_ident) = field_node.name.as_ref() else {
continue;
};
fields.push(nim::Field {
doc: repair_note(field_ident),
name: Ident::new(&field_name(field_ident)),
exported: true,
type_: nim::Type::Primitive(nim_holder(field_node.bits)),
});
}
build.types.push(nim::TypeDecl {
text: zerodds_lang_cst::nim::TypeText::default(),
doc: with_idl_note(
Doc::lines(&[
"A bitset. The fields are separate integers here; the packing",
"into the anchor is the codec's, because the order on the wire",
"is the declaration's.",
]),
&name,
&node.name,
),
name: Ident::new(&name),
exported: true,
body: nim::TypeBody::Object(fields),
});
}
TypeKind::Structure { .. } | TypeKind::Valuetype { .. } => {
let created = if matches!(node.kind, TypeKind::Valuetype { .. }) {
"NimValue"
} else {
"NimObject"
};
if ghost_created_for(applied, node.node_id, created).is_none() {
return Ok(());
}
let name = nim_type_name(ast, id);
declare_name(build, &name, &node.name)?;
let members = ast.members_of(id).to_vec();
let base = super::c::struct_base(ast, id);
let mut pairs = Vec::new();
if base.is_some() {
pairs.push((field_key("parent"), "parent".to_string()));
}
for member in &members {
if let Ok(member_node) = ast.member(*member) {
pairs.push((field_key(&member_node.name), member_node.name.clone()));
}
}
no_spelling_collision(as_pairs(&pairs), LANGUAGE, &name)?;
let mut fields = Vec::new();
if let Some(base) = base {
let mut doc = Doc::none();
doc.push("The base structure, the first member (XTypes 1.3 7.5.1.4.1.3).");
fields.push(nim::Field {
doc,
name: Ident::new(&field_name("parent")),
exported: true,
type_: nim_member_type(ast, base, "parent")?,
});
}
for member in &members {
let Ok(member_node) = ast.member(*member) else {
continue;
};
let Some(target) = member_node.type_.resolved() else {
return Err(MaterialiseError::Unevaluated {
construct: "member type".to_string(),
at: member_node.name.clone(),
});
};
let (type_, doc) = member_shape(ast, member_node, target)?;
fields.push(nim::Field {
doc,
name: Ident::new(&field_name(&member_node.name)),
exported: true,
type_,
});
}
build.types.push(nim::TypeDecl {
text: zerodds_lang_cst::nim::TypeText::default(),
doc: idl_note(&name, &node.name),
name: Ident::new(&name),
exported: true,
body: nim::TypeBody::Object(fields),
});
}
TypeKind::Union { discriminator, .. } => {
if ghost_created_for(applied, node.node_id, "NimUnion").is_none() {
return Ok(());
}
let name = nim_type_name(ast, id);
declare_name(build, &name, &node.name)?;
let Some(disc) = discriminator.resolved() else {
return Err(MaterialiseError::Unevaluated {
construct: "union discriminator".to_string(),
at: name,
});
};
let cases = ast.cases_of(id);
let mut pairs = vec![(nim::normalise(DISCRIMINATOR), "<discriminator>".to_string())];
for case in cases {
if let Ok(case_node) = ast.case(*case) {
if let Ok(member) = ast.member(case_node.member) {
pairs.push((field_key(&member.name), member.name.clone()));
}
}
}
no_spelling_collision(as_pairs(&pairs), LANGUAGE, &name)?;
let mut fields = vec![nim::Field {
doc: Doc::line("The discriminator, as it came off the wire."),
name: Ident::new(DISCRIMINATOR),
exported: true,
type_: nim_member_type(ast, disc, &name)?,
}];
for case in cases {
let Ok(case_node) = ast.case(*case) else {
continue;
};
if ghost_created_for(applied, case_node.node_id, "NimCase").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 (type_, doc) = held_shape(ast, member, target)?;
fields.push(nim::Field {
doc,
name: Ident::new(&field_name(&member.name)),
exported: true,
type_: nim::Type::option(type_),
});
}
build.types.push(nim::TypeDecl {
text: zerodds_lang_cst::nim::TypeText::default(),
doc: with_idl_note(
Doc::lines(&[
"A union. The branch that was written is the one that is `isSome`;",
"`d` is the discriminator, which may hold a value that selects no",
"branch at all (IDL 4.2 7.4.1.4.4.1). Only the selected branch goes",
"on the wire (DDS-XTypes 1.3 7.4.1.1.5.2). Not a Nim object variant:",
"a decoder constructs one from a discriminator it read at run time,",
"which Nim cannot check at compile time.",
]),
&name,
&node.name,
),
name: Ident::new(&name),
exported: true,
body: nim::TypeBody::Object(fields),
});
}
TypeKind::Native => {
if ghost_created_for(applied, node.node_id, "NimNative").is_none() {
return Ok(());
}
let name = nim_type_name(ast, id);
declare_name(build, &name, &node.name)?;
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());
build.types.push(nim::TypeDecl {
text: zerodds_lang_cst::nim::TypeText::default(),
doc: with_idl_note(
Doc::line("A native type. Opaque unless a facet named its mapping."),
&name,
&node.name,
),
name: Ident::new(&name),
exported: true,
body: nim::TypeBody::Alias(match mapping {
Some(spelling) => nim::Type::named(&spelling),
None => nim::Type::Primitive(nim::Primitive::Pointer),
}),
});
}
TypeKind::ValueBox { boxed } => {
let Some(target) = boxed.resolved() else {
return Ok(());
};
let name = nim_type_name(ast, id);
if build
.emitted
.iter()
.any(|(key, _)| *key == nim::normalise(&name))
{
return Ok(());
}
declare_name(build, &name, &node.name)?;
build.types.push(nim::TypeDecl {
text: zerodds_lang_cst::nim::TypeText::default(),
doc: with_idl_note(
Doc::line("A boxed value: a value that may be absent."),
&name,
&node.name,
),
name: Ident::new(&name),
exported: true,
body: nim::TypeBody::Alias(nim::Type::option(nim_member_type(ast, target, &name)?)),
});
}
TypeKind::ObjectRef { .. }
| TypeKind::AnnotationType { .. }
| TypeKind::Any
| TypeKind::ValueBase => {}
}
Ok(())
}
pub fn nim_member_type(
ast: &Ast,
target: Id<TypeNode>,
at: &str,
) -> Result<nim::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: LANGUAGE,
at: at.to_string(),
});
}
nim_value_type(ast, target)
}
pub fn nim_value_type(ast: &Ast, id: Id<TypeNode>) -> Result<nim::Type, MaterialiseError> {
value_type_at(ast, id, 0)
}
fn value_type_at(ast: &Ast, id: Id<TypeNode>, depth: usize) -> Result<nim::Type, MaterialiseError> {
if depth > MAX_TYPE_DEPTH {
return Err(MaterialiseError::Unevaluated {
construct: format!("type, whose Nim spelling nests past {MAX_TYPE_DEPTH}"),
at: nim_type_name(ast, id),
});
}
let Ok(node) = ast.type_(id) else {
return Ok(nim::Type::Primitive(nim::Primitive::Pointer));
};
Ok(match &node.kind {
TypeKind::Primitive(kind) => nim_primitive_type(*kind),
TypeKind::String { .. } | TypeKind::Fixed { .. } => {
nim::Type::Primitive(nim::Primitive::String)
}
TypeKind::Sequence { element, .. } => {
let Some(element) = element.resolved() else {
return Err(MaterialiseError::Unevaluated {
construct: "sequence element".to_string(),
at: node.name.clone(),
});
};
nim::Type::seq(value_type_at(ast, element, depth + 1)?)
}
TypeKind::Array { element, dims } => {
let Some(element) = element.resolved() else {
return Err(MaterialiseError::Unevaluated {
construct: "array element".to_string(),
at: node.name.clone(),
});
};
let mut type_ = value_type_at(ast, element, depth + 1)?;
for dim in dims.iter().rev() {
let length =
ast.eval_integral(*dim)
.map_err(|_| MaterialiseError::Unevaluated {
construct: "array length".to_string(),
at: node.name.clone(),
})?;
type_ = nim::Type::array(length, type_);
}
type_
}
TypeKind::Map { .. } => {
nim::Type::seq(nim::Type::named(&map_entry_name_at(ast, id, depth + 1)?))
}
TypeKind::Alias { aliased } if node.name.is_empty() => match aliased.resolved() {
Some(target) => value_type_at(ast, target, depth + 1)?,
None => nim::Type::Primitive(nim::Primitive::Pointer),
},
_ => nim::Type::named(&nim_type_name(ast, id)),
})
}
#[must_use]
pub fn nim_primitive_type(kind: PrimitiveKind) -> nim::Type {
let primitive = match kind {
PrimitiveKind::Boolean => nim::Primitive::Bool,
PrimitiveKind::Octet | PrimitiveKind::UInt8 | PrimitiveKind::Char => nim::Primitive::UInt8,
PrimitiveKind::Int8 => nim::Primitive::Int8,
PrimitiveKind::WChar | PrimitiveKind::UInt16 => nim::Primitive::UInt16,
PrimitiveKind::Int16 => nim::Primitive::Int16,
PrimitiveKind::Int32 => nim::Primitive::Int32,
PrimitiveKind::UInt32 => nim::Primitive::UInt32,
PrimitiveKind::Int64 => nim::Primitive::Int64,
PrimitiveKind::UInt64 => nim::Primitive::UInt64,
PrimitiveKind::Float32 => nim::Primitive::Float32,
PrimitiveKind::Float64 => nim::Primitive::Float64,
PrimitiveKind::Float128 => nim::Primitive::String,
};
nim::Type::Primitive(primitive)
}
#[must_use]
pub const fn nim_holder(bits: u16) -> nim::Primitive {
match bits {
0..=8 => nim::Primitive::UInt8,
9..=16 => nim::Primitive::UInt16,
17..=32 => nim::Primitive::UInt32,
_ => nim::Primitive::UInt64,
}
}
#[must_use]
pub fn nim_type_name(ast: &Ast, id: Id<TypeNode>) -> String {
let segments = scoped_segments(ast, id);
if segments.is_empty() {
return "ZdUnnamed".to_string();
}
let borrowed: Vec<&str> = segments.iter().map(String::as_str).collect();
escape_joined(&borrowed)
}
fn raw_type_name(ast: &Ast, id: Id<TypeNode>) -> String {
let segments = scoped_segments(ast, id);
if segments.is_empty() {
return "ZdUnnamed".to_string();
}
segments.join("_")
}
fn scoped_segments(ast: &Ast, id: Id<TypeNode>) -> Vec<String> {
let Ok(any) = ast.any_of_type(id) else {
return Vec::new();
};
match ast.scoped_name_of(any) {
Some(scoped) => scoped.segments().to_vec(),
None => Vec::new(),
}
}
pub fn nim_map_entry_name(ast: &Ast, id: Id<TypeNode>) -> Result<String, MaterialiseError> {
map_entry_name_at(ast, id, 0)
}
fn map_entry_name_at(
ast: &Ast,
id: Id<TypeNode>,
depth: usize,
) -> Result<String, MaterialiseError> {
if depth > MAX_TYPE_DEPTH {
return Err(MaterialiseError::Unevaluated {
construct: format!("map entry name, which nests past {MAX_TYPE_DEPTH}"),
at: nim_type_name(ast, id),
});
}
let Ok(node) = ast.type_(id) else {
return Ok(format!("{INVENTED_PREFIX}Unknown"));
};
let TypeKind::Map { key, value, .. } = &node.kind else {
return Ok(format!("{INVENTED_PREFIX}Unknown"));
};
let key = match key.resolved() {
Some(k) => type_tag(ast, k, depth + 1)?,
None => "Unknown".to_string(),
};
let value = match value.resolved() {
Some(v) => type_tag(ast, v, depth + 1)?,
None => "Unknown".to_string(),
};
Ok(nim::escape_name(&format!("{INVENTED_PREFIX}{key}_{value}")))
}
fn type_tag(ast: &Ast, id: Id<TypeNode>, depth: usize) -> Result<String, MaterialiseError> {
if depth > MAX_TYPE_DEPTH {
return Err(MaterialiseError::Unevaluated {
construct: format!("type tag, which nests past {MAX_TYPE_DEPTH}"),
at: nim_type_name(ast, id),
});
}
Ok(match ast.type_(id).map(|node| node.kind.clone()) {
Ok(TypeKind::Primitive(kind)) => capitalise(&kind.as_str().replace(' ', "_")),
Ok(TypeKind::String {
width: CharWidth::Char,
..
}) => "String".to_string(),
Ok(TypeKind::String {
width: CharWidth::WChar,
..
}) => "WString".to_string(),
Ok(TypeKind::Sequence { element, .. }) => match element.resolved() {
Some(element) => format!("SeqOf{}", type_tag(ast, element, depth + 1)?),
None => "SeqOfUnknown".to_string(),
},
Ok(TypeKind::Array { element, dims }) => match element.resolved() {
Some(element) => {
let mut name = format!("ArrOf{}", type_tag(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 => "ArrOfUnknown".to_string(),
},
Ok(TypeKind::Map { .. }) => map_entry_name_at(ast, id, depth + 1)?,
_ => raw_type_name(ast, id),
})
}
fn capitalise(text: &str) -> String {
let mut chars = text.chars();
let Some(first) = chars.next() else {
return String::new();
};
let mut out = String::new();
for upper in first.to_uppercase() {
out.push(upper);
}
out.push_str(chars.as_str());
out
}
#[must_use]
pub fn nim_bare_name(ast: &Ast, id: Id<TypeNode>) -> String {
let escaped = nim::escape_name(&raw_type_name(ast, id));
escaped.trim_matches('`').to_string()
}
#[must_use]
pub fn nim_enumerator_constant(ast: &Ast, id: Id<TypeNode>, enumerator: &str) -> String {
let prefix = module_prefix(ast, id);
escape_joined(&[prefix.as_str(), enumerator])
}
pub(crate) fn module_prefix(ast: &Ast, id: Id<TypeNode>) -> String {
let segments = scoped_segments(ast, id);
if segments.len() <= 1 {
return String::new();
}
let mut prefix = segments[..segments.len() - 1].join("_");
prefix.push('_');
prefix
}
fn declared_name(ast: &Ast, name: &str, module: Id<zerodds_ast::ModuleNode>) -> String {
let mut segments: Vec<String> = Vec::new();
let mut current = Some(module);
let mut depth = 0;
while let Some(id) = current {
if depth > MAX_MODULE_DEPTH {
break;
}
depth += 1;
let Ok(node) = ast.module(id) else { break };
if node.name.is_empty() {
break;
}
segments.push(node.name.clone());
current = node.parent;
}
segments.reverse();
segments.push(name.to_string());
let borrowed: Vec<&str> = segments.iter().map(String::as_str).collect();
escape_joined(&borrowed)
}
fn escape_joined(segments: &[&str]) -> String {
let joined: Vec<&str> = segments
.iter()
.copied()
.filter(|segment| !segment.is_empty())
.collect();
nim::escape_name(&joined.join("_"))
}
#[must_use]
pub fn field_name(name: &str) -> String {
nim::escape_name(name)
}
fn held_shape(
ast: &Ast,
member: &zerodds_ast::MemberNode,
target: Id<TypeNode>,
) -> Result<(nim::Type, Doc), MaterialiseError> {
let mut doc = repair_note(&member.name);
let mut type_ = nim_member_type(ast, target, &member.name)?;
if super::held_externally(ast, member) {
type_ = nim::Type::reference(type_);
doc.push("Held by reference - @external, DDS-XTypes 1.3 Table 21. The");
doc.push("indirection is the language mapping's and changes no octet.");
}
Ok((type_, doc))
}
fn member_shape(
ast: &Ast,
member: &zerodds_ast::MemberNode,
target: Id<TypeNode>,
) -> Result<(nim::Type, Doc), MaterialiseError> {
let (mut type_, mut doc) = held_shape(ast, member, target)?;
if member.facets.is_optional() {
type_ = nim::Type::option(type_);
doc.push("Optional. `none(T)` when the member is absent - DDS-XTypes 1.3");
doc.push("7.2.2.4.4.4.7. In a @mutable type an absent member is not written");
doc.push("at all; outside one its absence is written.");
}
Ok((type_, doc))
}
fn field_key(name: &str) -> String {
nim::normalise(&field_name(name))
}
fn idl_note(spelled: &str, declared: &str) -> Doc {
if spelled == declared {
return Doc::none();
}
Doc::line(&format!("IDL: `{declared}`."))
}
fn with_idl_note(mut doc: Doc, spelled: &str, declared: &str) -> Doc {
if spelled != declared {
doc.push(&format!("IDL: `{declared}`."));
}
doc
}
fn repair_note(declared: &str) -> Doc {
idl_note(&field_name(declared), declared)
}
fn declare_name(build: &mut Build, spelled: &str, declared: &str) -> Result<(), MaterialiseError> {
let key = nim::normalise(spelled);
if nim::PREDECLARED.contains(&key.as_str()) {
return Err(MaterialiseError::NoLanguageForm {
construct: format!(
"a declaration named `{declared}`, which is the Nim name this file's own \
declarations are written in terms of"
),
language: LANGUAGE,
at: "the generated module".to_string(),
});
}
if let Some((_, first)) = build.emitted.iter().find(|(other, _)| *other == key) {
return Err(MaterialiseError::SpellingCollision {
first: first.clone(),
second: declared.to_string(),
spelled: key,
language: LANGUAGE,
at: "the generated module".to_string(),
});
}
build.emitted.push((key, declared.to_string()));
Ok(())
}
fn as_pairs(pairs: &[(String, String)]) -> Vec<(&str, &str)> {
pairs
.iter()
.map(|(spelled, declared)| (spelled.as_str(), declared.as_str()))
.collect()
}
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 nim_constant(
value: &zerodds_ast::ConstValue,
type_: &nim::Type,
at: &str,
) -> Result<nim::Expr, MaterialiseError> {
use zerodds_ast::ConstValue;
let refuse = |construct: &str| MaterialiseError::NoLanguageForm {
construct: construct.to_string(),
language: LANGUAGE,
at: at.to_string(),
};
let is_string = matches!(type_, nim::Type::Primitive(nim::Primitive::String));
Ok(match value {
ConstValue::Boolean(flag) => nim::Expr::Literal(Literal::Bool(*flag)),
ConstValue::Integer(number) => nim::Expr::Literal(Literal::int(*number)),
ConstValue::Char(character) => {
nim::Expr::Literal(Literal::int(i128::from(u32::from(*character))))
}
ConstValue::WChar(point) => nim::Expr::Literal(Literal::int(i128::from(*point))),
ConstValue::Float { mantissa, exponent } => {
if is_string {
return Err(refuse("a `long double` constant, which has no Nim literal"));
}
nim::Expr::Literal(Literal::Float(constant::float_text(
mantissa,
exponent.as_deref(),
)))
}
ConstValue::Fixed { digits, scale } => {
nim::Expr::Literal(Literal::Str(constant::fixed_decimal(digits, *scale)))
}
ConstValue::String(text) => nim::Expr::Literal(Literal::Str(text.clone())),
ConstValue::WString(points) => nim::Expr::Literal(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")),
})
}