use crate::ir::codec::{EnumLayout, VecLayout};
use crate::ir::ids::BuiltinId;
use crate::ir::ops::{ReadOp, ReadSeq, SizeExpr, ValueExpr, WriteOp, WriteSeq};
use crate::ir::types::{PrimitiveType, TypeExpr};
use crate::render::kotlin::NamingConvention;
pub fn render_value(expr: &ValueExpr) -> String {
match expr {
ValueExpr::Instance => String::new(),
ValueExpr::Var(name) => name.clone(),
ValueExpr::Named(name) => NamingConvention::property_name(name),
ValueExpr::Field(parent, field) => {
let parent_str = render_value(parent);
let field_str = NamingConvention::property_name(field.as_str());
if parent_str.is_empty() {
field_str
} else {
format!("{}.{}", parent_str, field_str)
}
}
}
}
fn render_type_name(name: &str) -> String {
NamingConvention::class_name(name)
}
fn kotlin_type_for_type_expr(ty: &TypeExpr) -> String {
match ty {
TypeExpr::Primitive(p) => match p {
PrimitiveType::Bool => "Boolean".to_string(),
PrimitiveType::I8 => "Byte".to_string(),
PrimitiveType::U8 => "UByte".to_string(),
PrimitiveType::I16 => "Short".to_string(),
PrimitiveType::U16 => "UShort".to_string(),
PrimitiveType::I32 => "Int".to_string(),
PrimitiveType::U32 => "UInt".to_string(),
PrimitiveType::I64 | PrimitiveType::ISize => "Long".to_string(),
PrimitiveType::U64 | PrimitiveType::USize => "ULong".to_string(),
PrimitiveType::F32 => "Float".to_string(),
PrimitiveType::F64 => "Double".to_string(),
},
TypeExpr::String => "String".to_string(),
TypeExpr::Bytes => "ByteArray".to_string(),
TypeExpr::Vec(inner) => match inner.as_ref() {
TypeExpr::Primitive(primitive) => match primitive {
PrimitiveType::I32 | PrimitiveType::U32 => "IntArray".to_string(),
PrimitiveType::I16 | PrimitiveType::U16 => "ShortArray".to_string(),
PrimitiveType::I64
| PrimitiveType::U64
| PrimitiveType::ISize
| PrimitiveType::USize => "LongArray".to_string(),
PrimitiveType::F32 => "FloatArray".to_string(),
PrimitiveType::F64 => "DoubleArray".to_string(),
PrimitiveType::U8 | PrimitiveType::I8 => "ByteArray".to_string(),
PrimitiveType::Bool => "BooleanArray".to_string(),
},
_ => format!("List<{}>", kotlin_type_for_type_expr(inner)),
},
TypeExpr::Option(inner) => format!("{}?", kotlin_type_for_type_expr(inner)),
TypeExpr::Result { ok, err } => format!(
"BoltFFIResult<{}, {}>",
kotlin_type_for_type_expr(ok),
kotlin_type_for_type_expr(err)
),
TypeExpr::Record(id) => render_type_name(id.as_str()),
TypeExpr::Enum(id) => render_type_name(id.as_str()),
TypeExpr::Custom(id) => render_type_name(id.as_str()),
TypeExpr::Builtin(id) => match id.as_str() {
"Duration" => "Duration".to_string(),
"SystemTime" => "Instant".to_string(),
"Uuid" => "UUID".to_string(),
"Url" => "URI".to_string(),
_ => "String".to_string(),
},
TypeExpr::Handle(class_id) => render_type_name(class_id.as_str()),
TypeExpr::Callback(callback_id) => render_type_name(callback_id.as_str()),
TypeExpr::Void => "Unit".to_string(),
}
}
fn kotlin_type_for_write_seq(seq: &WriteSeq) -> String {
match seq.ops.first() {
Some(WriteOp::Primitive { primitive, .. }) => {
kotlin_type_for_type_expr(&TypeExpr::Primitive(*primitive))
}
Some(WriteOp::String { .. }) => "String".to_string(),
Some(WriteOp::Bytes { .. }) => "ByteArray".to_string(),
Some(WriteOp::Builtin { id, .. }) => {
kotlin_type_for_type_expr(&TypeExpr::Builtin(id.clone()))
}
Some(WriteOp::Record { id, .. }) => render_type_name(id.as_str()),
Some(WriteOp::Enum { id, .. }) => render_type_name(id.as_str()),
Some(WriteOp::Custom { id, .. }) => render_type_name(id.as_str()),
Some(WriteOp::Vec { element_type, .. }) => {
kotlin_type_for_type_expr(&TypeExpr::Vec(Box::new(element_type.clone())))
}
Some(WriteOp::Option { some, .. }) => format!("{}?", kotlin_type_for_write_seq(some)),
Some(WriteOp::Result { ok, err, .. }) => format!(
"BoltFFIResult<{}, {}>",
kotlin_type_for_write_seq(ok),
kotlin_type_for_write_seq(err)
),
_ => "Any".to_string(),
}
}
pub fn emit_size_expr(size: &SizeExpr) -> String {
match size {
SizeExpr::Fixed(value) => value.to_string(),
SizeExpr::Runtime => "0".to_string(),
SizeExpr::StringLen(value) => format!("Utf8Codec.maxBytes({})", render_value(value)),
SizeExpr::BytesLen(value) => format!("{}.size", render_value(value)),
SizeExpr::ValueSize(value) => render_value(value),
SizeExpr::WireSize { value, .. } => format!("{}.wireEncodedSize()", render_value(value)),
SizeExpr::BuiltinSize { id, value } => emit_builtin_size(id, &render_value(value)),
SizeExpr::Sum(parts) => {
let rendered = parts
.iter()
.map(emit_size_expr)
.collect::<Vec<_>>()
.join(" + ");
format!("({})", rendered)
}
SizeExpr::OptionSize { value, inner } => {
let inner_expr = emit_size_expr(inner);
format!(
"({}?.let {{ v -> 1 + {} }} ?: 1)",
render_value(value),
inner_expr
)
}
SizeExpr::VecSize {
value,
inner,
layout,
} => emit_vec_size(&render_value(value), inner, layout),
SizeExpr::ResultSize { value, ok, err } => {
let v = render_value(value);
let ok_expr = emit_size_expr(ok);
let err_expr = emit_size_expr(err);
format!(
"(when (val _r = {}) {{ is BoltFFIResult.Ok<*> -> {{ val okVal = _r.value; 1 + {} }}; is BoltFFIResult.Err<*> -> {{ val errVal = _r.error; 1 + {} }} }})",
v, ok_expr, err_expr
)
}
}
}
pub fn emit_size_expr_for_write_seq(seq: &WriteSeq) -> String {
match seq.ops.first() {
Some(WriteOp::Custom { underlying, .. }) => emit_size_expr(&underlying.size),
Some(WriteOp::Result { ok, err, .. }) => {
let ok_type = kotlin_type_for_write_seq(ok);
let err_type = kotlin_type_for_write_seq(err);
match &seq.size {
SizeExpr::ResultSize { value, ok, err } => {
let v = render_value(value);
let ok_expr = emit_size_expr(ok);
let err_expr = emit_size_expr(err);
format!(
"(when (val _r = {}) {{ is BoltFFIResult.Ok<*> -> {{ val okVal = boltffiUnsafeCast<{}>(_r.value); 1 + {} }}; is BoltFFIResult.Err<*> -> {{ val errVal = boltffiUnsafeCast<{}>(_r.error); 1 + {} }} }})",
v, ok_type, ok_expr, err_type, err_expr
)
}
_ => emit_size_expr(&seq.size),
}
}
_ => emit_size_expr(&seq.size),
}
}
pub fn emit_reader_read(seq: &ReadSeq) -> String {
let op = seq.ops.first().expect("read ops");
match op {
ReadOp::Primitive { primitive, .. } => {
let method = match primitive {
PrimitiveType::Bool => "readBool",
PrimitiveType::I8 => "readI8",
PrimitiveType::U8 => "readU8",
PrimitiveType::I16 => "readI16",
PrimitiveType::U16 => "readU16",
PrimitiveType::I32 => "readI32",
PrimitiveType::U32 => "readU32",
PrimitiveType::I64 | PrimitiveType::ISize => "readI64",
PrimitiveType::U64 | PrimitiveType::USize => "readU64",
PrimitiveType::F32 => "readF32",
PrimitiveType::F64 => "readF64",
};
format!("reader.{}()", method)
}
ReadOp::String { .. } => "reader.readString()".to_string(),
ReadOp::Bytes { .. } => "reader.readBytes()".to_string(),
ReadOp::Record { id, .. } => {
format!("{}.decode(reader)", render_type_name(id.as_str()))
}
ReadOp::Enum { id, layout, .. } => match layout {
EnumLayout::CStyle {
tag_type,
is_error: false,
} => {
format!(
"{}.fromValue(reader.{}())",
render_type_name(id.as_str()),
enum_tag_read_method(*tag_type),
)
}
EnumLayout::CStyle { is_error: true, .. }
| EnumLayout::Data { .. }
| EnumLayout::Recursive => {
format!("{}.decode(reader)", render_type_name(id.as_str()))
}
},
ReadOp::Option { some, .. } => {
let inner = emit_reader_read(some);
format!("reader.readOptional {{ {} }}", inner)
}
ReadOp::Vec {
element_type,
element,
layout,
..
} => emit_reader_vec(element_type, element, layout),
ReadOp::Result { ok, err, .. } => {
let ok_expr = emit_reader_read(ok);
let err_expr = emit_reader_read(err);
format!("reader.readResult({{ {} }}, {{ {} }})", ok_expr, err_expr)
}
ReadOp::Builtin { id, .. } => match id.as_str() {
"Duration" => "reader.readDuration()".to_string(),
"SystemTime" => "reader.readInstant()".to_string(),
"Uuid" => "reader.readUuid()".to_string(),
"Url" => "reader.readUri()".to_string(),
_ => "reader.readString()".to_string(),
},
ReadOp::Custom { underlying, .. } => emit_reader_read(underlying),
}
}
fn emit_reader_vec(element_type: &TypeExpr, element: &ReadSeq, layout: &VecLayout) -> String {
match layout {
VecLayout::Blittable { .. } => match element_type {
TypeExpr::Primitive(primitive) => {
let method = match primitive {
PrimitiveType::I32 | PrimitiveType::U32 => "readIntArray",
PrimitiveType::I16 | PrimitiveType::U16 => "readShortArray",
PrimitiveType::I64
| PrimitiveType::U64
| PrimitiveType::ISize
| PrimitiveType::USize => "readLongArray",
PrimitiveType::F32 => "readFloatArray",
PrimitiveType::F64 => "readDoubleArray",
PrimitiveType::U8 | PrimitiveType::I8 => "readBytes",
PrimitiveType::Bool => "readBooleanArray",
};
format!("reader.{}()", method)
}
_ => {
let inner = emit_reader_read(element);
format!("reader.readList {{ {} }}", inner)
}
},
VecLayout::Encoded => {
let inner = emit_reader_read(element);
format!("reader.readList {{ {} }}", inner)
}
}
}
fn enum_tag_read_method(tag_type: PrimitiveType) -> &'static str {
match tag_type {
PrimitiveType::I8 | PrimitiveType::U8 => "readI8",
PrimitiveType::I16 | PrimitiveType::U16 => "readI16",
PrimitiveType::I32 | PrimitiveType::U32 => "readI32",
PrimitiveType::I64 | PrimitiveType::U64 | PrimitiveType::ISize | PrimitiveType::USize => {
"readI64"
}
PrimitiveType::Bool => "readBool",
PrimitiveType::F32 => "readF32",
PrimitiveType::F64 => "readF64",
}
}
fn enum_tag_write_expr(tag_type: PrimitiveType, value_expr: &str) -> String {
match tag_type {
PrimitiveType::I8 | PrimitiveType::U8 => format!("wire.writeI8({value_expr})"),
PrimitiveType::I16 | PrimitiveType::U16 => format!("wire.writeI16({value_expr})"),
PrimitiveType::I32 | PrimitiveType::U32 => format!("wire.writeI32({value_expr})"),
PrimitiveType::I64 | PrimitiveType::U64 | PrimitiveType::ISize | PrimitiveType::USize => {
format!("wire.writeI64({value_expr})")
}
PrimitiveType::Bool => format!("wire.writeBool({value_expr})"),
PrimitiveType::F32 => format!("wire.writeF32({value_expr})"),
PrimitiveType::F64 => format!("wire.writeF64({value_expr})"),
}
}
pub fn emit_write_expr(seq: &WriteSeq) -> String {
let op = seq.ops.first().expect("write ops");
match op {
WriteOp::Primitive { primitive, value } => {
emit_write_primitive(*primitive, &render_value(value))
}
WriteOp::String { value } => format!("wire.writeString({})", render_value(value)),
WriteOp::Bytes { value } => format!("wire.writeBytes({})", render_value(value)),
WriteOp::Option { value, some } => {
let inner = emit_write_expr(some);
format!(
"{}?.let {{ v -> wire.writeU8(1u); {} }} ?: wire.writeU8(0u)",
render_value(value),
inner
)
}
WriteOp::Vec {
value,
element_type,
element,
layout,
} => emit_write_vec(&render_value(value), element_type, element, layout),
WriteOp::Record { value, .. } => {
format!("{}.wireEncodeTo(wire)", render_value(value))
}
WriteOp::Enum { value, layout, .. } => match layout {
EnumLayout::CStyle {
tag_type,
is_error: false,
} => enum_tag_write_expr(*tag_type, &format!("{}.value", render_value(value))),
EnumLayout::CStyle { is_error: true, .. }
| EnumLayout::Data { .. }
| EnumLayout::Recursive => {
format!("{}.wireEncodeTo(wire)", render_value(value))
}
},
WriteOp::Result { value, ok, err } => {
let v = render_value(value);
let ok_expr = emit_write_expr(ok);
let err_expr = emit_write_expr(err);
let ok_type = kotlin_type_for_write_seq(ok);
let err_type = kotlin_type_for_write_seq(err);
format!(
"when ({}) {{ is BoltFFIResult.Ok<*> -> {{ wire.writeU8(0u); val okVal = boltffiUnsafeCast<{}>({}.value); {} }} is BoltFFIResult.Err<*> -> {{ wire.writeU8(1u); val errVal = boltffiUnsafeCast<{}>({}.error); {} }} }}",
v, ok_type, v, ok_expr, err_type, v, err_expr
)
}
WriteOp::Builtin { id, value } => emit_write_builtin(id, &render_value(value)),
WriteOp::Custom { underlying, .. } => emit_write_expr(underlying),
}
}
fn emit_vec_size(value: &str, inner: &SizeExpr, layout: &VecLayout) -> String {
match layout {
VecLayout::Blittable { .. } => {
format!("(4 + {}.size * {})", value, emit_size_expr(inner))
}
VecLayout::Encoded => {
format!(
"(4 + {}.sumOf {{ item -> {} }})",
value,
emit_size_expr(inner)
)
}
}
}
fn emit_builtin_size(id: &BuiltinId, value: &str) -> String {
if id.as_str() == "Url" {
format!("Utf8Codec.maxBytes({}.toString())", value)
} else {
format!("{}.wireEncodedSize()", value)
}
}
fn emit_write_primitive(primitive: PrimitiveType, value: &str) -> String {
match primitive {
PrimitiveType::Bool => format!("wire.writeBool({})", value),
PrimitiveType::I8 => format!("wire.writeI8({})", value),
PrimitiveType::U8 => format!("wire.writeU8({})", value),
PrimitiveType::I16 => format!("wire.writeI16({})", value),
PrimitiveType::U16 => format!("wire.writeU16({})", value),
PrimitiveType::I32 => format!("wire.writeI32({})", value),
PrimitiveType::U32 => format!("wire.writeU32({})", value),
PrimitiveType::I64 | PrimitiveType::ISize => format!("wire.writeI64({})", value),
PrimitiveType::U64 | PrimitiveType::USize => format!("wire.writeU64({})", value),
PrimitiveType::F32 => format!("wire.writeF32({})", value),
PrimitiveType::F64 => format!("wire.writeF64({})", value),
}
}
fn emit_write_vec(
value: &str,
element_type: &TypeExpr,
element: &WriteSeq,
layout: &VecLayout,
) -> String {
match layout {
VecLayout::Blittable { .. } => match element_type {
TypeExpr::Primitive(_) => format!("wire.writePrimitiveList({})", value),
TypeExpr::Record(id) => format!(
"wire.writeU32({}.size.toUInt()); {}Writer.writeAllToWire(wire, {})",
value,
id.as_str(),
value
),
_ => {
let inner = emit_write_expr(element);
format!(
"wire.writeU32({}.size.toUInt()); {}.forEach {{ item -> {} }}",
value, value, inner
)
}
},
VecLayout::Encoded => {
let inner = emit_write_expr(element);
format!(
"wire.writeU32({}.size.toUInt()); {}.forEach {{ item -> {} }}",
value, value, inner
)
}
}
}
fn emit_write_builtin(id: &BuiltinId, value: &str) -> String {
match id.as_str() {
"Duration" => format!("wire.writeDuration({})", value),
"SystemTime" => format!("wire.writeInstant({})", value),
"Uuid" => format!("wire.writeUuid({})", value),
"Url" => format!("wire.writeUri({})", value),
_ => format!("wire.writeString({})", value),
}
}