use super::{CodecTarget, Constant, Handle, Handoff, Routine, Scalar};
use crate::adapter::kotlin::{
kotlin_map_entry_name, kotlin_value_type, property_name, type_tag, unsigned_holder,
};
use crate::encode::EncodeError;
use zerodds_ast::{Ast, MemberNode, TypeNode};
use zerodds_ast_core::Id;
use zerodds_lang_cst::common::{Doc, Ident, Literal, Path};
use zerodds_lang_cst::kotlin;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Kotlin;
const SIGNED_CHAR: &str = "signed char";
impl Kotlin {
fn allocate(
base: &str,
element: kotlin::Type,
count: kotlin::Expr,
init: Option<kotlin::Expr>,
fill: Vec<kotlin::Stmt>,
) -> Vec<kotlin::Stmt> {
let length = Self::to_array_size(count);
let mut out = vec![kotlin::Stmt::Expr(kotlin::Expr::Binary {
op: "=",
lhs: Box::new(<Self as CodecTarget>::whole(base)),
rhs: Box::new(kotlin::Expr::NewArray {
element: Box::new(element),
length: Box::new(length),
init: init.map(Box::new),
}),
})];
out.extend(fill);
out
}
fn camel(method: &str) -> String {
let mut out = String::new();
let mut rising = false;
for ch in method.chars() {
if ch == '_' {
rising = true;
continue;
}
if rising {
out.extend(ch.to_uppercase());
rising = false;
} else {
out.push(ch);
}
}
out
}
fn atom(value: kotlin::Expr) -> kotlin::Expr {
match value {
kotlin::Expr::Literal(Literal::Int(ref text) | Literal::Float(ref text))
if text.starts_with('-') =>
{
kotlin::Expr::Paren(Box::new(value))
}
kotlin::Expr::Literal(_)
| kotlin::Expr::Path(_)
| kotlin::Expr::Call { .. }
| kotlin::Expr::Field { .. }
| kotlin::Expr::Index { .. }
| kotlin::Expr::New { .. }
| kotlin::Expr::NewArray { .. }
| kotlin::Expr::ArrayOf { .. }
| kotlin::Expr::Paren(_)
| kotlin::Expr::NotNull(_) => value,
other => kotlin::Expr::Paren(Box::new(other)),
}
}
fn convert(value: kotlin::Expr, method: &str) -> kotlin::Expr {
kotlin::Expr::method_call(Self::atom(value), method, Vec::new())
}
fn to_array_size(value: kotlin::Expr) -> kotlin::Expr {
Self::cast(kotlin::Type::Primitive(kotlin::Primitive::Int), value)
}
fn widen_count(value: kotlin::Expr) -> kotlin::Expr {
if !Self::already(&kotlin::Type::Primitive(kotlin::Primitive::Int), &value) {
return value;
}
match value {
kotlin::Expr::Literal(_) => Self::suffix_long(value),
other => Self::cast(kotlin::Type::Primitive(kotlin::Primitive::Long), other),
}
}
fn narrow_shift(value: kotlin::Expr) -> kotlin::Expr {
match value {
kotlin::Expr::Literal(Literal::Int(text)) if text.ends_with('L') => {
kotlin::Expr::Literal(Literal::Int(text.trim_end_matches('L').to_string()))
}
other => Self::cast(kotlin::Type::Primitive(kotlin::Primitive::Int), other),
}
}
fn wide_bit_bound(bound: Option<&kotlin::Expr>) -> bool {
match bound {
Some(kotlin::Expr::Literal(Literal::Int(text))) => text
.trim_end_matches('L')
.parse::<u16>()
.is_ok_and(|bits| unsigned_holder(bits) == kotlin::Primitive::Long),
_ => false,
}
}
fn drop_to_long(value: kotlin::Expr) -> kotlin::Expr {
match value {
kotlin::Expr::Call {
receiver: Some(inner),
ref name,
ref args,
} if name.as_str() == "toLong" && args.is_empty() => *inner,
other => other,
}
}
fn walk_local(value: &kotlin::Expr) -> bool {
match value {
kotlin::Expr::Path(path) => match path.segments() {
[single] => single.as_str().starts_with("zd_"),
_ => false,
},
_ => false,
}
}
fn suffix_long(value: kotlin::Expr) -> kotlin::Expr {
match value {
kotlin::Expr::Literal(Literal::Int(text))
if text.parse::<i128>().is_ok() && !text.ends_with('L') =>
{
kotlin::Expr::Literal(Literal::Int(format!("{text}L")))
}
other => other,
}
}
fn already(type_: &kotlin::Type, value: &kotlin::Expr) -> bool {
use kotlin::{Primitive as P, Type};
match value {
kotlin::Expr::Paren(inner) => Self::already(type_, inner),
kotlin::Expr::Literal(Literal::Int(text)) => {
let is_long = text.ends_with('L') || text == "Long.MIN_VALUE";
matches!(type_, Type::Primitive(P::Long)) == is_long
&& matches!(type_, Type::Primitive(P::Long) | Type::Primitive(P::Int))
}
kotlin::Expr::Field { name, .. } if name.as_str() == "size" => {
*type_ == Type::Primitive(P::Int)
}
kotlin::Expr::Field { value, name }
if matches!(name.as_str(), "length" | "extent") && Self::walk_local(value) =>
{
*type_ == Type::Primitive(P::Long)
}
kotlin::Expr::Path(path) if matches!(path.segments(), [single] if single.as_str() == "zd_bits") => {
*type_ == Type::Primitive(P::Long)
}
kotlin::Expr::Binary { op, lhs, .. }
if matches!(*op, "and" | "or" | "ushr" | "shl") =>
{
Self::already(type_, lhs)
}
kotlin::Expr::Call {
name,
receiver: Some(_),
..
} => {
let returns = match name.as_str() {
"readBool" => Type::Primitive(P::Boolean),
"readU8" | "readI8" | "readChar" | "readI16" | "readU16" | "readI32"
| "readWchar" | "toInt" => Type::Primitive(P::Int),
"readU32" | "readI64" | "readU64" | "readEnum" | "readBitmask"
| "readBitset" | "toLong" => Type::Primitive(P::Long),
"readF32" | "toFloat" => Type::Primitive(P::Float),
"readF64" | "toDouble" => Type::Primitive(P::Double),
"readString" | "readStringAlloc" | "readFixed" => Type::named("String"),
"readF128" => Type::named("Float128"),
"toByte" => Type::Primitive(P::Byte),
"toShort" => Type::Primitive(P::Short),
"toChar" => Type::Primitive(P::Char),
_ => return false,
};
returns == *type_
}
_ => false,
}
}
fn reader_body(name: &str, sample: kotlin::Type, body: Vec<kotlin::Stmt>) -> Vec<kotlin::Stmt> {
let defaulter = crate::default_kotlin::defaulter_from_decoder(name);
let mut statements = vec![kotlin::Stmt::Declare {
mutable: true,
type_: sample,
name: Ident::new("v"),
value: Some(Self::call(&defaulter, Vec::new())),
}];
statements.extend(body);
statements.push(kotlin::Stmt::Return(Some(Self::whole("v"))));
statements
}
}
impl CodecTarget for Kotlin {
type Expr = kotlin::Expr;
type Stmt = kotlin::Stmt;
type Block = Vec<kotlin::Stmt>;
type Type = kotlin::Type;
type Decl = kotlin::Decl;
fn scalar(kind: Scalar) -> Self::Type {
use kotlin::Primitive as P;
kotlin::Type::Primitive(match kind {
Scalar::Bool => P::Boolean,
Scalar::Char => P::Char,
Scalar::Int8 => P::Byte,
Scalar::Int16 => P::Short,
Scalar::Index | Scalar::Size | Scalar::Int32 | Scalar::UInt8 | Scalar::UInt16 => P::Int,
Scalar::Int64 | Scalar::UInt32 | Scalar::UInt64 => P::Long,
Scalar::Float => P::Float,
Scalar::Double => P::Double,
Scalar::LongDouble => return kotlin::Type::named("Float128"),
})
}
fn named(name: &str) -> Self::Type {
kotlin::Type::named(name)
}
fn nothing() -> Self::Type {
kotlin::Type::Unit
}
fn handle(kind: Handle) -> Self::Type {
kotlin::Type::named(match kind {
Handle::DHeader => "DHeader",
Handle::EmHeader => "EmHeader",
Handle::Pid => "Pid",
Handle::TryConstruct => "TryConstruct",
Handle::ReaderMark => "ReaderMark",
Handle::PidRead => "PidRead",
Handle::EmHeaderRead => "EmHeaderRead",
})
}
fn code_point(value: Self::Expr, narrow: bool) -> Self::Expr {
if narrow {
return kotlin::Expr::field(value, "code");
}
value
}
fn unsigned_less(octets: u8, left: Self::Expr, right: Self::Expr) -> Self::Expr {
if octets < 8 {
return Self::binary("<", left, right);
}
Self::binary(
"<",
kotlin::Expr::Call {
receiver: Some(Box::new(kotlin::Expr::Path(Path::new(&[
"java", "lang", "Long",
])))),
name: Ident::new("compareUnsigned"),
args: vec![left, right],
},
Self::int(0),
)
}
fn bool_less(left: Self::Expr, right: Self::Expr) -> Self::Expr {
Self::binary("&&", kotlin::Expr::Not(Box::new(left)), right)
}
fn constant(which: Constant) -> Self::Expr {
let qualified = |name: &str| format!("zerodds.wire.{name}");
kotlin::Expr::Path(Path::new(
&qualified(match which {
Constant::WcharWidth2 => "WcharWidth.OCTETS_2",
Constant::WcharWidth4 => "WcharWidth.OCTETS_4",
Constant::WcharFixed => "WcharFraming.FIXED",
Constant::WcharGiopLengthPrefixed => "WcharFraming.GIOP_LENGTH_PREFIXED",
Constant::WstringUnitsStream => "WstringUnitOrder.STREAM",
Constant::WstringUnitsBigEndianAlways => "WstringUnitOrder.BIG_ENDIAN_ALWAYS",
Constant::WstringUnitsByteOrderMark => "WstringUnitOrder.BYTE_ORDER_MARK",
Constant::WstringUnitsHostUnswapped => "WstringUnitOrder.HOST_UNSWAPPED",
Constant::WstringSpecification => "WstringLengthPolicy.SPECIFICATION",
Constant::WstringCharacterCount => "WstringLengthPolicy.CHARACTER_COUNT",
Constant::WstringCharacterCountTerminated => {
"WstringLengthPolicy.CHARACTER_COUNT_TERMINATED"
}
Constant::WstringSpecificationWideUnits => {
"WstringLengthPolicy.SPECIFICATION_WIDE_UNITS"
}
Constant::WstringRosTypeSupport => "WstringLengthPolicy.ROS_TYPE_SUPPORT",
Constant::WstringOctetsTerminated2 => "WstringLengthPolicy.OCTETS_TERMINATED_2",
Constant::WstringOctetsTerminated4 => "WstringLengthPolicy.OCTETS_TERMINATED_4",
Constant::WstringCharactersTerminated2 => {
"WstringLengthPolicy.CHARACTERS_TERMINATED_2"
}
Constant::FixedDeclared => "FixedDigitCount.DECLARED",
Constant::FixedValuePrecision => "FixedDigitCount.VALUE_PRECISION",
Constant::FixedValueStringDigits => "FixedDigitCount.VALUE_STRING_DIGITS",
Constant::ParameterMember => "ParameterRole.MEMBER",
Constant::ParameterOptional => "ParameterRole.OPTIONAL",
Constant::TryConstructDiscard => "TryConstruct.DISCARD",
Constant::TryConstructTrim => "TryConstruct.TRIM",
Constant::TryConstructUseDefault => "TryConstruct.USE_DEFAULT",
Constant::StatusOk => "Status.OK",
Constant::StatusDiscarded => "Status.DISCARDED",
Constant::StatusImpossibleLength => "Status.IMPOSSIBLE_LENGTH",
Constant::StatusOutOfRange => "Status.OUT_OF_RANGE",
Constant::StatusTruncated => "Status.TRUNCATED",
})
.split('.')
.collect::<Vec<_>>(),
))
}
fn unsigned_holder(bits: u16) -> Self::Type {
kotlin::Type::Primitive(unsigned_holder(bits))
}
fn runtime_static(method: &str, mut args: Vec<Self::Expr>) -> Self::Expr {
if method == "pid_is_user_member" && !args.is_empty() {
let header = args.remove(0);
return kotlin::Expr::method_call(header, "isUserMember", Vec::new());
}
Self::call(&Self::camel(method), args)
}
fn name(text: &str) -> Self::Expr {
kotlin::Expr::name(text)
}
fn int(value: i128) -> Self::Expr {
kotlin::Expr::int(value)
}
fn literal(value: Literal) -> Self::Expr {
kotlin::Expr::Literal(value)
}
fn call(callee: &str, args: Vec<Self::Expr>) -> Self::Expr {
if callee == "strcmp" {
let mut arguments = args.into_iter();
if let (Some(left), Some(right)) = (arguments.next(), arguments.next()) {
return kotlin::Expr::method_call(left, "compareTo", vec![right]);
}
return kotlin::Expr::call("strcmp", Vec::new());
}
kotlin::Expr::call(callee, args)
}
fn runtime(receiver: &str, method: &str, args: Vec<Self::Expr>) -> Self::Expr {
let args = match method {
"write_u32" => args.into_iter().map(Self::widen_count).collect(),
"write_i8" | "write_i16" => args
.into_iter()
.map(|arg| Self::cast(kotlin::Type::Primitive(kotlin::Primitive::Int), arg))
.collect(),
"write_bitmask" if Self::wide_bit_bound(args.first()) => {
args.into_iter().map(Self::drop_to_long).collect()
}
"bitfield" if Self::wide_bit_bound(args.get(1)) => {
args.into_iter().map(Self::drop_to_long).collect()
}
_ => args,
};
kotlin::Expr::method_call(Self::name(receiver), &Self::camel(method), args)
}
fn carries_hdds_axes() -> bool {
true
}
fn member(base: &str, field: &str) -> Self::Expr {
kotlin::Expr::field(Self::name(base), field)
}
fn whole(base: &str) -> Self::Expr {
Self::name(base)
}
fn wide_int(value: i128) -> Self::Expr {
let text = if value > i128::from(i64::MAX) {
#[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
let pattern = value as u64 as i64;
zerodds_lang_render::kotlin::long_text(pattern)
} else if value > i128::from(i32::MAX) || value < i128::from(i32::MIN) {
#[allow(clippy::cast_possible_truncation)]
let pattern = value as i64;
zerodds_lang_render::kotlin::long_text(pattern)
} else {
format!("{value}")
};
kotlin::Expr::Literal(Literal::Int(text))
}
fn unsigned_int(value: i128) -> Self::Expr {
Self::wide_int(value)
}
fn wide_unsigned(value: u64) -> Self::Expr {
#[allow(clippy::cast_possible_wrap)]
let pattern = value as i64;
kotlin::Expr::Literal(Literal::Int(zerodds_lang_render::kotlin::long_text(
pattern,
)))
}
fn wide_signed(value: i128) -> Self::Expr {
#[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
let pattern = if value > i128::from(i64::MAX) {
value as u64 as i64
} else {
value as i64
};
kotlin::Expr::Literal(Literal::Int(zerodds_lang_render::kotlin::long_text(
pattern,
)))
}
fn field_of(value: Self::Expr, field: &str) -> Self::Expr {
kotlin::Expr::field(value, field)
}
fn index(value: Self::Expr, at: Self::Expr) -> Self::Expr {
kotlin::Expr::Index {
value: Box::new(value),
index: Box::new(at),
}
}
fn count_of(base: &str, fixed: Option<u64>) -> Self::Expr {
match fixed {
Some(bound) => kotlin::Expr::int(i128::from(bound)),
None => kotlin::Expr::field(Self::whole(base), "size"),
}
}
fn binary(op: &'static str, lhs: Self::Expr, rhs: Self::Expr) -> Self::Expr {
let spelled = match op {
"&" => "and",
"|" => "or",
">>" => "ushr",
other => other,
};
if spelled == "ushr" || spelled == "shl" {
return kotlin::Expr::Binary {
op: spelled,
lhs: Box::new(lhs),
rhs: Box::new(Self::narrow_shift(rhs)),
};
}
let long = kotlin::Type::Primitive(kotlin::Primitive::Long);
let (lhs, rhs) = if Self::already(&long, &lhs) {
(lhs, Self::suffix_long(rhs))
} else if Self::already(&long, &rhs) {
(Self::suffix_long(lhs), rhs)
} else {
(lhs, rhs)
};
kotlin::Expr::Binary {
op: spelled,
lhs: Box::new(lhs),
rhs: Box::new(rhs),
}
}
fn unary(op: &'static str, value: Self::Expr) -> Self::Expr {
match op {
"!" => kotlin::Expr::Not(Box::new(value)),
"~" => Self::convert(value, "inv"),
_ => value,
}
}
fn paren(value: Self::Expr) -> Self::Expr {
kotlin::Expr::Paren(Box::new(value))
}
fn cast(type_: Self::Type, value: Self::Expr) -> Self::Expr {
if type_ == kotlin::Type::named(SIGNED_CHAR) {
return Self::convert(value, "toByte");
}
if type_ == kotlin::Type::Primitive(kotlin::Primitive::Boolean) {
if let kotlin::Expr::Literal(Literal::Int(text)) = &value {
return kotlin::Expr::Literal(Literal::Bool(text.trim_end_matches('L') != "0"));
}
}
if Self::already(&type_, &value) {
return value;
}
match type_ {
kotlin::Type::Primitive(kotlin::Primitive::Byte) => Self::convert(value, "toByte"),
kotlin::Type::Primitive(kotlin::Primitive::Short) => Self::convert(value, "toShort"),
kotlin::Type::Primitive(kotlin::Primitive::Int) => Self::convert(value, "toInt"),
kotlin::Type::Primitive(kotlin::Primitive::Long) => Self::convert(value, "toLong"),
kotlin::Type::Primitive(kotlin::Primitive::Float) => Self::convert(value, "toFloat"),
kotlin::Type::Primitive(kotlin::Primitive::Double) => Self::convert(value, "toDouble"),
kotlin::Type::Primitive(kotlin::Primitive::Char) => Self::convert(value, "toChar"),
_ => value,
}
}
fn handoff(value: Self::Expr, _form: Handoff) -> Self::Expr {
value
}
fn declare(type_: Self::Type, name: &str, value: Option<Self::Expr>) -> Self::Stmt {
kotlin::Stmt::Declare {
mutable: true,
type_,
name: Ident::new(name),
value,
}
}
fn perform(value: Self::Expr) -> Self::Stmt {
kotlin::Stmt::Expr(value)
}
fn give_back(value: Option<Self::Expr>) -> Self::Stmt {
kotlin::Stmt::Return(Some(value.unwrap_or_else(|| Self::whole("v"))))
}
fn mark_used(_name: &str) -> Vec<Self::Stmt> {
Vec::new()
}
fn drop_value(value: Self::Expr) -> Self::Stmt {
kotlin::Stmt::Expr(value)
}
fn read_call(
callee: &str,
place: Self::Expr,
_form: Handoff,
behaviour: Self::Expr,
) -> Self::Stmt {
kotlin::Stmt::Expr(kotlin::Expr::Binary {
op: "=",
lhs: Box::new(place),
rhs: Box::new(Self::call(callee, vec![Self::name("r"), behaviour])),
})
}
fn read_forward(callee: &str) -> Self::Stmt {
Self::read_call(
callee,
Self::whole("v"),
Handoff::Value,
Self::name("zd_tc"),
)
}
fn keep_first(base: &str, count: Self::Expr) -> Vec<Self::Stmt> {
vec![kotlin::Stmt::Expr(kotlin::Expr::Binary {
op: "=",
lhs: Box::new(Self::whole(base)),
rhs: Box::new(kotlin::Expr::method_call(
Self::whole(base),
"copyOfRange",
vec![
Self::to_array_size(Self::unsigned_int(0)),
Self::to_array_size(count),
],
)),
})]
}
fn when(
condition: Self::Expr,
then: Self::Block,
otherwise: Option<Self::Block>,
) -> Self::Stmt {
kotlin::Stmt::If {
condition,
then: kotlin::Block { statements: then },
otherwise: otherwise.map(|body| kotlin::Block { statements: body }),
}
}
fn count_up(counter: &str, bound: Self::Expr, body: Self::Block) -> Self::Stmt {
kotlin::Stmt::CountUp {
counter: Ident::new(counter),
bound: Box::new(Self::to_array_size(bound)),
body: kotlin::Block { statements: body },
}
}
fn until(condition: Self::Expr, body: Self::Block) -> Self::Stmt {
kotlin::Stmt::While {
condition,
body: kotlin::Block { statements: body },
}
}
fn leave() -> Self::Stmt {
kotlin::Stmt::Break
}
fn note(doc: Doc) -> Self::Stmt {
kotlin::Stmt::Comment(doc)
}
fn make_room(
base: &str,
element: Self::Type,
count: Self::Expr,
_assigned_whole: bool,
fill: Vec<Self::Stmt>,
) -> Vec<Self::Stmt> {
Self::allocate(base, element, count, None, fill)
}
fn make_room_of(
ast: &Ast,
element_id: Id<TypeNode>,
base: &str,
element: Self::Type,
count: Self::Expr,
_assigned_whole: bool,
fill: Vec<Self::Stmt>,
) -> Vec<Self::Stmt> {
let init = crate::default_kotlin::alias_slot_start(ast, element_id);
Self::allocate(base, element, count, init, fill)
}
fn store(place: Self::Expr, type_: Self::Type, value: Self::Expr) -> Self::Stmt {
if Self::already(&type_, &value) {
return kotlin::Stmt::Expr(kotlin::Expr::Binary {
op: "=",
lhs: Box::new(place),
rhs: Box::new(value),
});
}
let narrow = matches!(
type_,
kotlin::Type::Primitive(
kotlin::Primitive::Byte | kotlin::Primitive::Short | kotlin::Primitive::Char
)
);
let value = if narrow {
Self::cast(type_, value)
} else {
value
};
kotlin::Stmt::Expr(kotlin::Expr::Binary {
op: "=",
lhs: Box::new(place),
rhs: Box::new(value),
})
}
fn storage(type_: Self::Type, name: &str) -> Self::Stmt {
kotlin::Stmt::Declare {
mutable: false,
type_: type_.clone(),
name: Ident::new(name),
value: Some(kotlin::Expr::New {
type_: Box::new(type_),
args: Vec::new(),
}),
}
}
fn header_field(value: Self::Expr, field: &str) -> Self::Expr {
kotlin::Expr::field(
value,
match field {
"pid" => "pid",
"end" => "isEnd",
"ignore" => "ignore",
"length" => "length",
"member_id" => "memberId",
"must_understand" => "mustUnderstand",
"extent" => "extent",
other => other,
},
)
}
fn present(base: &str, field: &str) -> Self::Expr {
kotlin::Expr::Binary {
op: "!=",
lhs: Box::new(Self::member(base, field)),
rhs: Box::new(Self::absent()),
}
}
fn set_present(base: &str, field: &str, present: bool) -> Vec<Self::Stmt> {
if present {
return Vec::new();
}
vec![kotlin::Stmt::Expr(kotlin::Expr::Binary {
op: "=",
lhs: Box::new(Self::member(base, field)),
rhs: Box::new(Self::absent()),
})]
}
fn case_member(base: &str, field: &str) -> Self::Expr {
Self::member(base, field)
}
fn entry_half(entry: Self::Expr, key: bool) -> Self::Expr {
kotlin::Expr::field(entry, if key { "key" } else { "value" })
}
fn optional_value(_ast: &Ast, _id: Id<TypeNode>, base: &str, field: &str) -> Self::Expr {
kotlin::Expr::NotNull(Box::new(Self::member(base, field)))
}
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 optional_destination(base: &str, field: &str) -> (Vec<Self::Stmt>, Self::Expr) {
(Vec::new(), Self::member(base, field))
}
fn elements(base: &str) -> Self::Expr {
Self::whole(base)
}
fn absent() -> Self::Expr {
kotlin::Expr::Literal(Literal::Null)
}
fn materialised(_value: Self::Expr) -> Option<Self::Expr> {
None
}
fn block(statements: Vec<Self::Stmt>) -> Self::Block {
statements
}
fn into_statements(block: Self::Block) -> Vec<Self::Stmt> {
block
}
fn routine(spec: Routine<Self>) -> Option<Self::Decl> {
let body = spec.body?;
let mut params = vec![kotlin::Param {
name: Ident::new(if spec.writing { "w" } else { "r" }),
type_: kotlin::Type::named(if spec.writing { "Writer" } else { "Reader" }),
}];
if spec.writing {
params.push(kotlin::Param {
name: Ident::new("v"),
type_: spec.sample.clone(),
});
}
params.extend(spec.extra.into_iter().map(|(type_, name)| kotlin::Param {
name: Ident::new(&name),
type_,
}));
let (returns, body) = if spec.writing {
(kotlin::Type::Unit, body)
} else {
(
spec.sample.clone(),
Self::reader_body(&spec.name, spec.sample, body),
)
};
Some(kotlin::Decl::Func(kotlin::FuncDecl {
doc: spec.doc,
name: Ident::new(&spec.name),
params,
returns,
body,
}))
}
fn value_type(ast: &Ast, id: Id<TypeNode>) -> Self::Type {
let mut seen = id;
for _ in 0..64 {
let Ok(zerodds_ast::TypeKind::Alias { aliased }) =
ast.type_(seen).map(|node| &node.kind)
else {
break;
};
let Some(target) = aliased.resolved() else {
break;
};
seen = target;
}
let plain = matches!(
ast.type_(seen).map(|node| &node.kind),
Ok(zerodds_ast::TypeKind::Primitive(kind)) if *kind != zerodds_ast::PrimitiveKind::Float128
) || matches!(
ast.type_(seen).map(|node| &node.kind),
Ok(zerodds_ast::TypeKind::Enumeration { .. } | zerodds_ast::TypeKind::Bitmask { .. })
);
let shown = if plain { id } else { seen };
kotlin_value_type(ast, shown, "codec")
.unwrap_or_else(|_| kotlin::Type::named(&format!("UnresolvedType{}", id.slot())))
}
fn type_name(ast: &Ast, id: Id<TypeNode>) -> String {
type_tag(ast, id, 0)
}
fn escape(name: &str) -> String {
property_name(name)
}
fn inherited_path(depth: usize) -> Option<String> {
Some(format!("{}.", property_name("parent")).repeat(depth))
}
fn map_entry_name(ast: &Ast, id: Id<TypeNode>) -> String {
kotlin_map_entry_name(ast, id)
}
fn module_prefix(_ast: &Ast, _id: Id<TypeNode>) -> String {
String::new()
}
fn member_default(
ast: &Ast,
node: &MemberNode,
type_: Id<TypeNode>,
place: Self::Expr,
) -> Result<Vec<Self::Stmt>, EncodeError> {
crate::default_kotlin::member_default(ast, node, type_, place)
}
fn default_into(
ast: &Ast,
id: Id<TypeNode>,
place: Self::Expr,
) -> Result<Self::Stmt, EncodeError> {
Ok(crate::default_kotlin::default_into(ast, id, place))
}
fn enumerator_constant(ast: &Ast, id: Id<TypeNode>, enumerator: &str) -> Self::Expr {
crate::default_kotlin::enumerator_constant(ast, id, enumerator)
}
}