use super::{CodecTarget, Constant, Handle, Handoff, Routine, Scalar};
use crate::adapter::ocaml::{
ocaml_enumerator_name, ocaml_map_entry_name, ocaml_type_name, ocaml_value_type,
};
use crate::encode::EncodeError;
use zerodds_ast::{Ast, CharWidth, MemberNode, PrimitiveKind, TypeKind, TypeNode};
use zerodds_ast_core::Id;
use zerodds_lang_cst::common::{Doc, Literal};
use zerodds_lang_cst::ocaml::{self, CodeExpr, CodecFn, CodecType, Stmt, ValueKind};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct OCaml;
const MAX_TYPE_DEPTH: usize = 64;
impl OCaml {
fn type_(text: &str, kind: ValueKind, default: &str) -> CodecType {
CodecType {
text: text.to_string(),
kind,
default: default.to_string(),
}
}
fn constant_text(text: &str, kind: ValueKind) -> CodeExpr {
CodeExpr::RuntimeConst {
text: text.to_string(),
kind,
}
}
fn policy(text: &str) -> CodeExpr {
CodeExpr::TextLit(text.to_string())
}
}
pub fn ocaml_type_text(node: &ocaml::Type) -> String {
match node {
ocaml::Type::Primitive(primitive) => primitive.as_str().to_string(),
ocaml::Type::Named(name) => name.as_str().to_string(),
ocaml::Type::Array(element) => format!("{} array", ocaml_type_text(element)),
ocaml::Type::Option(inner) => format!("{} option", ocaml_type_text(inner)),
ocaml::Type::Tuple(parts) => {
let rendered: Vec<String> = parts.iter().map(ocaml_type_text).collect();
format!("({})", rendered.join(" * "))
}
ocaml::Type::Qualified { module, name } => {
let mut out = String::new();
for segment in module {
out.push_str(segment.as_str());
out.push('.');
}
out.push_str(name.as_str());
out
}
}
}
pub fn kind_of(ast: &Ast, id: Id<TypeNode>, depth: usize) -> ValueKind {
if depth > MAX_TYPE_DEPTH {
return ValueKind::Aggregate;
}
let Ok(node) = ast.type_(id) else {
return ValueKind::Aggregate;
};
match &node.kind {
TypeKind::Primitive(kind) => match kind {
PrimitiveKind::Boolean => ValueKind::Bool,
PrimitiveKind::Octet
| PrimitiveKind::UInt8
| PrimitiveKind::Char
| PrimitiveKind::Int8
| PrimitiveKind::Int16
| PrimitiveKind::UInt16
| PrimitiveKind::WChar => ValueKind::Int,
PrimitiveKind::Int32 => ValueKind::Int32,
PrimitiveKind::UInt32 | PrimitiveKind::Int64 => ValueKind::Int64,
PrimitiveKind::UInt64 | PrimitiveKind::Float128 => ValueKind::Text,
PrimitiveKind::Float32 | PrimitiveKind::Float64 => ValueKind::Float,
},
TypeKind::String { .. } | TypeKind::Fixed { .. } => ValueKind::Text,
TypeKind::Enumeration { .. } => ValueKind::Enum(ocaml_type_name(ast, id)),
TypeKind::Bitmask { .. } => {
if ast.effective_bit_bound(id) <= 32 {
ValueKind::Int32
} else {
ValueKind::Int64
}
}
TypeKind::Alias { aliased } => aliased.resolved().map_or(ValueKind::Aggregate, |target| {
kind_of(ast, target, depth + 1)
}),
_ if depth > 8 => ValueKind::Aggregate,
TypeKind::Sequence { element, .. } => {
element.resolved().map_or(ValueKind::Aggregate, |target| {
ValueKind::ArrayOf(Box::new(kind_of(ast, target, depth + 1)))
})
}
TypeKind::Array { element, dims } => match element.resolved() {
Some(target) => {
let mut kind = kind_of(ast, target, depth + 1);
for _ in dims {
kind = ValueKind::ArrayOf(Box::new(kind));
}
kind
}
None => ValueKind::Aggregate,
},
TypeKind::Map { key, value, .. } => {
let half = |half: Option<Id<TypeNode>>| {
half.map_or(ValueKind::Aggregate, |target| {
kind_of(ast, target, depth + 1)
})
};
ValueKind::ArrayOf(Box::new(ValueKind::RecordOf(vec![
("key".to_string(), half(key.resolved())),
("value".to_string(), half(value.resolved())),
])))
}
TypeKind::Structure { .. } | TypeKind::Valuetype { .. } => {
let mut fields = Vec::new();
if let Some(base) = crate::adapter::c::struct_base(ast, id) {
fields.push((ocaml::lower_ident("parent"), kind_of(ast, base, depth + 1)));
}
for member in ast.members_of(id).iter().copied() {
let Ok(member_node) = ast.member(member) else {
continue;
};
let Some(target) = member_node.type_.resolved() else {
continue;
};
let mut kind = kind_of(ast, target, depth + 1);
if member_node.facets.is_optional() {
kind = ValueKind::OptionOf(Box::new(kind));
}
fields.push((ocaml::lower_ident(&member_node.name), kind));
}
ValueKind::RecordOf(fields)
}
TypeKind::Union { discriminator, .. } => {
let mut fields = Vec::new();
if let Some(disc) = discriminator.resolved() {
fields.push(("_d".to_string(), kind_of(ast, disc, depth + 1)));
}
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(target) = member.type_.resolved() else {
continue;
};
fields.push((
ocaml::lower_ident(&member.name),
ValueKind::OptionOf(Box::new(kind_of(ast, target, depth + 1))),
));
}
ValueKind::RecordOf(fields)
}
TypeKind::Bitset { .. } => {
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_name) = field_node.name.as_ref() else {
continue;
};
let kind = match crate::adapter::ocaml::ocaml_holder(field_node.bits) {
ocaml::Primitive::Int32 => ValueKind::Int32,
ocaml::Primitive::Int64 => ValueKind::Int64,
_ => ValueKind::Int,
};
fields.push((ocaml::lower_ident(field_name), kind));
}
ValueKind::RecordOf(fields)
}
_ => ValueKind::Aggregate,
}
}
pub fn default_of(ast: &Ast, id: Id<TypeNode>) -> String {
match kind_of(ast, id, 0) {
ValueKind::Bool => "false".to_string(),
ValueKind::Int => "0".to_string(),
ValueKind::Int32 => "0l".to_string(),
ValueKind::Int64 => "0L".to_string(),
ValueKind::Float => "0.0".to_string(),
ValueKind::Text => "\"\"".to_string(),
_ => format!("{} ()", crate::default_ocaml::defaulter_name(ast, id)),
}
}
impl CodecTarget for OCaml {
type Expr = CodeExpr;
type Stmt = Stmt;
type Block = Vec<Stmt>;
type Type = CodecType;
type Decl = CodecFn;
fn scalar(kind: Scalar) -> Self::Type {
match kind {
Scalar::Bool => Self::type_("bool", ValueKind::Bool, "false"),
Scalar::Char
| Scalar::Int8
| Scalar::UInt8
| Scalar::Int16
| Scalar::UInt16
| Scalar::UInt32
| Scalar::Index
| Scalar::Size => Self::type_("int", ValueKind::Int, "0"),
Scalar::Int32 => Self::type_("int32", ValueKind::Int32, "0l"),
Scalar::Int64 => Self::type_("int64", ValueKind::Int64, "0L"),
Scalar::UInt64 => Self::type_(ocaml::UNSIGNED_HOLDER, ValueKind::Int64, "0L"),
Scalar::Float | Scalar::Double => Self::type_("float", ValueKind::Float, "0.0"),
Scalar::LongDouble => Self::type_("string", ValueKind::Text, "\"0.0\""),
}
}
fn named(name: &str) -> Self::Type {
Self::type_(name, ValueKind::Aggregate, "zd_unsupported_default")
}
fn nothing() -> Self::Type {
Self::type_("unit", ValueKind::UnitValue, "()")
}
fn handle(kind: Handle) -> Self::Type {
match kind {
Handle::DHeader => Self::type_(
"Zdwire.dheader",
ValueKind::Handle("dheader"),
"Zdwire.no_dheader ()",
),
Handle::EmHeader => Self::type_(
"Zdwire.emheader",
ValueKind::Handle("emheader"),
"Zdwire.emheader_none",
),
Handle::Pid => Self::type_("Zdwire.pid", ValueKind::Handle("pid"), "Zdwire.pid_none"),
Handle::TryConstruct => Self::type_(
"Zdwire.try_construct",
ValueKind::Handle("try_construct"),
"Zdwire.Discard",
),
Handle::ReaderMark => Self::type_(
"Zdwire.reader_mark",
ValueKind::Handle("reader_mark"),
"Zdwire.mark_none",
),
Handle::PidRead => Self::type_(
"Zdwire.pid_read",
ValueKind::Handle("pid_read"),
"Zdwire.pid_read_none",
),
Handle::EmHeaderRead => Self::type_(
"Zdwire.emheader_read",
ValueKind::Handle("emheader_read"),
"Zdwire.emheader_read_none",
),
}
}
fn code_point(value: Self::Expr, _narrow: bool) -> Self::Expr {
value
}
fn constant(which: Constant) -> Self::Expr {
match which {
Constant::WcharWidth2 => CodeExpr::Int(2),
Constant::WcharWidth4 => CodeExpr::Int(4),
Constant::WcharFixed => Self::policy("fixed"),
Constant::WcharGiopLengthPrefixed => Self::policy("giop-length-prefixed"),
Constant::WstringUnitsStream => Self::policy("stream"),
Constant::WstringUnitsBigEndianAlways => Self::policy("big-endian-always"),
Constant::WstringUnitsByteOrderMark => Self::policy("byte-order-mark"),
Constant::WstringUnitsHostUnswapped => Self::policy("host-unswapped"),
Constant::WstringSpecification => Self::policy("specification"),
Constant::WstringCharacterCount => Self::policy("character-count"),
Constant::WstringCharacterCountTerminated => Self::policy("character-count-terminated"),
Constant::WstringSpecificationWideUnits => Self::policy("specification-wide-units"),
Constant::WstringRosTypeSupport => Self::policy("ros-type-support"),
Constant::WstringOctetsTerminated2 => Self::policy("octets/terminated/2"),
Constant::WstringOctetsTerminated4 => Self::policy("octets/terminated/4"),
Constant::WstringCharactersTerminated2 => Self::policy("characters/terminated/2"),
Constant::FixedDeclared => Self::policy("declared"),
Constant::FixedValuePrecision => Self::policy("value-precision"),
Constant::FixedValueStringDigits => Self::policy("value-string-digits"),
Constant::ParameterMember => {
Self::constant_text("Zdwire.Role_member", ValueKind::Handle("role"))
}
Constant::ParameterOptional => {
Self::constant_text("Zdwire.Role_optional", ValueKind::Handle("role"))
}
Constant::TryConstructDiscard => {
Self::constant_text("Zdwire.Discard", ValueKind::Handle("try_construct"))
}
Constant::TryConstructTrim => {
Self::constant_text("Zdwire.Trim", ValueKind::Handle("try_construct"))
}
Constant::TryConstructUseDefault => {
Self::constant_text("Zdwire.Use_default", ValueKind::Handle("try_construct"))
}
Constant::StatusOk => Self::constant_text("Zdwire.St_ok", ValueKind::Handle("status")),
Constant::StatusDiscarded => {
Self::constant_text("Zdwire.St_discarded", ValueKind::Handle("status"))
}
Constant::StatusImpossibleLength => {
Self::constant_text("Zdwire.St_impossible_length", ValueKind::Handle("status"))
}
Constant::StatusOutOfRange => {
Self::constant_text("Zdwire.St_out_of_range", ValueKind::Handle("status"))
}
Constant::StatusTruncated => {
Self::constant_text("Zdwire.St_truncated", ValueKind::Handle("status"))
}
}
}
fn unsigned_less(_octets: u8, left: Self::Expr, right: Self::Expr) -> Self::Expr {
CodeExpr::Static {
method: "u64_less".to_string(),
args: vec![left, right],
}
}
fn carries_hdds_axes() -> bool {
true
}
fn unsigned_holder(bits: u16) -> Self::Type {
match crate::adapter::ocaml::ocaml_holder(bits) {
ocaml::Primitive::Int32 => Self::type_("int32", ValueKind::Int32, "0l"),
ocaml::Primitive::Int64 => Self::type_("int64", ValueKind::Int64, "0L"),
_ => Self::type_("int", ValueKind::Int, "0"),
}
}
fn value_type(ast: &Ast, id: Id<TypeNode>) -> Self::Type {
let text = match ocaml_value_type(ast, id) {
Ok(type_) => ocaml_type_text(&type_),
Err(_) => "zd_no_ocaml_form".to_string(),
};
CodecType {
text,
kind: kind_of(ast, id, 0),
default: default_of(ast, id),
}
}
fn type_name(ast: &Ast, id: Id<TypeNode>) -> String {
type_tag(ast, id, 0)
}
fn map_entry_name(ast: &Ast, id: Id<TypeNode>) -> String {
ocaml_map_entry_name(ast, id)
}
fn map_entry_type(ast: &Ast, id: Id<TypeNode>) -> Self::Type {
let name = ocaml_map_entry_name(ast, id);
let default = match ast.type_(id).map(|node| node.kind.clone()) {
Ok(TypeKind::Map { key, value, .. }) => match (key.resolved(), value.resolved()) {
(Some(key), Some(value)) => format!(
"({{ key = {}; value = {} }} : {name})",
default_of(ast, key),
default_of(ast, value)
),
_ => "zd_unsupported_default".to_string(),
},
_ => "zd_unsupported_default".to_string(),
};
CodecType {
text: name,
kind: ValueKind::Aggregate,
default,
}
}
fn module_prefix(_ast: &Ast, _id: Id<TypeNode>) -> String {
String::new()
}
fn escape(name: &str) -> String {
ocaml::lower_ident(name)
}
fn inherited_path(depth: usize) -> Option<String> {
Some(format!("{}.", ocaml::lower_ident("parent")).repeat(depth))
}
fn name(text: &str) -> Self::Expr {
CodeExpr::Name(text.to_string())
}
fn int(value: i128) -> Self::Expr {
CodeExpr::Int(value)
}
fn wide_int(value: i128) -> Self::Expr {
CodeExpr::Int(value)
}
fn unsigned_int(value: i128) -> Self::Expr {
CodeExpr::Int(value)
}
#[allow(clippy::cast_possible_wrap)]
fn wide_unsigned(value: u64) -> Self::Expr {
CodeExpr::Int64Lit(value as i64)
}
#[allow(clippy::cast_possible_truncation)]
fn wide_signed(value: i128) -> Self::Expr {
CodeExpr::Int64Lit(value as i64)
}
fn literal(value: Literal) -> Self::Expr {
match value {
Literal::Int(text) => match text.as_str() {
"true" => CodeExpr::BoolLit(true),
"false" => CodeExpr::BoolLit(false),
other => other
.parse::<i128>()
.map_or_else(|_| CodeExpr::TextLit(other.to_string()), CodeExpr::Int),
},
Literal::Bool(flag) => CodeExpr::BoolLit(flag),
Literal::Str(text) => CodeExpr::TextLit(text),
Literal::Float(text) => CodeExpr::FloatLit(text),
Literal::Char(ch) => CodeExpr::Int(i128::from(u32::from(ch))),
Literal::Bytes(_) | Literal::Null => CodeExpr::Absent,
}
}
fn call(callee: &str, args: Vec<Self::Expr>) -> Self::Expr {
if callee == "strcmp" {
return CodeExpr::Static {
method: "strcmp".to_string(),
args,
};
}
CodeExpr::Call {
callee: callee.to_string(),
args,
}
}
fn runtime(receiver: &str, method: &str, args: Vec<Self::Expr>) -> Self::Expr {
if method == "read_pid" && args.is_empty() {
return Self::runtime(
receiver,
"read_pid_as",
vec![Self::constant(Constant::ParameterMember)],
);
}
let receiver = if receiver == "r" { "r" } else { "w" };
CodeExpr::Runtime {
receiver,
method: method.to_string(),
args,
}
}
fn runtime_static(method: &str, args: Vec<Self::Expr>) -> Self::Expr {
CodeExpr::Static {
method: method.to_string(),
args,
}
}
fn member(base: &str, field: &str) -> Self::Expr {
CodeExpr::Member {
base: base.to_string(),
field: field.to_string(),
case: false,
}
}
fn whole(base: &str) -> Self::Expr {
CodeExpr::Whole(base.to_string())
}
fn field_of(value: Self::Expr, field: &str) -> Self::Expr {
CodeExpr::HeaderField {
value: Box::new(value),
field: field.to_string(),
}
}
fn index(value: Self::Expr, at: Self::Expr) -> Self::Expr {
CodeExpr::Index {
base: Box::new(value),
index: Box::new(at),
}
}
fn count_of(base: &str, fixed: Option<u64>) -> Self::Expr {
match fixed {
Some(bound) => CodeExpr::Int(i128::from(bound)),
None => CodeExpr::CountOf(base.to_string()),
}
}
fn elements(base: &str) -> Self::Expr {
CodeExpr::Elements(base.to_string())
}
fn case_member(base: &str, field: &str) -> Self::Expr {
CodeExpr::Member {
base: base.to_string(),
field: field.to_string(),
case: true,
}
}
fn entry_half(entry: Self::Expr, key: bool) -> Self::Expr {
CodeExpr::EntryHalf {
entry: Box::new(entry),
key,
}
}
fn absent() -> Self::Expr {
CodeExpr::Absent
}
fn materialised(_value: Self::Expr) -> Option<Self::Expr> {
None
}
fn optional_value(_ast: &Ast, _id: Id<TypeNode>, base: &str, field: &str) -> Self::Expr {
CodeExpr::OptionGet(Box::new(Self::member(base, field)))
}
fn present(base: &str, field: &str) -> Self::Expr {
CodeExpr::IsSome {
base: base.to_string(),
field: field.to_string(),
}
}
fn set_present(base: &str, field: &str, present: bool) -> Vec<Self::Stmt> {
if present {
return Vec::new();
}
vec![Stmt::Assign {
place: Self::member(base, field),
value: CodeExpr::Absent,
}]
}
fn clear_union_cases(base: &str, fields: &[String]) -> Vec<Self::Stmt> {
fields
.iter()
.flat_map(|field| Self::set_present(base, field, false))
.collect()
}
fn optional_destination(base: &str, field: &str) -> (Vec<Self::Stmt>, Self::Expr) {
(
Vec::new(),
CodeExpr::OptionSlot {
base: base.to_string(),
field: field.to_string(),
},
)
}
fn binary(op: &'static str, lhs: Self::Expr, rhs: Self::Expr) -> Self::Expr {
CodeExpr::Binary {
op,
lhs: Box::new(lhs),
rhs: Box::new(rhs),
}
}
fn unary(op: &'static str, value: Self::Expr) -> Self::Expr {
CodeExpr::Unary {
op,
value: Box::new(value),
}
}
fn paren(value: Self::Expr) -> Self::Expr {
CodeExpr::Paren(Box::new(value))
}
fn cast(type_: Self::Type, value: Self::Expr) -> Self::Expr {
CodeExpr::Convert {
target: type_,
value: Box::new(value),
}
}
fn handoff(value: Self::Expr, _form: Handoff) -> Self::Expr {
value
}
fn declare(type_: Self::Type, name: &str, value: Option<Self::Expr>) -> Self::Stmt {
Stmt::Let {
type_,
name: name.to_string(),
value,
}
}
fn perform(value: Self::Expr) -> Self::Stmt {
if let CodeExpr::Binary { op: "=", lhs, rhs } = value {
return Stmt::Assign {
place: *lhs,
value: *rhs,
};
}
Stmt::Perform(value)
}
fn give_back(_value: Option<Self::Expr>) -> Self::Stmt {
Stmt::Return
}
fn when(
condition: Self::Expr,
then: Self::Block,
otherwise: Option<Self::Block>,
) -> Self::Stmt {
Stmt::When {
condition,
then,
otherwise: otherwise.filter(|body| !body.is_empty()),
}
}
fn count_up(counter: &str, bound: Self::Expr, body: Self::Block) -> Self::Stmt {
Stmt::For {
counter: counter.to_string(),
bound,
body,
}
}
fn until(condition: Self::Expr, body: Self::Block) -> Self::Stmt {
Stmt::While { condition, body }
}
fn leave() -> Self::Stmt {
Stmt::Leave
}
fn note(doc: Doc) -> Self::Stmt {
Stmt::Note(doc)
}
fn drop_value(value: Self::Expr) -> Self::Stmt {
Stmt::Discard(value)
}
fn mark_used(name: &str) -> Vec<Self::Stmt> {
vec![Stmt::Discard(CodeExpr::Name(name.to_string()))]
}
fn read_call(
callee: &str,
place: Self::Expr,
_form: Handoff,
behaviour: Self::Expr,
) -> Self::Stmt {
Stmt::ReadInto {
callee: callee.to_string(),
place,
behaviour,
}
}
fn read_forward(callee: &str) -> Self::Stmt {
Stmt::ReadInto {
callee: callee.to_string(),
place: CodeExpr::Whole("v".to_string()),
behaviour: CodeExpr::Name("zd_tc".to_string()),
}
}
fn block(statements: Vec<Self::Stmt>) -> Self::Block {
statements
}
fn into_statements(block: Self::Block) -> Vec<Self::Stmt> {
block
}
fn make_room(
base: &str,
element: Self::Type,
count: Self::Expr,
_assigned_whole: bool,
fill: Vec<Self::Stmt>,
) -> Vec<Self::Stmt> {
let mut out = vec![Stmt::Alloc {
base: base.to_string(),
element,
count,
}];
out.extend(fill);
out
}
fn keep_first(base: &str, count: Self::Expr) -> Vec<Self::Stmt> {
vec![Stmt::KeepFirst {
base: base.to_string(),
count,
}]
}
fn sequence_element(
base: &str,
index: Self::Expr,
_element: Self::Type,
) -> (Vec<Self::Stmt>, Self::Expr, Vec<Self::Stmt>) {
(
Vec::new(),
Self::index(Self::elements(base), index),
Vec::new(),
)
}
fn storage(type_: Self::Type, name: &str) -> Self::Stmt {
if type_.text.starts_with("zd_map_entry_") {
return Stmt::Scratch {
name: name.to_string(),
};
}
Stmt::Let {
type_,
name: name.to_string(),
value: None,
}
}
fn routine(spec: Routine<Self>) -> Option<Self::Decl> {
let body = spec.body?;
let defaulter = if spec.writing {
None
} else {
Some(crate::default_ocaml::defaulter_from_decoder(&spec.name))
};
Some(CodecFn {
doc: spec.doc,
name: spec.name,
sample: spec.sample,
reading: !spec.writing,
defaulter,
extra: spec.extra,
body,
})
}
fn default_into(
ast: &Ast,
id: Id<TypeNode>,
place: Self::Expr,
) -> Result<Self::Stmt, EncodeError> {
Ok(Stmt::Assign {
place,
value: CodeExpr::Call {
callee: crate::default_ocaml::defaulter_name(ast, id),
args: Vec::new(),
},
})
}
fn member_default(
ast: &Ast,
node: &MemberNode,
type_: Id<TypeNode>,
place: Self::Expr,
) -> Result<Vec<Self::Stmt>, EncodeError> {
crate::default_ocaml::member_default(ast, node, type_, place)
}
fn enumerator_constant(ast: &Ast, id: Id<TypeNode>, enumerator: &str) -> Self::Expr {
CodeExpr::Construct(ocaml_enumerator_name(ast, id, enumerator))
}
}
fn bound_tag(ast: &Ast, bound: &zerodds_ast::Bound) -> String {
match bound.expr() {
None => String::new(),
Some(expr) => match ast.eval_integral(expr) {
Ok(limit) => format!("_b{limit}"),
Err(_) => "_bx".to_string(),
},
}
}
#[must_use]
pub fn type_tag(ast: &Ast, id: Id<TypeNode>, depth: usize) -> String {
if depth > MAX_TYPE_DEPTH {
return "unresolved".to_string();
}
let node = match ast.type_(id) {
Ok(node) => node,
Err(_) => return "unresolved".to_string(),
};
match &node.kind {
TypeKind::Primitive(kind) => kind.as_str().replace(' ', "_"),
TypeKind::String {
width: CharWidth::Char,
bound,
} => format!("string{}", bound_tag(ast, bound)),
TypeKind::String {
width: CharWidth::WChar,
bound,
} => format!("wstring{}", bound_tag(ast, bound)),
TypeKind::Fixed { digits, scale } => format!("fixed_{digits}_{scale}"),
TypeKind::Sequence { element, bound } => match element.resolved() {
Some(element) => format!(
"seq{}_{}",
bound_tag(ast, bound),
type_tag(ast, element, depth + 1)
),
None => format!("seq_unresolved{}", bound_tag(ast, bound)),
},
TypeKind::Array { element, dims } => {
let mut name = match element.resolved() {
Some(element) => format!("arr_{}", type_tag(ast, element, depth + 1)),
None => "arr_unresolved".to_string(),
};
for dim in dims {
match ast.eval_integral(*dim) {
Ok(length) => name.push_str(&format!("_{length}")),
Err(_) => name.push_str("_x"),
}
}
name
}
TypeKind::Map { key, value, bound } => {
let half = |half: Option<Id<TypeNode>>| match half {
Some(target) => type_tag(ast, target, depth + 1),
None => "unresolved".to_string(),
};
format!(
"map{}_{}_{}",
bound_tag(ast, bound),
half(key.resolved()),
half(value.resolved())
)
}
TypeKind::Alias { aliased } => match aliased.resolved() {
Some(target) => type_tag(ast, target, depth + 1),
None => "unresolved".to_string(),
},
TypeKind::ValueBox { boxed } => match boxed.resolved() {
Some(target) => format!("box_{}", type_tag(ast, target, depth + 1)),
None => "box_unresolved".to_string(),
},
TypeKind::Structure { .. }
| TypeKind::Union { .. }
| TypeKind::Enumeration { .. }
| TypeKind::Bitmask { .. }
| TypeKind::Bitset { .. }
| TypeKind::Valuetype { .. }
| TypeKind::Native => ocaml_type_name(ast, id),
TypeKind::Any => "any".to_string(),
TypeKind::ValueBase => "valuebase".to_string(),
TypeKind::ObjectRef { .. } => "objectref".to_string(),
TypeKind::AnnotationType { .. } => "annotation".to_string(),
}
}