use crate::adapter::constant;
use crate::adapter::cst::MaterialiseError;
use crate::adapter::interface;
use crate::apply::Applied;
use crate::verbatim;
use seesaw_tgg::ident::GhostId;
use zerodds_ast::{
Ast, Bound, CaseLabel, CharWidth, ParamDirection, PrimitiveKind, TypeKind, TypeNode,
};
use zerodds_ast_core::{Id, NodeId};
use zerodds_lang_cst::ada;
use zerodds_lang_cst::common::{Doc, Ident, Literal, Path};
const LANGUAGE: &str = "Ada";
pub const PACKAGE: &str = "Zd_Types";
pub const FILE_NAME: &str = "zd_types.ads";
const SEQ_PREFIX: &str = "Zd_Seq_";
const ARR_PREFIX: &str = "Zd_Arr_";
const MAP_PREFIX: &str = "Zd_Map_";
const MAP_ENTRY_PREFIX: &str = "Zd_Map_Entry_";
const OPT_PREFIX: &str = "Zd_Opt_";
const REF_PREFIX: &str = "Zd_Ref_";
const ARRAY_SUFFIX: &str = "_Array";
pub const DISCRIMINANT: &str = "D";
pub const PRESENT: &str = "Present";
pub const VALUE: &str = "Value";
const MAX_MODULE_DEPTH: usize = 64;
const MAX_DEPTH: usize = 64;
#[derive(Debug, Clone, Default)]
struct Cycle {
cut: Vec<Id<TypeNode>>,
completed: Vec<Id<TypeNode>>,
predeclared: Vec<Id<TypeNode>>,
deferred: Vec<ada::Decl>,
}
#[must_use]
pub fn held_by_reference(
ast: &Ast,
aggregate: Id<TypeNode>,
member_type: Id<TypeNode>,
optional: bool,
external: bool,
) -> bool {
if !(optional || external) {
return false;
}
let Ok(plan) = ordered_types(ast) else {
return false;
};
let position = |id: Id<TypeNode>| plan.order.iter().position(|other| *other == id);
let Some(at) = position(aggregate) else {
return false;
};
let waiting =
|id: Id<TypeNode>| plan.cut.contains(&id) && position(id).is_none_or(|place| place >= at);
let Some(target) = declared_target(ast, member_type) else {
return false;
};
if waiting(target) {
return true;
}
let end = alias_end(ast, target);
end != target && waiting(end)
}
#[must_use]
pub fn reference_designated(ast: &Ast, member_type: Id<TypeNode>) -> Option<Id<TypeNode>> {
let target = declared_target(ast, member_type)?;
let Ok(plan) = ordered_types(ast) else {
return Some(target);
};
if plan.cut.contains(&target) {
return Some(target);
}
Some(alias_end(ast, target))
}
fn pending(ast: &Ast, id: Id<TypeNode>, cycle: &Cycle) -> Option<Id<TypeNode>> {
let target = declared_target(ast, id)?;
if cycle.cut.contains(&target) && !cycle.completed.contains(&target) {
return Some(target);
}
let end = alias_end(ast, target);
if end != target && cycle.cut.contains(&end) && !cycle.completed.contains(&end) {
return Some(end);
}
None
}
fn alias_end(ast: &Ast, id: Id<TypeNode>) -> Id<TypeNode> {
let mut current = id;
for _ in 0..MAX_DEPTH {
match ast.type_(current).map(|node| node.kind.clone()) {
Ok(TypeKind::Alias { aliased }) => match aliased.resolved() {
Some(next) => current = next,
None => return current,
},
_ => return current,
}
}
current
}
fn declared_target(ast: &Ast, id: Id<TypeNode>) -> Option<Id<TypeNode>> {
let mut current = id;
for _ in 0..MAX_DEPTH {
let node = ast.type_(current).ok()?;
match &node.kind {
TypeKind::Primitive(_)
| TypeKind::String { .. }
| TypeKind::Fixed { .. }
| TypeKind::Any
| TypeKind::ValueBase
| TypeKind::ObjectRef { .. }
| TypeKind::AnnotationType { .. } => return None,
TypeKind::Alias { aliased } if node.name.is_empty() => {
current = aliased.resolved()?;
}
_ => return Some(current),
}
}
None
}
fn predeclare(ast: &Ast, id: Id<TypeNode>, out: &mut Vec<ada::Decl>, cycle: &mut Cycle) {
if cycle.predeclared.contains(&id) {
return;
}
cycle.predeclared.push(id);
out.push(ada::Decl::Type(ada::TypeDecl {
doc: Doc::lines(&[
"Declared here and completed below (RM 3.10.1). The IDL type reaches",
"itself, so the name is needed before the type can be written; between",
"the two an incomplete type may only be the designated subtype of an",
"access type, which is what every use of it here is.",
]),
name: Ident::new(&ada_type_name(ast, id)),
def: ada::TypeDef::Incomplete,
}));
}
fn incomplete(name: &str, why: &str) -> ada::Decl {
ada::Decl::Type(ada::TypeDecl {
doc: Doc::line(why),
name: Ident::new(name),
def: ada::TypeDef::Incomplete,
})
}
fn reference_type(
ast: &Ast,
target: Id<TypeNode>,
out: &mut Vec<ada::Decl>,
emitted: &mut Vec<Declared>,
cycle: &mut Cycle,
) -> Result<String, MaterialiseError> {
let name = format!("{REF_PREFIX}{}", type_tag(ast, target));
if already(emitted, &name) {
return Ok(name);
}
remember(emitted, &name, &name, false)?;
predeclare(ast, target, out, cycle);
out.push(ada::Decl::Type(ada::TypeDecl {
doc: Doc::lines(&[
"A reference to a type that reaches this one back. `null` is the absent",
"state; RM 3.10.1(9) admits an incomplete designated subtype, which is",
"why this declaration can stand before the type it designates.",
]),
name: Ident::new(&name),
def: ada::TypeDef::Access(Path::single(&ada_type_name(ast, target))),
}));
Ok(name)
}
#[derive(Debug, Clone)]
struct Declared {
key: String,
declared: String,
overloadable: bool,
}
pub fn ada_items(ast: &Ast, applied: &Applied) -> Result<Vec<ada::Decl>, MaterialiseError> {
let mut out = Vec::new();
let mut emitted: Vec<Declared> = Vec::new();
let plan = ordered_types(ast)?;
let mut cycle = Cycle {
cut: plan.cut,
completed: Vec::new(),
predeclared: Vec::new(),
deferred: Vec::new(),
};
for id in plan.order {
let start = out.len();
declare(ast, applied, id, &mut out, &mut emitted, &mut cycle)?;
verbatim::weave(
verbatim::declaration_slots(ast, id, verbatim::aliases::ADA),
&ada_type_name(ast, id),
start,
&mut out,
);
}
out.append(&mut cycle.deferred);
for id in &cycle.predeclared {
if !cycle.completed.contains(id) {
let name = ada_type_name(ast, *id);
return Err(MaterialiseError::NoLanguageForm {
construct: format!(
"`{name}`, declared incomplete to break a cycle and never completed \
— Ada 2012 RM 3.10.1 requires the completion in the same \
declarative part"
),
language: LANGUAGE,
at: name,
});
}
}
for (_, node) in ast.constants() {
if ghost_created_for(applied, node.node_id, "AdaConst").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) => ada_value_path(ast, target)?,
None => ada::Primitive::Integer32.path(),
};
let name = declared_name(ast, &node.name, node.module);
remember(&mut emitted, &name, &node.name, false)?;
out.push(ada::Decl::Object(ada::ObjectDecl {
doc: idl_note(&name, &node.name),
name: Ident::new(&name),
type_: type_.clone(),
value: ada_constant(&value, &type_, &node.name)?,
}));
}
for (_, node) in ast.exceptions() {
if ghost_created_for(applied, node.node_id, "AdaError").is_none() {
continue;
}
let name = declared_name(ast, &node.name, node.module);
remember(&mut emitted, &name, &node.name, false)?;
let mut pairs = Vec::new();
for member in &node.members {
if let Ok(member_node) = ast.member(*member) {
pairs.push((component_name(&member_node.name), member_node.name.clone()));
}
}
no_folded_collision(&pairs, &name)?;
let mut components = 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 spelled = component_name(&member_node.name);
components.push(ada::Component {
doc: idl_note(&spelled, &member_node.name),
name: Ident::new(&spelled),
type_: ada_member_path(ast, target, &member_node.name)?,
});
}
out.push(ada::Decl::Type(ada::TypeDecl {
doc: with_idl_note(
Doc::lines(&[
"An exception, as the record its members make. Not an Ada",
"`exception`: RM 11.1 gives an exception no components at all and",
"only a message string travels with one, so an IDL exception's",
"members would have nowhere to sit. Raising is the RPC arc's.",
]),
&name,
&node.name,
),
name: Ident::new(&name),
def: ada::TypeDef::Record(ada::Record::plain(components)),
}));
}
for (id, node) in ast.interfaces() {
if ghost_created_for(applied, node.node_id, "AdaInterface").is_none() {
continue;
}
let name = declared_name(ast, &node.name, node.module);
remember(&mut emitted, &name, &node.name, false)?;
out.push(ada::Decl::Type(ada::TypeDecl {
doc: with_idl_note(
Doc::lines(&[
"An interface, as the Ada 2005 interface type its operations and",
"attributes make (RM 3.9.4): an attribute a Get_/Set_ pair of",
"primitive operations, `readonly` only the first. The operations",
"below are its primitive operations and are abstract, which RM",
"3.9.4 requires; the transport is the RPC arc's business and the",
"shape is the IDL's.",
"",
"A derived interface carries its bases' operations and attributes",
"too: this type is written with no progenitor list, so IDL 4.2",
"7.4.3's inherited members are declared against it rather than",
"inherited.",
]),
&name,
&node.name,
),
name: Ident::new(&name),
def: ada::TypeDef::Interface,
}));
let operations = interface::all_operations_flattened(ast, id)
.map_err(|error| interface::interface_error_to_materialise(error, &node.name))?;
let mut signatures = Vec::new();
for op in &operations {
signatures.push((component_name(&op.name), op.name.clone()));
}
no_folded_collision(&signatures, &name)?;
for op in &operations {
let spelled = component_name(&op.name);
remember(&mut emitted, &spelled, &op.name, true)?;
let mut names = vec![(SELF.to_string(), "<controlling operand>".to_string())];
let mut params = vec![ada::Param {
name: Ident::new(SELF),
mode: ada::Mode::In,
type_: Path::single(&name),
}];
for param in &op.params {
let spelled = component_name(¶m.name);
names.push((spelled.clone(), param.name.clone()));
params.push(ada::Param {
name: Ident::new(&spelled),
mode: match param.direction {
ParamDirection::In => ada::Mode::In,
ParamDirection::Out => ada::Mode::Out,
ParamDirection::InOut => ada::Mode::InOut,
},
type_: ada_member_path(ast, param.type_, ¶m.name)?,
});
}
no_folded_collision(&names, &spelled)?;
let result = match op.returns {
Some(returns) => Some(ada_member_path(ast, returns, &op.name)?),
None => None,
};
let mut doc = idl_note(&spelled, &op.name);
if let Some(line) = op.raises_line() {
doc.push(&line.replace('\u{a7}', "section "));
}
out.push(ada::Decl::Subprogram(ada::Subprogram {
doc,
kind: if result.is_some() {
ada::SubprogramKind::Function
} else {
ada::SubprogramKind::Procedure
},
name: Ident::new(&spelled),
params,
result,
is_abstract: true,
}));
}
}
Ok(out)
}
const SELF: &str = "Self";
pub fn ada_file(ast: &Ast, applied: &Applied) -> Result<ada::File, MaterialiseError> {
let decls = ada_items(ast, applied)?;
Ok(ada::File {
doc: Doc::lines(&[
"Generated by ZeroDDS from an IDL source. Do not edit.",
"",
"Ada 2012. GNAT derives the unit name from the file name, so this",
"spec has to be compiled as zd_types.ads.",
"",
"Identifiers are case-insensitive in Ada (RM 2.3(5/2)), so the IDL",
"spelling of a member reads back from the Ada one unchanged. Where it",
"does not -- an Ada reserved word, an identifier RM 2.3 will not have,",
"a module flattened onto the name -- the IDL spelling is in the comment",
"beside the declaration, because a member's ID under @autoid(HASH) is",
"the MD5 of the IDL name (DDS-XTypes 1.3 7.3.1.2.1.1).",
]),
withs: context_clause(&decls),
use_types: operator_types(&decls),
package: Ident::new(PACKAGE),
decls,
})
}
fn operator_types(decls: &[ada::Decl]) -> Vec<Path> {
let mut marks: Vec<Path> = Vec::new();
let note = |mark: &Path, marks: &mut Vec<Path>| {
let resolved = resolve_mark(decls, mark);
if resolved.segments().len() > 1 && !marks.contains(&resolved) {
marks.push(resolved);
}
};
for decl in decls {
match decl {
ada::Decl::Object(node) if needs_operator(&node.value) => {
note(&node.type_, &mut marks);
}
ada::Decl::Type(node) => {
let ada::TypeDef::Record(record) = &node.def else {
continue;
};
for discriminant in &record.discriminants {
if discriminant.default.as_ref().is_some_and(needs_operator) {
note(&discriminant.type_, &mut marks);
}
}
let Some(variant) = &record.variant else {
continue;
};
let Some(selector) = record
.discriminants
.iter()
.find(|d| d.name == variant.discriminant)
else {
continue;
};
for arm in &variant.arms {
for choice in &arm.choices {
if let ada::Choice::Value(value) = choice {
if needs_operator(value) {
note(&selector.type_, &mut marks);
}
}
}
}
}
_ => {}
}
}
marks
}
fn needs_operator(expr: &ada::Expr) -> bool {
match expr {
ada::Expr::Negate(_) => true,
ada::Expr::Literal(Literal::Int(text) | Literal::Float(text)) => text.starts_with('-'),
_ => false,
}
}
fn resolve_mark(decls: &[ada::Decl], mark: &Path) -> Path {
let mut current = mark.clone();
for _ in 0..MAX_DEPTH {
if current.segments().len() > 1 {
return current;
}
let Some(name) = current.segments().first().map(Ident::as_str) else {
return current;
};
let next = decls.iter().find_map(|decl| match decl {
ada::Decl::Subtype(node) if node.name.as_str() == name => Some(node.mark.clone()),
_ => None,
});
match next {
Some(mark) => current = mark,
None => return current,
}
}
current
}
fn context_clause(decls: &[ada::Decl]) -> Vec<Path> {
let mut roots: Vec<String> = Vec::new();
for decl in decls {
collect_roots(decl, &mut roots);
}
let mut out = Vec::new();
for unit in [
Path::single("Interfaces"),
Path::new(&["Ada", "Strings", "Unbounded"]),
Path::single("System"),
] {
let named = unit
.segments()
.first()
.is_some_and(|root| roots.iter().any(|seen| seen == root.as_str()));
if named {
out.push(unit);
}
}
out
}
fn push_root(path: &Path, out: &mut Vec<String>) {
if path.segments().len() > 1 {
if let Some(root) = path.segments().first() {
out.push(root.as_str().to_string());
}
}
}
fn collect_roots(decl: &ada::Decl, out: &mut Vec<String>) {
let push = push_root;
match decl {
ada::Decl::Type(node) => match &node.def {
ada::TypeDef::Record(record) => {
for discriminant in &record.discriminants {
push(&discriminant.type_, out);
}
for component in &record.components {
push(&component.type_, out);
}
if let Some(variant) = &record.variant {
for arm in &variant.arms {
for component in &arm.components {
push(&component.type_, out);
}
}
}
}
ada::TypeDef::Derived(mark) | ada::TypeDef::Access(mark) => push(mark, out),
ada::TypeDef::Array { index, element } => {
push(element, out);
for dimension in index {
if let ada::Index::Unconstrained(mark) = dimension {
push(mark, out);
}
}
}
ada::TypeDef::Enumeration(_) | ada::TypeDef::Interface | ada::TypeDef::Incomplete => {}
},
ada::Decl::Subtype(node) => push(&node.mark, out),
ada::Decl::Object(node) => {
push(&node.type_, out);
if let ada::Expr::Call { name, .. } = &node.value {
push(name, out);
}
}
ada::Decl::Subprogram(node) => {
for param in &node.params {
push(¶m.type_, out);
}
if let Some(result) = &node.result {
push(result, out);
}
}
ada::Decl::EnumRep(_)
| ada::Decl::Comment(_)
| ada::Decl::Verbatim(_)
| ada::Decl::SubprogramBody(_) => {}
}
}
#[derive(Debug, Clone)]
pub struct Plan {
pub order: Vec<Id<TypeNode>>,
pub cut: Vec<Id<TypeNode>>,
}
pub fn ordered_types(ast: &Ast) -> Result<Plan, MaterialiseError> {
let mut cut: Vec<Id<TypeNode>> = Vec::new();
let passes = ast.types().count() + 1;
for _ in 0..passes {
let mut walk = Walk {
order: Vec::new(),
seen: Vec::new(),
stack: Vec::new(),
found: None,
};
for (id, _) in ast.types() {
visit(ast, id, &cut, &mut walk, 0);
if walk.found.is_some() {
break;
}
}
match walk.found {
None => {
return Ok(Plan {
order: walk.order,
cut,
});
}
Some(at) if !cut.contains(&at) => cut.push(at),
Some(_) => break,
}
}
Err(MaterialiseError::NoLanguageForm {
construct: "a cycle in the type graph that cannot be cut at an aggregate — Ada \
2012 RM 3.10.1 admits an incomplete type only as the designated \
subtype of an access type"
.to_string(),
language: LANGUAGE,
at: "the generated package".to_string(),
})
}
struct Walk {
order: Vec<Id<TypeNode>>,
seen: Vec<Id<TypeNode>>,
stack: Vec<Id<TypeNode>>,
found: Option<Id<TypeNode>>,
}
fn visit(ast: &Ast, id: Id<TypeNode>, cut: &[Id<TypeNode>], walk: &mut Walk, depth: usize) {
if walk.found.is_some() {
return;
}
if let Some(position) = walk.stack.iter().position(|other| *other == id) {
walk.found = Some(cut_point(ast, &walk.stack[position..]).unwrap_or(id));
return;
}
if depth > MAX_DEPTH || walk.seen.contains(&id) {
return;
}
walk.seen.push(id);
walk.stack.push(id);
for dependency in dependencies(ast, id) {
if cut.contains(&dependency) {
continue;
}
visit(ast, dependency, cut, walk, depth + 1);
if walk.found.is_some() {
break;
}
}
walk.stack.pop();
if walk.found.is_none() {
walk.order.push(id);
}
}
fn cut_point(ast: &Ast, cycle: &[Id<TypeNode>]) -> Option<Id<TypeNode>> {
cycle.iter().copied().find(|id| is_aggregate(ast, *id))
}
fn is_aggregate(ast: &Ast, id: Id<TypeNode>) -> bool {
matches!(
ast.type_(id).map(|node| &node.kind),
Ok(TypeKind::Structure { .. } | TypeKind::Valuetype { .. } | TypeKind::Union { .. })
)
}
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 { .. } => {
out.extend(super::c::struct_base(ast, id));
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 declare(
ast: &Ast,
applied: &Applied,
id: Id<TypeNode>,
out: &mut Vec<ada::Decl>,
emitted: &mut Vec<Declared>,
cycle: &mut Cycle,
) -> Result<(), MaterialiseError> {
let before = out.len() + cycle.deferred.len();
declare_type(ast, applied, id, out, emitted, cycle)?;
if out.len() + cycle.deferred.len() > before && !cycle.completed.contains(&id) {
cycle.completed.push(id);
}
Ok(())
}
#[allow(clippy::too_many_lines)]
fn declare_type(
ast: &Ast,
applied: &Applied,
id: Id<TypeNode>,
out: &mut Vec<ada::Decl>,
emitted: &mut Vec<Declared>,
cycle: &mut Cycle,
) -> Result<(), MaterialiseError> {
let Ok(node) = ast.type_(id) else {
return Ok(());
};
match &node.kind {
TypeKind::Primitive(_) | TypeKind::String { .. } | TypeKind::Fixed { .. } => {}
TypeKind::Sequence { element, .. } => {
let Some(element) = element.resolved() else {
return Err(MaterialiseError::Unevaluated {
construct: "sequence element".to_string(),
at: ada_type_name(ast, id),
});
};
let access = ada_type_name(ast, id);
if already(emitted, &access) {
cycle.completed.push(id);
return Ok(());
}
let array = format!("{access}{ARRAY_SUFFIX}");
remember(emitted, &array, &array, false)?;
remember(emitted, &access, &access, false)?;
let elements = ada::Decl::Type(ada::TypeDecl {
doc: Doc::lines(&[
"The elements of a sequence. `Natural range <>` leaves the length",
"out of the type, which is where the wire form keeps it. A",
"component's subtype must be definite (RM 3.8), so this type is",
"reached through the access type below.",
]),
name: Ident::new(&array),
def: ada::TypeDef::Array {
index: vec![ada::Index::Unconstrained(Path::single("Natural"))],
element: ada_member_path(ast, element, &access)?,
},
});
match pending(ast, element, cycle) {
Some(target) => {
predeclare(ast, target, out, cycle);
out.push(incomplete(
&array,
"The elements of a sequence whose element type is not declared \
yet. Completed below, after the element.",
));
cycle.deferred.push(elements);
}
None => out.push(elements),
}
out.push(ada::Decl::Type(ada::TypeDecl {
doc: Doc::line(
"A sequence. `null` is an empty one that was never allocated; the \
storage is the caller's.",
),
name: Ident::new(&access),
def: ada::TypeDef::Access(Path::single(&array)),
}));
}
TypeKind::Array { element, dims } => {
let Some(element) = element.resolved() else {
return Err(MaterialiseError::Unevaluated {
construct: "array element".to_string(),
at: ada_type_name(ast, id),
});
};
if let Some(target) = pending(ast, element, cycle) {
let name = ada_type_name(ast, target);
return Err(MaterialiseError::NoLanguageForm {
construct: format!(
"an array whose element `{name}` reaches the array itself — Ada \
2012 RM 3.10.1 does not admit an incomplete type as an array \
element, and the value would have no bound"
),
language: LANGUAGE,
at: ada_type_name(ast, id),
});
}
let tag = type_tag(ast, element);
let mut lengths = Vec::new();
for dim in dims {
lengths.push(ast.eval_integral(*dim).map_err(|_| {
MaterialiseError::Unevaluated {
construct: "array length".to_string(),
at: ada_type_name(ast, id),
}
})?);
}
let mut current = ada_member_path(ast, element, &tag)?;
for index in (0..lengths.len()).rev() {
let name = array_type_name(&tag, &lengths[index..]);
if !already(emitted, &name) {
remember(emitted, &name, &name, false)?;
out.push(ada::Decl::Type(ada::TypeDecl {
doc: Doc::line(
"One dimension of an array. The element count is in the type \
and not on the wire.",
),
name: Ident::new(&name),
def: ada::TypeDef::Array {
index: vec![ada::Index::Range {
low: ada::Expr::int(0),
high: ada::Expr::int(lengths[index] - 1),
}],
element: current,
},
}));
}
current = Path::single(&name);
}
}
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: ada_type_name(ast, id),
});
};
let access = ada_type_name(ast, id);
if already(emitted, &access) {
cycle.completed.push(id);
return Ok(());
}
let entry = ada_map_entry_name(ast, id);
let array = format!("{access}{ARRAY_SUFFIX}");
remember(emitted, &entry, &entry, false)?;
remember(emitted, &array, &array, false)?;
remember(emitted, &access, &access, false)?;
let pair = ada::Decl::Type(ada::TypeDecl {
doc: Doc::line(
"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(&entry),
def: ada::TypeDef::Record(ada::Record::plain(vec![
ada::Component {
doc: Doc::none(),
name: Ident::new("Key"),
type_: ada_member_path(ast, key, &entry)?,
},
ada::Component {
doc: Doc::none(),
name: Ident::new(VALUE),
type_: ada_member_path(ast, value, &entry)?,
},
])),
});
let entries = ada::Decl::Type(ada::TypeDecl {
doc: Doc::line("The entries of a map, in the order the wire carries them."),
name: Ident::new(&array),
def: ada::TypeDef::Array {
index: vec![ada::Index::Unconstrained(Path::single("Natural"))],
element: Path::single(&entry),
},
});
let waiting: Vec<Id<TypeNode>> = [key, value]
.into_iter()
.filter_map(|target| pending(ast, target, cycle))
.collect();
if waiting.is_empty() {
out.push(pair);
out.push(entries);
} else {
for target in waiting {
predeclare(ast, target, out, cycle);
}
out.push(incomplete(
&entry,
"One entry of a map whose key or value is not declared yet. \
Completed below.",
));
out.push(incomplete(
&array,
"The entries of a map whose entry record is not complete yet. \
Completed below.",
));
cycle.deferred.push(pair);
cycle.deferred.push(entries);
}
out.push(ada::Decl::Type(ada::TypeDecl {
doc: Doc::line("A map, held as the ordered sequence of pairs it is on the wire."),
name: Ident::new(&access),
def: ada::TypeDef::Access(Path::single(&array)),
}));
}
TypeKind::Alias { aliased } => {
if node.name.is_empty() {
return Ok(());
}
if ghost_created_for(applied, node.node_id, "AdaSubtype").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 = ada_type_name(ast, id);
remember(emitted, &name, &node.name, false)?;
let decl = ada::Decl::Subtype(ada::SubtypeDecl {
doc: idl_note(&name, &node.name),
name: Ident::new(&name),
mark: ada_member_path(ast, target, &node.name)?,
});
if pending(ast, target, cycle).is_some() {
cycle.deferred.push(decl);
} else {
out.push(decl);
}
}
TypeKind::Enumeration { enumerators } => {
if ghost_created_for(applied, node.node_id, "AdaEnum").is_none() {
return Ok(());
}
let name = ada_type_name(ast, id);
let mut literals = Vec::new();
let mut codes = Vec::new();
let mut pairs = Vec::new();
for enumerator in enumerators {
let Ok(enum_node) = ast.enumerator(*enumerator) else {
continue;
};
if ghost_created_for(applied, enum_node.node_id, "AdaLiteral").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 spelled = component_name(&enum_node.name);
pairs.push((spelled.clone(), enum_node.name.clone()));
literals.push(Ident::new(&spelled));
codes.push((Ident::new(&spelled), value));
}
if literals.is_empty() {
return Err(MaterialiseError::NoLanguageForm {
construct: format!(
"`{name}`, an enumeration with no enumerators — Ada 2012 RM \
3.5.1 requires at least one literal"
),
language: LANGUAGE,
at: name,
});
}
for pair in codes.windows(2) {
if pair[0].1 >= pair[1].1 {
return Err(MaterialiseError::NoLanguageForm {
construct: format!(
"`{name}`, an enumeration whose @value codes do not ascend \
({} => {}, then {} => {}) — Ada 2012 RM 13.4 requires an \
enumeration representation clause to follow the type's own \
ordering, and without the clause the wire numbering is lost",
pair[0].0.as_str(),
pair[0].1,
pair[1].0.as_str(),
pair[1].1
),
language: LANGUAGE,
at: name,
});
}
}
no_folded_collision(&pairs, &name)?;
remember(emitted, &name, &node.name, false)?;
for (spelled, declared) in &pairs {
remember(emitted, spelled, declared, true)?;
}
out.push(ada::Decl::Type(ada::TypeDecl {
doc: with_idl_note(
Doc::lines(&[
"An enumeration. The literals carry no codes here: RM 3.5.1",
"numbers them 0, 1, 2 and so on in declaration order, so the clause",
"below is what states the wire numbering.",
]),
&name,
&node.name,
),
name: Ident::new(&name),
def: ada::TypeDef::Enumeration(literals),
}));
out.push(ada::Decl::EnumRep(ada::EnumRep {
doc: Doc::lines(&[
"The codes DDS-XTypes 1.3 7.4.3.5.3 puts on the wire, as the",
"representation clause of RM 13.4. Without it an @value gap would be",
"silently renumbered to the literal's position.",
]),
type_: Ident::new(&name),
values: codes
.into_iter()
.map(|(literal, value)| (literal, ada::Expr::int(value)))
.collect(),
}));
}
TypeKind::Bitmask { bits } => {
if ghost_created_for(applied, node.node_id, "AdaBitmask").is_none() {
return Ok(());
}
let name = ada_type_name(ast, id);
remember(emitted, &name, &node.name, false)?;
let bound = ast.effective_bit_bound(id);
out.push(ada::Decl::Type(ada::TypeDecl {
doc: with_idl_note(
Doc::line(
"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),
def: ada::TypeDef::Derived(ada_holder(bound).path()),
}));
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, "AdaBitConst").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(),
}
})?;
if position >= bound {
return Err(MaterialiseError::NoLanguageForm {
construct: format!(
"`{}`, a bit at position {position} of a bitmask bounded at \
{bound} — the constant has no value in the holder type",
bit_node.name
),
language: LANGUAGE,
at: name,
});
}
let spelled = format!("{prefix}{}", component_name(&bit_node.name));
remember(emitted, &spelled, &bit_node.name, false)?;
out.push(ada::Decl::Object(ada::ObjectDecl {
doc: idl_note(&spelled, &bit_node.name),
name: Ident::new(&spelled),
type_: Path::single(&name),
value: ada::Expr::int(1_i128 << u32::from(position)),
}));
}
}
TypeKind::Bitset { .. } => {
if ghost_created_for(applied, node.node_id, "AdaBitset").is_none() {
return Ok(());
}
let name = ada_type_name(ast, id);
remember(emitted, &name, &node.name, false)?;
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((component_name(field_ident), field_ident.clone()));
}
no_folded_collision(&pairs, &name)?;
let mut components = Vec::new();
let mut index = 0;
for field in &ast.all_bitfields_of(id) {
let Ok(field_node) = ast.bitfield(*field) else {
continue;
};
if field_node.name.is_none() {
continue;
}
components.push(ada::Component {
doc: Doc::none(),
name: Ident::new(&pairs[index].0),
type_: ada_holder(field_node.bits).path(),
});
index += 1;
}
out.push(ada::Decl::Type(ada::TypeDecl {
doc: with_idl_note(
Doc::lines(&[
"A bitset. The fields are separate integers here and not an Ada",
"record representation clause: the order on the wire is the",
"declaration's, and the packing into the anchor is the codec's,",
"where it can be seen.",
]),
&name,
&node.name,
),
name: Ident::new(&name),
def: ada::TypeDef::Record(ada::Record::plain(components)),
}));
}
TypeKind::Structure { .. } | TypeKind::Valuetype { .. } => {
let created = if matches!(node.kind, TypeKind::Valuetype { .. }) {
"AdaValue"
} else {
"AdaRecord"
};
if ghost_created_for(applied, node.node_id, created).is_none() {
return Ok(());
}
let name = ada_type_name(ast, id);
remember(emitted, &name, &node.name, false)?;
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((component_name("parent"), "parent".to_string()));
}
for member in &members {
if let Ok(member_node) = ast.member(*member) {
pairs.push((component_name(&member_node.name), member_node.name.clone()));
}
}
no_folded_collision(&pairs, &name)?;
let mut components = Vec::new();
if let Some(base) = base {
components.push(ada::Component {
doc: Doc::line(
"The base structure, the first member (XTypes 1.3 7.5.1.4.1.3).",
),
name: Ident::new(&component_name("parent")),
type_: ada_member_path(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 external = member_node
.facets
.xtypes
.as_ref()
.is_some_and(|xtypes| xtypes.is_external);
let optional = member_node.facets.is_optional();
let mut type_ = ada_member_path(ast, target, &member_node.name)?;
let mut doc = Doc::none();
match pending(ast, target, cycle) {
Some(waiting) if optional || external => {
type_ = Path::single(&reference_type(ast, waiting, out, emitted, cycle)?);
doc = Doc::lines(&[
"Held by reference, because the type reaches this one back",
"and an Ada record cannot contain itself. `null` is the",
"absent state of an @optional member (DDS-XTypes 1.3",
"7.2.2.4.4.4.7) and the indirection is what @external asks",
"for (Table 21); RM 3.10.1 admits an incomplete type only",
"as the designated subtype of an access type, so this is",
"also the only spelling available here.",
]);
}
Some(waiting) => {
return Err(MaterialiseError::NoLanguageForm {
construct: format!(
"a member of `{}`, which reaches `{name}` back with no \
@optional, no @external and no collection between them \
— the value would have no bound, and RM 3.10.1 does \
not admit an incomplete type as a record component",
ada_type_name(ast, waiting)
),
language: LANGUAGE,
at: member_node.name.clone(),
});
}
None if optional => {
type_ = Path::single(&optional_type(
ast,
target,
&member_node.name,
out,
emitted,
)?);
doc = Doc::lines(&[
"Optional. `Present` is False when the member is absent:",
"DDS-XTypes 1.3 7.2.2.4.4.4.7. In a @mutable type an absent",
"member is not written at all; outside one its absence is",
"written.",
]);
}
None => {}
}
let spelled = component_name(&member_node.name);
components.push(ada::Component {
doc: with_idl_note(doc, &spelled, &member_node.name),
name: Ident::new(&spelled),
type_,
});
}
out.push(ada::Decl::Type(ada::TypeDecl {
doc: idl_note(&name, &node.name),
name: Ident::new(&name),
def: ada::TypeDef::Record(ada::Record::plain(components)),
}));
}
TypeKind::Union { discriminator, .. } => {
if ghost_created_for(applied, node.node_id, "AdaUnion").is_none() {
return Ok(());
}
let name = ada_type_name(ast, id);
remember(emitted, &name, &node.name, false)?;
let Some(disc) = discriminator.resolved() else {
return Err(MaterialiseError::Unevaluated {
construct: "union discriminator".to_string(),
at: name,
});
};
out.push(union_decl(
ast, applied, id, disc, &name, &node.name, cycle,
)?);
}
TypeKind::Native => {
if ghost_created_for(applied, node.node_id, "AdaNative").is_none() {
return Ok(());
}
let name = ada_type_name(ast, id);
remember(emitted, &name, &node.name, false)?;
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())
.filter(|spelling| is_ada_name(spelling));
out.push(ada::Decl::Type(ada::TypeDecl {
doc: with_idl_note(
Doc::line(
"A native type. `System.Address` unless a facet named a mapping \
that is an Ada subtype mark.",
),
&name,
&node.name,
),
name: Ident::new(&name),
def: ada::TypeDef::Derived(match mapping {
Some(spelling) => Path::new(&spelling.split('.').collect::<Vec<&str>>()),
None => ada::Primitive::Address.path(),
}),
}));
}
TypeKind::ValueBox { boxed } => {
let Some(target) = boxed.resolved() else {
return Err(MaterialiseError::Unevaluated {
construct: "boxed value target".to_string(),
at: node.name.clone(),
});
};
let name = ada_type_name(ast, id);
if already(emitted, &name) {
cycle.completed.push(id);
return Ok(());
}
remember(emitted, &name, &node.name, false)?;
if let Some(waiting) = pending(ast, target, cycle) {
predeclare(ast, waiting, out, cycle);
}
out.push(ada::Decl::Type(ada::TypeDecl {
doc: with_idl_note(
Doc::line("A boxed value: an access type, `null` when absent."),
&name,
&node.name,
),
name: Ident::new(&name),
def: ada::TypeDef::Access(ada_member_path(ast, target, &name)?),
}));
}
TypeKind::ObjectRef { .. }
| TypeKind::AnnotationType { .. }
| TypeKind::Any
| TypeKind::ValueBase => {}
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn union_decl(
ast: &Ast,
applied: &Applied,
id: Id<TypeNode>,
disc: Id<TypeNode>,
name: &str,
declared: &str,
cycle: &Cycle,
) -> Result<ada::Decl, MaterialiseError> {
let underlying = strip_aliases(ast, disc);
let discrete = matches!(
ast.type_(underlying).map(|node| &node.kind),
Ok(TypeKind::Enumeration { .. })
| Ok(TypeKind::Primitive(
PrimitiveKind::Boolean
| PrimitiveKind::Char
| PrimitiveKind::WChar
| PrimitiveKind::Octet
| PrimitiveKind::Int8
| PrimitiveKind::UInt8
| PrimitiveKind::Int16
| PrimitiveKind::UInt16
| PrimitiveKind::Int32
| PrimitiveKind::UInt32
| PrimitiveKind::Int64
| PrimitiveKind::UInt64
))
);
if !discrete {
return Err(MaterialiseError::NoLanguageForm {
construct: format!(
"`{name}`, a union whose discriminator is not a discrete type — Ada \
2012 RM 3.7 admits only a discrete or an access type as a discriminant"
),
language: LANGUAGE,
at: name.to_string(),
});
}
let cases = ast.cases_of(id);
let mut pairs = vec![(DISCRIMINANT.to_string(), "<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((component_name(&member.name), member.name.clone()));
}
}
}
no_folded_collision(&pairs, name)?;
let mut arms: Vec<ada::Arm> = Vec::new();
let mut default: Option<ada::Arm> = None;
let mut covered: Vec<i128> = Vec::new();
for case in cases {
let Ok(case_node) = ast.case(*case) else {
continue;
};
if ghost_created_for(applied, case_node.node_id, "AdaCase").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(),
});
};
if let Some(waiting) = pending(ast, target, cycle) {
return Err(MaterialiseError::NoLanguageForm {
construct: format!(
"a union branch of `{}`, which reaches `{name}` back with no \
collection between them — the value would have no bound",
ada_type_name(ast, waiting)
),
language: LANGUAGE,
at: member.name.clone(),
});
}
let spelled = component_name(&member.name);
let component = ada::Component {
doc: idl_note(&spelled, &member.name),
name: Ident::new(&spelled),
type_: ada_member_path(ast, target, &member.name)?,
};
let mut choices = Vec::new();
let mut is_default = false;
for label in &case_node.labels {
match label {
CaseLabel::Default => is_default = true,
CaseLabel::Value(expr) => {
let value =
ast.eval_integral(*expr)
.map_err(|_| MaterialiseError::Unevaluated {
construct: "union case label".to_string(),
at: member.name.clone(),
})?;
if covered.contains(&value) {
return Err(MaterialiseError::NoLanguageForm {
construct: format!(
"`{name}`, a union with two branches selected by {value} \
— Ada 2012 RM 3.8.1 requires the choices of a variant \
part to be distinct"
),
language: LANGUAGE,
at: member.name.clone(),
});
}
covered.push(value);
choices.push(ada::Choice::Value(choice_expr(
ast, underlying, value, name,
)?));
}
}
}
if is_default {
default = Some(ada::Arm {
choices: vec![ada::Choice::Others],
components: vec![component],
});
} else if choices.is_empty() {
continue;
} else {
arms.push(ada::Arm {
choices,
components: vec![component],
});
}
}
if let Some(arm) = default {
arms.push(arm);
} else if !covers_everything(ast, underlying, &covered) {
arms.push(ada::Arm {
choices: vec![ada::Choice::Others],
components: Vec::new(),
});
}
Ok(ada::Decl::Type(ada::TypeDecl {
doc: with_idl_note(
Doc::lines(&[
"A union, as the discriminated record of RM 3.8.1. `D` is part of the",
"value and not a convention beside it, so reading a branch the",
"discriminant does not select raises Constraint_Error rather than",
"returning whatever was there. Only the selected branch is written to",
"the wire (DDS-XTypes 1.3 7.4.1.1.5.2).",
]),
name,
declared,
),
name: Ident::new(name),
def: ada::TypeDef::Record(ada::Record {
body_text: zerodds_lang_cst::common::BodyText::none(),
discriminants: vec![ada::Discriminant {
name: Ident::new(DISCRIMINANT),
type_: ada_member_path(ast, disc, name)?,
default: Some(discriminant_default(ast, underlying, name)?),
}],
components: Vec::new(),
variant: Some(ada::Variant {
discriminant: Ident::new(DISCRIMINANT),
arms,
}),
}),
}))
}
fn choice_expr(
ast: &Ast,
underlying: Id<TypeNode>,
value: i128,
at: &str,
) -> Result<ada::Expr, MaterialiseError> {
match ast.type_(underlying).map(|node| node.kind.clone()) {
Ok(TypeKind::Enumeration { enumerators }) => {
for enumerator in &enumerators {
let Ok(enum_node) = ast.enumerator(*enumerator) else {
continue;
};
if ast.effective_enumerator_value(*enumerator) == Some(value) {
return Ok(ada::Expr::name(&component_name(&enum_node.name)));
}
}
Err(MaterialiseError::NoLanguageForm {
construct: format!(
"a union case label of {value}, which names no enumerator of the \
discriminator — an Ada variant choice has to be a value of the \
discriminant's subtype"
),
language: LANGUAGE,
at: at.to_string(),
})
}
Ok(TypeKind::Primitive(PrimitiveKind::Boolean)) => {
Ok(ada::Expr::Literal(Literal::Bool(value != 0)))
}
_ => Ok(ada::Expr::int(value)),
}
}
fn covers_everything(ast: &Ast, underlying: Id<TypeNode>, covered: &[i128]) -> bool {
match ast.type_(underlying).map(|node| node.kind.clone()) {
Ok(TypeKind::Enumeration { enumerators }) => enumerators.iter().all(|enumerator| {
ast.effective_enumerator_value(*enumerator)
.is_some_and(|value| covered.contains(&value))
}),
Ok(TypeKind::Primitive(PrimitiveKind::Boolean)) => {
covered.contains(&0) && covered.contains(&1)
}
_ => false,
}
}
fn discriminant_default(
ast: &Ast,
underlying: Id<TypeNode>,
at: &str,
) -> Result<ada::Expr, MaterialiseError> {
match ast.type_(underlying).map(|node| node.kind.clone()) {
Ok(TypeKind::Enumeration { enumerators }) => {
for enumerator in &enumerators {
if let Ok(enum_node) = ast.enumerator(*enumerator) {
return Ok(ada::Expr::name(&component_name(&enum_node.name)));
}
}
Err(MaterialiseError::NoLanguageForm {
construct: "a union on an enumeration with no enumerators, which has no \
value its discriminant could default to"
.to_string(),
language: LANGUAGE,
at: at.to_string(),
})
}
Ok(TypeKind::Primitive(PrimitiveKind::Boolean)) => {
Ok(ada::Expr::Literal(Literal::Bool(false)))
}
_ => Ok(ada::Expr::int(0)),
}
}
fn optional_type(
ast: &Ast,
target: Id<TypeNode>,
at: &str,
out: &mut Vec<ada::Decl>,
emitted: &mut Vec<Declared>,
) -> Result<String, MaterialiseError> {
let name = format!("{OPT_PREFIX}{}", type_tag(ast, target));
if already(emitted, &name) {
return Ok(name);
}
remember(emitted, &name, &name, false)?;
out.push(ada::Decl::Type(ada::TypeDecl {
doc: Doc::lines(&[
"An optional value (DDS-XTypes 1.3 7.2.2.4.4.4.7). The absent state has",
"no component at all, so reading `Value` when `Present` is False raises",
"Constraint_Error (RM 3.8.1) rather than returning whatever was there,",
"which is what a flag beside the value would do.",
]),
name: Ident::new(&name),
def: ada::TypeDef::Record(ada::Record {
body_text: zerodds_lang_cst::common::BodyText::none(),
discriminants: vec![ada::Discriminant {
name: Ident::new(PRESENT),
type_: ada::Primitive::Boolean.path(),
default: Some(ada::Expr::Literal(Literal::Bool(false))),
}],
components: Vec::new(),
variant: Some(ada::Variant {
discriminant: Ident::new(PRESENT),
arms: vec![
ada::Arm {
choices: vec![ada::Choice::Value(ada::Expr::Literal(Literal::Bool(true)))],
components: vec![ada::Component {
doc: Doc::none(),
name: Ident::new(VALUE),
type_: ada_member_path(ast, target, at)?,
}],
},
ada::Arm {
choices: vec![ada::Choice::Value(ada::Expr::Literal(Literal::Bool(false)))],
components: Vec::new(),
},
],
}),
}),
}));
Ok(name)
}
pub fn ada_member_path(
ast: &Ast,
target: Id<TypeNode>,
at: &str,
) -> Result<Path, 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(),
});
}
ada_value_path(ast, target)
}
pub fn ada_value_path(ast: &Ast, id: Id<TypeNode>) -> Result<Path, MaterialiseError> {
value_path_at(ast, id, 0)
}
fn value_path_at(ast: &Ast, id: Id<TypeNode>, depth: usize) -> Result<Path, MaterialiseError> {
if depth > MAX_DEPTH {
return Err(MaterialiseError::Unevaluated {
construct: format!("typedef chain, which nests past {MAX_DEPTH}"),
at: type_name_at(ast, id, 0),
});
}
let Ok(node) = ast.type_(id) else {
return Ok(ada::Primitive::Address.path());
};
Ok(match &node.kind {
TypeKind::Primitive(kind) => ada_primitive(*kind).path(),
TypeKind::String { .. } | TypeKind::Fixed { .. } => ada::Primitive::UnboundedString.path(),
TypeKind::Alias { aliased } if node.name.is_empty() => match aliased.resolved() {
Some(target) => value_path_at(ast, target, depth + 1)?,
None => ada::Primitive::Address.path(),
},
_ => Path::single(&type_name_at(ast, id, depth + 1)),
})
}
#[must_use]
pub const fn ada_primitive(kind: PrimitiveKind) -> ada::Primitive {
match kind {
PrimitiveKind::Boolean => ada::Primitive::Boolean,
PrimitiveKind::Octet | PrimitiveKind::UInt8 | PrimitiveKind::Char => {
ada::Primitive::Unsigned8
}
PrimitiveKind::Int8 => ada::Primitive::Integer8,
PrimitiveKind::WChar | PrimitiveKind::UInt16 => ada::Primitive::Unsigned16,
PrimitiveKind::Int16 => ada::Primitive::Integer16,
PrimitiveKind::Int32 => ada::Primitive::Integer32,
PrimitiveKind::UInt32 => ada::Primitive::Unsigned32,
PrimitiveKind::Int64 => ada::Primitive::Integer64,
PrimitiveKind::UInt64 => ada::Primitive::Unsigned64,
PrimitiveKind::Float32 => ada::Primitive::Float32,
PrimitiveKind::Float64 => ada::Primitive::Float64,
PrimitiveKind::Float128 => ada::Primitive::UnboundedString,
}
}
#[must_use]
pub const fn ada_holder(bits: u16) -> ada::Primitive {
match bits {
0..=8 => ada::Primitive::Unsigned8,
9..=16 => ada::Primitive::Unsigned16,
17..=32 => ada::Primitive::Unsigned32,
_ => ada::Primitive::Unsigned64,
}
}
impl verbatim::Carrier for ada::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::Type(node) = self else {
return false;
};
let ada::TypeDef::Record(record) = &mut node.def else {
return false;
};
if node.name.as_str() != name {
return false;
}
record.body_text.begin.append(begin);
record.body_text.end.append(end);
true
}
}
#[must_use]
pub fn ada_type_name(ast: &Ast, id: Id<TypeNode>) -> String {
type_name_at(ast, id, 0)
}
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 "Zd_Unnamed".to_string();
};
match &node.kind {
TypeKind::Primitive(kind) => ada_primitive(*kind).as_str().to_string(),
TypeKind::String { .. } | TypeKind::Fixed { .. } => {
ada::Primitive::UnboundedString.as_str().to_string()
}
TypeKind::Sequence { element, .. } => match element.resolved() {
Some(element) => format!("{SEQ_PREFIX}{}", type_tag_at(ast, element, depth + 1)),
None => format!("{SEQ_PREFIX}Unknown"),
},
TypeKind::Array { element, dims } => match element.resolved() {
Some(element) => {
let mut lengths = Vec::new();
for dim in dims {
match ast.eval_integral(*dim) {
Ok(length) => lengths.push(length),
Err(_) => {
return format!(
"{ARR_PREFIX}{}_X",
type_tag_at(ast, element, depth + 1)
);
}
}
}
array_type_name(&type_tag_at(ast, element, depth + 1), &lengths)
}
None => format!("{ARR_PREFIX}Unknown"),
},
TypeKind::Map { key, value, .. } => match (key.resolved(), value.resolved()) {
(Some(key), Some(value)) => format!(
"{MAP_PREFIX}{}_{}",
type_tag_at(ast, key, depth + 1),
type_tag_at(ast, value, depth + 1)
),
_ => format!("{MAP_PREFIX}Unknown"),
},
TypeKind::Alias { aliased } if node.name.is_empty() => match aliased.resolved() {
Some(target) => type_name_at(ast, target, depth + 1),
None => "Zd_Unnamed".to_string(),
},
_ => {
let Ok(any) = ast.any_of_type(id) else {
return "Zd_Unnamed".to_string();
};
match ast.scoped_name_of(any) {
Some(scoped) => flatten(scoped.segments()),
None => "Zd_Unnamed".to_string(),
}
}
}
}
fn array_type_name(tag: &str, lengths: &[i128]) -> String {
let mut name = format!("{ARR_PREFIX}{tag}");
for length in lengths {
name.push('_');
name.push_str(&length.to_string());
}
name
}
#[must_use]
pub fn ada_map_entry_name(ast: &Ast, id: Id<TypeNode>) -> String {
map_entry_name_at(ast, id, 0)
}
fn map_entry_name_at(ast: &Ast, id: Id<TypeNode>, depth: usize) -> String {
if depth > MAX_DEPTH {
return format!("{MAP_ENTRY_PREFIX}Deep_{}", id.slot());
}
let Ok(node) = ast.type_(id) else {
return format!("{MAP_ENTRY_PREFIX}Unknown");
};
let TypeKind::Map { key, value, .. } = &node.kind else {
return format!("{MAP_ENTRY_PREFIX}Unknown");
};
let key = key
.resolved()
.map_or_else(|| "Unknown".to_string(), |k| type_tag_at(ast, k, depth + 1));
let value = value
.resolved()
.map_or_else(|| "Unknown".to_string(), |v| type_tag_at(ast, v, depth + 1));
format!("{MAP_ENTRY_PREFIX}{key}_{value}")
}
#[must_use]
pub fn type_tag(ast: &Ast, id: Id<TypeNode>) -> String {
type_tag_at(ast, id, 0)
}
fn type_tag_at(ast: &Ast, id: Id<TypeNode>, depth: usize) -> String {
if depth > MAX_DEPTH {
return format!("Deep_{}", id.slot());
}
match ast.type_(id).map(|node| node.kind.clone()) {
Ok(TypeKind::Primitive(kind)) => ada::ada_case(&kind.as_str().replace(' ', "_")),
Ok(TypeKind::String { width, bound }) => format!(
"{}{}",
match width {
CharWidth::Char => "String",
CharWidth::WChar => "Wstring",
},
bound_tag(ast, bound)
),
Ok(TypeKind::Fixed { digits, scale }) => format!("Fixed_{digits}_{scale}"),
Ok(TypeKind::Sequence { element, bound }) => match element.resolved() {
Some(element) => format!(
"Seq_Of{}_{}",
bound_tag(ast, bound),
type_tag_at(ast, element, depth + 1)
),
None => "Seq_Of_Unknown".to_string(),
},
Ok(TypeKind::Array { element, dims }) => match element.resolved() {
Some(element) => {
let mut name = format!("Arr_Of_{}", 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 => "Arr_Of_Unknown".to_string(),
},
Ok(TypeKind::Map { bound, .. }) => {
{
let entry = map_entry_name_at(ast, id, depth + 1);
match bound_tag(ast, bound) {
bound if bound.is_empty() => entry,
bound => format!("Map{bound}_{entry}"),
}
}
}
_ => type_name_at(ast, id, depth + 1),
}
}
fn bound_tag(ast: &Ast, bound: Bound) -> String {
match bound.expr() {
None => String::new(),
Some(expr) => match ast.eval_integral(expr) {
Ok(length) => format!("_{length}"),
Err(_) => "_X".to_string(),
},
}
}
fn strip_aliases(ast: &Ast, id: Id<TypeNode>) -> Id<TypeNode> {
let mut current = id;
for _ in 0..MAX_DEPTH {
match ast.type_(current).map(|node| node.kind.clone()) {
Ok(TypeKind::Alias { aliased }) => match aliased.resolved() {
Some(target) => current = target,
None => return current,
},
_ => return current,
}
}
current
}
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 = flatten(&segments[..segments.len() - 1]);
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();
flatten_str(&borrowed)
}
fn flatten(segments: &[String]) -> String {
let borrowed: Vec<&str> = segments.iter().map(String::as_str).collect();
flatten_str(&borrowed)
}
fn flatten_str(segments: &[&str]) -> String {
segments
.iter()
.map(|segment| component_name(segment))
.collect::<Vec<String>>()
.join("_")
}
#[must_use]
pub fn component_name(name: &str) -> String {
ada::ada_case(&ada::legal_name(&ada::escape_name(name)))
}
fn is_ada_name(text: &str) -> bool {
if text.is_empty() {
return false;
}
text.split('.').all(|segment| {
!segment.is_empty()
&& segment.starts_with(|ch: char| ch.is_ascii_alphabetic())
&& segment
.chars()
.all(|ch| ch.is_ascii_alphanumeric() || ch == '_')
&& !segment.ends_with('_')
&& !segment.contains("__")
})
}
fn idl_note(spelled: &str, declared: &str) -> Doc {
if !moved(spelled, declared) {
return Doc::none();
}
Doc::line(&format!("IDL: `{declared}`."))
}
fn with_idl_note(mut doc: Doc, spelled: &str, declared: &str) -> Doc {
if moved(spelled, declared) {
doc.push(&format!("IDL: `{declared}`."));
}
doc
}
fn moved(spelled: &str, declared: &str) -> bool {
ada::fold(spelled) != ada::fold(declared)
}
fn already(emitted: &[Declared], spelled: &str) -> bool {
let key = ada::fold(spelled);
emitted.iter().any(|other| other.key == key)
}
fn remember(
emitted: &mut Vec<Declared>,
spelled: &str,
declared: &str,
overloadable: bool,
) -> Result<(), MaterialiseError> {
let key = ada::fold(spelled);
if let Some(first) = emitted
.iter()
.find(|other| other.key == key && !(overloadable && other.overloadable))
{
return Err(MaterialiseError::SpellingCollision {
first: first.declared.clone(),
second: declared.to_string(),
spelled: spelled.to_string(),
language: LANGUAGE,
at: "the generated package".to_string(),
});
}
emitted.push(Declared {
key,
declared: declared.to_string(),
overloadable,
});
Ok(())
}
fn no_folded_collision(pairs: &[(String, String)], at: &str) -> Result<(), MaterialiseError> {
let mut seen: Vec<(String, &str)> = Vec::new();
for (spelled, declared) in pairs {
let key = ada::fold(spelled);
if let Some((_, first)) = seen.iter().find(|(other, _)| *other == key) {
return Err(MaterialiseError::SpellingCollision {
first: (*first).to_string(),
second: declared.clone(),
spelled: spelled.clone(),
language: LANGUAGE,
at: at.to_string(),
});
}
seen.push((key, declared));
}
Ok(())
}
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 ada_constant(
value: &zerodds_ast::ConstValue,
type_: &Path,
at: &str,
) -> Result<ada::Expr, MaterialiseError> {
use zerodds_ast::ConstValue;
let refuse = |construct: &str| MaterialiseError::NoLanguageForm {
construct: construct.to_string(),
language: LANGUAGE,
at: at.to_string(),
};
let is_text = type_ == &ada::Primitive::UnboundedString.path();
let text = |value: &str| -> Result<ada::Expr, MaterialiseError> {
if !value.chars().all(is_graphic_ascii) {
return Err(MaterialiseError::NoLanguageForm {
construct: "a string constant holding a character an Ada string literal \
cannot carry — RM 2.6 admits only graphic characters, and \
GNAT reads a source file as Latin-1 unless it is told \
otherwise, so a non-ASCII literal would not be the value \
the IDL wrote"
.to_string(),
language: LANGUAGE,
at: at.to_string(),
});
}
Ok(ada::Expr::Call {
name: Path::new(&["Ada", "Strings", "Unbounded", "To_Unbounded_String"]),
args: vec![ada::Expr::Literal(Literal::Str(value.to_string()))],
})
};
Ok(match value {
ConstValue::Boolean(flag) => ada::Expr::Literal(Literal::Bool(*flag)),
ConstValue::Integer(number) => ada::Expr::int(*number),
ConstValue::Char(character) => ada::Expr::int(i128::from(u32::from(*character))),
ConstValue::WChar(point) => ada::Expr::int(i128::from(*point)),
ConstValue::Float { mantissa, exponent } => {
if is_text {
return Err(refuse("a `long double` constant, which has no Ada literal"));
}
ada::Expr::Literal(Literal::Float(ada_float_text(
mantissa,
exponent.as_deref(),
)))
}
ConstValue::Fixed { digits, scale } => text(&constant::fixed_decimal(digits, *scale))?,
ConstValue::String(value) => text(value)?,
ConstValue::WString(points) => text(
&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")),
})
}
fn is_graphic_ascii(ch: char) -> bool {
('\u{20}'..='\u{7e}').contains(&ch)
}
fn ada_float_text(mantissa: &str, exponent: Option<&str>) -> String {
let text = constant::float_text(mantissa, exponent);
if text.contains('.') {
return text;
}
match text.split_once(['e', 'E']) {
Some((head, tail)) => format!("{head}.0e{tail}"),
None => format!("{text}.0"),
}
}