use zerodds_ast::{Ast, CaseLabel, MemberNode, TypeKind};
use zerodds_ast_core::Id;
use zerodds_lang_cst::c;
use zerodds_lang_cst::common::{Doc, Ident, Literal};
use crate::adapter::c::{
c_type_name, c_value_type, pointer_annotated, pointer_member, value_is_pointer,
};
use crate::adapter::cst::MaterialiseError;
const MAX_DEPTH: usize = 64;
const PRELUDE: &str = "\
#ifndef ZD_LIFECYCLE_DEFINED
#define ZD_LIFECYCLE_DEFINED
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
/* XTypes 1.3 Table 45: a copy is shallow or deep. */
typedef enum zd_copy_depth {
ZD_COPY_DEEP = 0,
ZD_COPY_SHALLOW = 1
} zd_copy_depth;
/* XTypes 1.3 Table 45: what a copy does with a destination member that is
* not NULL. REUSE is the default of a deep copy. */
typedef enum zd_copy_action {
ZD_COPY_REUSE = 0,
ZD_COPY_REPLACE = 1,
ZD_COPY_RELEASE = 2
} zd_copy_action;
#endif";
#[derive(Clone)]
enum Kind {
External(Id<zerodds_ast::TypeNode>),
Borrowed(Id<zerodds_ast::TypeNode>),
Inline(Id<zerodds_ast::TypeNode>),
Plain(Id<zerodds_ast::TypeNode>),
}
struct Slot {
field: String,
kind: Kind,
labels: Vec<Option<i128>>,
}
fn resolved(ast: &Ast, id: Id<zerodds_ast::TypeNode>) -> Id<zerodds_ast::TypeNode> {
let mut current = id;
for _ in 0..MAX_DEPTH {
match ast.type_(current).map(|node| &node.kind) {
Ok(TypeKind::Alias { aliased }) => match aliased.resolved() {
Some(next) => current = next,
None => return current,
},
_ => return current,
}
}
current
}
fn array_shape(
ast: &Ast,
id: Id<zerodds_ast::TypeNode>,
) -> Option<(Vec<i128>, Id<zerodds_ast::TypeNode>)> {
let mut dims = Vec::new();
let mut current = resolved(ast, id);
let mut found = false;
for _ in 0..MAX_DEPTH {
let Ok(node) = ast.type_(current) else {
break;
};
let TypeKind::Array {
element,
dims: lengths,
} = &node.kind
else {
break;
};
found = true;
for length in lengths {
dims.push(ast.eval_integral(*length).ok()?);
}
current = resolved(ast, element.resolved()?);
}
found.then_some((dims, current))
}
fn needs(ast: &Ast, id: Id<zerodds_ast::TypeNode>) -> bool {
let mut seen: Vec<Id<zerodds_ast::TypeNode>> = Vec::new();
let mut pending = vec![id];
while let Some(id) = pending.pop() {
let id = resolved(ast, id);
if seen.contains(&id) {
continue;
}
seen.push(id);
let members: Vec<&MemberNode> = match ast.type_(id).map(|node| node.kind.clone()) {
Ok(TypeKind::Structure { .. } | TypeKind::Valuetype { .. }) => {
if let Some(base) = super::c::struct_base(ast, id) {
pending.push(base);
}
ast.members_of(id)
.iter()
.filter_map(|member| ast.member(*member).ok())
.collect()
}
Ok(TypeKind::Union { .. }) => ast
.cases_of(id)
.iter()
.filter_map(|case| ast.case(*case).ok())
.filter_map(|case| ast.member(case.member).ok())
.collect(),
_ => continue,
};
for member in members {
let Some(target) = member.type_.resolved() else {
continue;
};
let annotated = pointer_annotated(member);
if annotated {
return true;
}
if pointer_member(ast, id, target, annotated) {
continue;
}
pending.push(array_shape(ast, target).map_or(target, |(_, element)| element));
}
}
false
}
fn slot(ast: &Ast, enclosing: Id<zerodds_ast::TypeNode>, member: &MemberNode) -> Option<Slot> {
let target = member.type_.resolved()?;
let annotated = pointer_annotated(member);
let kind = if annotated && !value_is_pointer(ast, target) {
Kind::External(target)
} else if annotated {
Kind::Borrowed(target)
} else if pointer_member(ast, enclosing, target, annotated) {
Kind::Plain(target)
} else {
let element = array_shape(ast, target).map_or(target, |(_, element)| element);
if needs(ast, element) {
Kind::Inline(target)
} else {
Kind::Plain(target)
}
};
Some(Slot {
field: c::escape_keyword(&member.name),
kind,
labels: Vec::new(),
})
}
fn slots(ast: &Ast, id: Id<zerodds_ast::TypeNode>) -> Vec<Slot> {
match ast.type_(id).map(|node| node.kind.clone()) {
Ok(TypeKind::Structure { .. } | TypeKind::Valuetype { .. }) => {
let mut out = Vec::new();
if let Some(base) = super::c::struct_base(ast, id) {
out.push(Slot {
field: super::c::PARENT.to_string(),
kind: if needs(ast, base) {
Kind::Inline(base)
} else {
Kind::Plain(base)
},
labels: Vec::new(),
});
}
out.extend(
ast.members_of(id)
.iter()
.filter_map(|member| ast.member(*member).ok())
.filter_map(|member| slot(ast, id, member)),
);
out
}
Ok(TypeKind::Union { .. }) => {
let mut out = Vec::new();
for case in ast.cases_of(id) {
let Ok(case_node) = ast.case(*case) else {
continue;
};
let Ok(member) = ast.member(case_node.member) else {
continue;
};
let Some(mut slot) = slot(ast, id, member) else {
continue;
};
for label in &case_node.labels {
match label {
CaseLabel::Default => slot.labels.push(None),
CaseLabel::Value(expr) => {
if let Ok(value) = ast.eval_integral(*expr) {
slot.labels.push(Some(value));
}
}
}
}
out.push(slot);
}
out
}
_ => Vec::new(),
}
}
fn name(text: &str) -> c::Expr {
c::Expr::Name(Ident::new(text))
}
fn int(value: i128) -> c::Expr {
c::Expr::Literal(Literal::int(value))
}
fn arrow(value: c::Expr, field: &str) -> c::Expr {
c::Expr::Arrow {
value: Box::new(value),
name: Ident::new(field),
}
}
fn dot(value: c::Expr, field: &str) -> c::Expr {
c::Expr::Member {
value: Box::new(value),
name: Ident::new(field),
}
}
fn addr(value: c::Expr) -> c::Expr {
c::Expr::AddressOf(Box::new(value))
}
fn deref(value: c::Expr) -> c::Expr {
c::Expr::Paren(Box::new(c::Expr::Deref(Box::new(value))))
}
fn call(function: &str, args: Vec<c::Expr>) -> c::Expr {
c::Expr::Call {
callee: Box::new(name(function)),
args,
}
}
fn binary(op: &'static str, lhs: c::Expr, rhs: c::Expr) -> c::Expr {
c::Expr::Binary {
op,
lhs: Box::new(lhs),
rhs: Box::new(rhs),
}
}
fn assign(place: c::Expr, value: c::Expr) -> c::Statement {
c::Statement::Expr(binary("=", place, value))
}
fn eq(lhs: c::Expr, rhs: c::Expr) -> c::Expr {
binary("==", lhs, rhs)
}
fn ne(lhs: c::Expr, rhs: c::Expr) -> c::Expr {
binary("!=", lhs, rhs)
}
fn null() -> c::Expr {
name("NULL")
}
fn and(lhs: c::Expr, rhs: c::Expr) -> c::Expr {
binary("&&", lhs, rhs)
}
fn or(lhs: c::Expr, rhs: c::Expr) -> c::Expr {
binary("||", lhs, rhs)
}
fn not(value: c::Expr) -> c::Expr {
c::Expr::Unary {
op: "!",
value: Box::new(c::Expr::Paren(Box::new(value))),
}
}
fn block(statements: Vec<c::Statement>) -> c::Block {
c::Block { statements }
}
fn when(condition: c::Expr, then: Vec<c::Statement>) -> c::Statement {
c::Statement::If {
condition,
then: block(then),
otherwise: None,
}
}
fn either(
condition: c::Expr,
then: Vec<c::Statement>,
otherwise: Vec<c::Statement>,
) -> c::Statement {
c::Statement::If {
condition,
then: block(then),
otherwise: Some(block(otherwise)),
}
}
fn fail() -> c::Statement {
c::Statement::Return(Some(name("false")))
}
fn type_of(ast: &Ast, id: Id<zerodds_ast::TypeNode>) -> c::Type {
c_value_type(ast, id)
}
fn init_name(ast: &Ast, id: Id<zerodds_ast::TypeNode>) -> String {
format!("zd_init_{}", c_type_name(ast, id))
}
fn fini_name(ast: &Ast, id: Id<zerodds_ast::TypeNode>) -> String {
format!("zd_fini_{}", c_type_name(ast, id))
}
fn copy_name(ast: &Ast, id: Id<zerodds_ast::TypeNode>) -> String {
format!("zd_copy_{}", c_type_name(ast, id))
}
fn copy_with_name(ast: &Ast, id: Id<zerodds_ast::TypeNode>) -> String {
format!("zd_copy_with_{}", c_type_name(ast, id))
}
fn over_elements<F: Fn(c::Expr) -> Vec<c::Statement>>(
dims: &[i128],
place: c::Expr,
depth: usize,
each: &F,
) -> Vec<c::Statement> {
let Some((first, rest)) = dims.split_first() else {
return each(place);
};
let index = format!("zd_i{depth}");
let element = c::Expr::Index {
value: Box::new(place),
index: Box::new(name(&index)),
};
vec![c::Statement::For {
init: Box::new(c::Statement::Declare {
type_: c::Type::Primitive(c::Primitive::Size),
name: Ident::new(&index),
value: Some(int(0)),
}),
condition: binary("<", name(&index), int(*first)),
step: c::Expr::Unary {
op: "++",
value: Box::new(name(&index)),
},
body: block(over_elements(rest, element, depth + 1, each)),
}]
}
fn walk_value<F: Fn(Id<zerodds_ast::TypeNode>, c::Expr) -> Vec<c::Statement>>(
ast: &Ast,
ty: Id<zerodds_ast::TypeNode>,
place: c::Expr,
depth: usize,
per_aggregate: &F,
) -> Vec<c::Statement> {
if let Some((dims, element)) = array_shape(ast, ty) {
return over_elements(&dims, place, depth, &|element_place| {
per_aggregate(resolved(ast, element), element_place)
});
}
per_aggregate(resolved(ast, ty), place)
}
fn init_value(
ast: &Ast,
ty: Id<zerodds_ast::TypeNode>,
place: c::Expr,
depth: usize,
) -> Vec<c::Statement> {
walk_value(ast, ty, place, depth, &|aggregate, lvalue| {
if !needs(ast, aggregate) {
return Vec::new();
}
vec![c::Statement::Expr(call(
&init_name(ast, aggregate),
vec![addr(lvalue)],
))]
})
}
fn fini_value(
ast: &Ast,
ty: Id<zerodds_ast::TypeNode>,
place: c::Expr,
depth: usize,
) -> Vec<c::Statement> {
walk_value(ast, ty, place, depth, &|aggregate, lvalue| {
if !needs(ast, aggregate) {
return Vec::new();
}
vec![c::Statement::Expr(call(
&fini_name(ast, aggregate),
vec![addr(lvalue)],
))]
})
}
fn copy_aggregates(
ast: &Ast,
ty: Id<zerodds_ast::TypeNode>,
dst: c::Expr,
src: c::Expr,
depth: usize,
) -> Vec<c::Statement> {
if let Some((dims, element)) = array_shape(ast, ty) {
let element = resolved(ast, element);
return over_elements_pair(&dims, dst, src, depth, &|d, s| copy_one(ast, element, d, s));
}
copy_one(ast, resolved(ast, ty), dst, src)
}
fn copy_one(
ast: &Ast,
aggregate: Id<zerodds_ast::TypeNode>,
dst: c::Expr,
src: c::Expr,
) -> Vec<c::Statement> {
vec![when(
not(call(
©_with_name(ast, aggregate),
vec![addr(dst), addr(src), name("depth"), name("action")],
)),
vec![fail()],
)]
}
fn over_elements_pair<F: Fn(c::Expr, c::Expr) -> Vec<c::Statement>>(
dims: &[i128],
dst: c::Expr,
src: c::Expr,
depth: usize,
each: &F,
) -> Vec<c::Statement> {
let Some((first, rest)) = dims.split_first() else {
return each(dst, src);
};
let index = format!("zd_i{depth}");
let at = |value: c::Expr| c::Expr::Index {
value: Box::new(value),
index: Box::new(name(&index)),
};
vec![c::Statement::For {
init: Box::new(c::Statement::Declare {
type_: c::Type::Primitive(c::Primitive::Size),
name: Ident::new(&index),
value: Some(int(0)),
}),
condition: binary("<", name(&index), int(*first)),
step: c::Expr::Unary {
op: "++",
value: Box::new(name(&index)),
},
body: block(over_elements_pair(rest, at(dst), at(src), depth + 1, each)),
}]
}
fn copy_plain(
ast: &Ast,
ty: Id<zerodds_ast::TypeNode>,
dst: c::Expr,
src: c::Expr,
) -> c::Statement {
if array_shape(ast, ty).is_some() {
return c::Statement::Expr(call(
"memcpy",
vec![dst, src, c::Expr::SizeOf(Box::new(type_of(ast, ty)))],
));
}
assign(dst, src)
}
fn release(ast: &Ast, ty: Id<zerodds_ast::TypeNode>, pointer: c::Expr) -> Vec<c::Statement> {
let mut out = fini_value(ast, ty, deref(pointer.clone()), 0);
out.push(c::Statement::Expr(call("free", vec![pointer])));
out
}
fn copy_external(
ast: &Ast,
ty: Id<zerodds_ast::TypeNode>,
dst: c::Expr,
src: c::Expr,
) -> Vec<c::Statement> {
let held = type_of(ast, ty);
let pointer_type = c::Type::Pointer(Box::new(held.clone()));
let deep = {
let mut statements = vec![
when(
and(
ne(dst.clone(), null()),
eq(name("action"), name("ZD_COPY_RELEASE")),
),
{
let mut gone = release(ast, ty, dst.clone());
gone.push(assign(dst.clone(), null()));
gone
},
),
when(
and(
ne(dst.clone(), null()),
eq(name("action"), name("ZD_COPY_REPLACE")),
),
vec![assign(dst.clone(), null())],
),
];
let mut made = vec![
assign(
dst.clone(),
c::Expr::Cast {
type_: Box::new(pointer_type),
value: Box::new(call("malloc", vec![c::Expr::SizeOf(Box::new(held))])),
},
),
when(eq(dst.clone(), null()), vec![fail()]),
];
made.extend(init_value(ast, ty, deref(dst.clone()), 0));
statements.push(when(eq(dst.clone(), null()), made));
statements.extend(copy_value_through(
ast,
ty,
deref(dst.clone()),
deref(src.clone()),
));
statements
};
vec![either(
eq(src.clone(), null()),
vec![
when(
and(
ne(dst.clone(), null()),
ne(name("action"), name("ZD_COPY_REPLACE")),
),
release(ast, ty, dst.clone()),
),
assign(dst.clone(), null()),
],
vec![either(
eq(name("depth"), name("ZD_COPY_SHALLOW")),
vec![
when(
and(ne(dst.clone(), null()), ne(dst.clone(), src.clone())),
release(ast, ty, dst.clone()),
),
assign(dst.clone(), src.clone()),
],
deep,
)],
)]
}
fn copy_value_through(
ast: &Ast,
ty: Id<zerodds_ast::TypeNode>,
dst: c::Expr,
src: c::Expr,
) -> Vec<c::Statement> {
let element = array_shape(ast, ty).map_or(ty, |(_, element)| element);
if needs(ast, element) {
copy_aggregates(ast, ty, dst, src, 0)
} else {
vec![copy_plain(ast, ty, dst, src)]
}
}
fn function(
doc: Doc,
returns: c::Type,
name: &str,
params: Vec<c::Param>,
body: Vec<c::Statement>,
) -> c::Decl {
c::Decl::Function(c::Function {
doc,
is_static: true,
is_inline: false,
is_maybe_unused: true,
returns,
name: Ident::new(name),
params,
body: Some(block(body)),
})
}
fn prototype(returns: c::Type, name: &str, params: Vec<c::Param>) -> c::Decl {
c::Decl::Function(c::Function {
doc: Doc::none(),
is_static: true,
is_inline: false,
is_maybe_unused: true,
returns,
name: Ident::new(name),
params,
body: None,
})
}
fn param(type_: c::Type, name: &str) -> c::Param {
c::Param {
type_,
name: Some(Ident::new(name)),
}
}
fn pointer_to(ast: &Ast, id: Id<zerodds_ast::TypeNode>) -> c::Type {
c::Type::Pointer(Box::new(type_of(ast, id)))
}
fn const_pointer_to(ast: &Ast, id: Id<zerodds_ast::TypeNode>) -> c::Type {
c::Type::Pointer(Box::new(c::Type::Const(Box::new(type_of(ast, id)))))
}
fn is_union(ast: &Ast, id: Id<zerodds_ast::TypeNode>) -> bool {
matches!(
ast.type_(id).map(|node| &node.kind),
Ok(TypeKind::Union { .. })
)
}
fn discriminator_type(ast: &Ast, id: Id<zerodds_ast::TypeNode>) -> c::Type {
match ast.type_(id).map(|node| &node.kind) {
Ok(TypeKind::Union { discriminator, .. }) => discriminator
.resolved()
.map_or(c::Type::Primitive(c::Primitive::Int32), |disc| {
type_of(ast, disc)
}),
_ => c::Type::Primitive(c::Primitive::Int32),
}
}
fn selects(
ast: &Ast,
aggregate: Id<zerodds_ast::TypeNode>,
all: &[Slot],
slot: &Slot,
base: &c::Expr,
) -> c::Expr {
let selector = arrow(base.clone(), "_d");
let disc = discriminator_type(ast, aggregate);
let label = |value: i128| {
eq(
selector.clone(),
c::Expr::Cast {
type_: Box::new(disc.clone()),
value: Box::new(int(value)),
},
)
};
let mut condition: Option<c::Expr> = None;
let mut push = |expr: c::Expr| {
condition = Some(match condition.take() {
Some(previous) => or(previous, expr),
None => expr,
});
};
for entry in &slot.labels {
match entry {
Some(value) => push(label(*value)),
None => {
let mut none: Option<c::Expr> = None;
for other in all {
for explicit in other.labels.iter().flatten() {
none = Some(match none.take() {
Some(previous) => or(previous, label(*explicit)),
None => label(*explicit),
});
}
}
push(match none {
Some(any) => not(any),
None => int(1),
});
}
}
}
condition.unwrap_or_else(|| int(0))
}
fn place(aggregate_is_union: bool, base: &c::Expr, slot: &Slot) -> c::Expr {
if aggregate_is_union {
dot(arrow(base.clone(), "_u"), &slot.field)
} else {
arrow(base.clone(), &slot.field)
}
}
fn init_function(ast: &Ast, id: Id<zerodds_ast::TypeNode>) -> c::Decl {
let v = name("v");
let mut body = Vec::new();
if is_union(ast, id) {
body.push(c::Statement::Expr(call(
"memset",
vec![
addr(arrow(v.clone(), "_u")),
int(0),
c::Expr::SizeOf(Box::new(c::Type::UnionTag(Ident::new(&format!(
"{}_u",
c_type_name(ast, id)
))))),
],
)));
} else {
for slot in slots(ast, id) {
let member = place(false, &v, &slot);
match &slot.kind {
Kind::External(_) | Kind::Borrowed(_) => body.push(assign(member, null())),
Kind::Inline(ty) => body.extend(init_value(ast, *ty, member, 0)),
Kind::Plain(_) => {}
}
}
}
if body.is_empty() {
body.push(c::Statement::Expr(c::Expr::Cast {
type_: Box::new(c::Type::Void),
value: Box::new(v),
}));
}
function(
Doc::lines(&[
"Sets the external members to NULL and the optional members absent.",
"XTypes 1.3 7.5.1.2.3.1.",
]),
c::Type::Void,
&init_name(ast, id),
vec![param(pointer_to(ast, id), "v")],
body,
)
}
fn fini_function(ast: &Ast, id: Id<zerodds_ast::TypeNode>) -> c::Decl {
let v = name("v");
let union = is_union(ast, id);
let all = slots(ast, id);
let mut body = Vec::new();
for slot in &all {
let member = place(union, &v, slot);
let mut then = match &slot.kind {
Kind::External(ty) => {
vec![when(ne(member.clone(), null()), {
let mut gone = release(ast, *ty, member.clone());
gone.push(assign(member.clone(), null()));
gone
})]
}
Kind::Inline(ty) => fini_value(ast, *ty, member.clone(), 0),
Kind::Plain(_) | Kind::Borrowed(_) => continue,
};
if union {
then = vec![when(selects(ast, id, &all, slot, &v), then)];
}
body.extend(then);
}
if body.is_empty() {
body.push(c::Statement::Expr(c::Expr::Cast {
type_: Box::new(c::Type::Void),
value: Box::new(v),
}));
}
function(
Doc::lines(&[
"Deletes what the non-NULL external members reference, and sets them to NULL.",
"An application that does not want an object deleted sets its pointer to",
"NULL first. XTypes 1.3 7.5.1.2.3.1.",
]),
c::Type::Void,
&fini_name(ast, id),
vec![param(pointer_to(ast, id), "v")],
body,
)
}
fn copy_with_function(ast: &Ast, id: Id<zerodds_ast::TypeNode>) -> c::Decl {
let dst = name("dst");
let src = name("src");
let union = is_union(ast, id);
let all = slots(ast, id);
let mut body = Vec::new();
if union {
body.push(when(
ne(arrow(dst.clone(), "_d"), arrow(src.clone(), "_d")),
vec![
when(
ne(name("action"), name("ZD_COPY_REPLACE")),
vec![c::Statement::Expr(call(
&fini_name(ast, id),
vec![dst.clone()],
))],
),
c::Statement::Expr(call(
"memset",
vec![
addr(arrow(dst.clone(), "_u")),
int(0),
c::Expr::SizeOf(Box::new(c::Type::UnionTag(Ident::new(&format!(
"{}_u",
c_type_name(ast, id)
))))),
],
)),
],
));
body.push(assign(arrow(dst.clone(), "_d"), arrow(src.clone(), "_d")));
}
for slot in &all {
let dst_member = place(union, &dst, slot);
let src_member = place(union, &src, slot);
let mut statements = Vec::new();
match &slot.kind {
Kind::External(ty) => {
statements.extend(copy_external(ast, *ty, dst_member, src_member));
}
Kind::Inline(ty) => {
statements.extend(copy_aggregates(ast, *ty, dst_member, src_member, 0));
}
Kind::Plain(ty) | Kind::Borrowed(ty) => {
statements.push(copy_plain(ast, *ty, dst_member, src_member));
}
}
if union {
body.push(when(selects(ast, id, &all, slot, &src), statements));
} else {
body.extend(statements);
}
}
for unused in ["depth", "action"] {
body.push(c::Statement::Expr(c::Expr::Cast {
type_: Box::new(c::Type::Void),
value: Box::new(name(unused)),
}));
}
body.push(c::Statement::Return(Some(name("true"))));
function(
Doc::lines(&[
"Copies `src` into `dst`. XTypes 1.3 7.5.1.2.3.1 and Table 45: a deep copy",
"allocates a NULL destination member and fills a non-NULL one, as `action`",
"says (REUSE, REPLACE, RELEASE); a shallow copy points the destination at",
"the source's object. Answers false where an allocation fails.",
]),
c::Type::Primitive(c::Primitive::Bool),
©_with_name(ast, id),
vec![
param(pointer_to(ast, id), "dst"),
param(const_pointer_to(ast, id), "src"),
param(c::Type::Named(Ident::new("zd_copy_depth")), "depth"),
param(c::Type::Named(Ident::new("zd_copy_action")), "action"),
],
body,
)
}
fn copy_function(ast: &Ast, id: Id<zerodds_ast::TypeNode>) -> c::Decl {
function(
Doc::lines(&[
"The deep copy with the default action, which reuses a non-NULL destination.",
"XTypes 1.3 7.5.1.2.3.1.",
]),
c::Type::Primitive(c::Primitive::Bool),
©_name(ast, id),
vec![
param(pointer_to(ast, id), "dst"),
param(const_pointer_to(ast, id), "src"),
],
vec![c::Statement::Return(Some(call(
©_with_name(ast, id),
vec![
name("dst"),
name("src"),
name("ZD_COPY_DEEP"),
name("ZD_COPY_REUSE"),
],
)))],
)
}
fn prototypes(ast: &Ast, id: Id<zerodds_ast::TypeNode>) -> Vec<c::Decl> {
vec![
prototype(
c::Type::Void,
&init_name(ast, id),
vec![param(pointer_to(ast, id), "v")],
),
prototype(
c::Type::Void,
&fini_name(ast, id),
vec![param(pointer_to(ast, id), "v")],
),
prototype(
c::Type::Primitive(c::Primitive::Bool),
©_with_name(ast, id),
vec![
param(pointer_to(ast, id), "dst"),
param(const_pointer_to(ast, id), "src"),
param(c::Type::Named(Ident::new("zd_copy_depth")), "depth"),
param(c::Type::Named(Ident::new("zd_copy_action")), "action"),
],
),
]
}
fn check(ast: &Ast, id: Id<zerodds_ast::TypeNode>) -> Result<(), MaterialiseError> {
let name = c_type_name(ast, id);
let unevaluated = |construct: &str, at: &str| MaterialiseError::Unevaluated {
construct: construct.to_string(),
at: format!("{name}.{at}"),
};
let mut members: Vec<(Id<zerodds_ast::MemberNode>, Vec<CaseLabel>)> = Vec::new();
match ast.type_(id).map(|node| node.kind.clone()) {
Ok(TypeKind::Structure { .. } | TypeKind::Valuetype { .. }) => {
members.extend(ast.all_members_of(id).into_iter().map(|m| (m, Vec::new())));
}
Ok(TypeKind::Union { .. }) => {
for case in ast.cases_of(id) {
let case = ast
.case(*case)
.map_err(|_| unevaluated("a union case the model does not hold", ""))?;
members.push((case.member, case.labels.clone()));
}
}
_ => return Ok(()),
}
for (member, labels) in members {
let member = ast
.member(member)
.map_err(|_| unevaluated("a member the model does not hold", ""))?;
let target = member
.type_
.resolved()
.ok_or_else(|| unevaluated("member type", &member.name))?;
let mut current = resolved(ast, target);
for _ in 0..MAX_DEPTH {
let Ok(node) = ast.type_(current) else {
return Err(unevaluated("a type the model does not hold", &member.name));
};
let TypeKind::Array { element, dims } = &node.kind else {
break;
};
for dim in dims {
ast.eval_integral(*dim)
.map_err(|_| unevaluated("array dimension", &member.name))?;
}
let Some(next) = element.resolved() else {
return Err(unevaluated("array element type", &member.name));
};
current = resolved(ast, next);
}
for label in labels {
if let CaseLabel::Value(expr) = label {
ast.eval_integral(expr)
.map_err(|_| unevaluated("case label", &member.name))?;
}
}
}
Ok(())
}
pub fn lifecycle(
ast: &Ast,
order: &[Id<zerodds_ast::TypeNode>],
) -> Result<Vec<c::Decl>, MaterialiseError> {
for id in order {
check(ast, *id)?;
}
let eligible: Vec<Id<zerodds_ast::TypeNode>> = order
.iter()
.copied()
.filter(|id| {
matches!(
ast.type_(*id).map(|node| &node.kind),
Ok(TypeKind::Structure { .. }
| TypeKind::Valuetype { .. }
| TypeKind::Union { .. })
) && needs(ast, *id)
})
.collect();
if eligible.is_empty() {
return Ok(Vec::new());
}
let mut out = vec![c::Decl::Verbatim(PRELUDE.to_string())];
for id in &eligible {
out.extend(prototypes(ast, *id));
}
for id in &eligible {
out.push(init_function(ast, *id));
out.push(fini_function(ast, *id));
out.push(copy_with_function(ast, *id));
out.push(copy_function(ast, *id));
}
Ok(out)
}