use super::{CodecTarget, Constant, Handle, Handoff, Routine, Scalar};
use crate::adapter::julia::{
julia_map_entry_name, julia_module_prefix, julia_type_name, julia_value_type,
};
use crate::encode::EncodeError;
use zerodds_ast::{Ast, CharWidth, MemberNode, TypeKind, TypeNode};
use zerodds_ast_core::Id;
use zerodds_lang_cst::common::{Doc, Ident, Literal};
use zerodds_lang_cst::julia::{self as jl, escape_field, escape_name};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Julia;
const MAX_TYPE_DEPTH: usize = 64;
const SIGNED_CHAR: &str = "signed char";
const MAP_ENTRY: &str = "_ZdMapEntryScratch";
impl Julia {
fn suite(body: Vec<jl::Statement>) -> Vec<jl::Statement> {
body
}
fn reading_body(name: &str, sample: jl::Type, body: Vec<jl::Statement>) -> Vec<jl::Statement> {
let defaulter = crate::default_julia::defaulter_from_decoder(name);
let mut statements = vec![
jl::Statement::Assign {
place: jl::Expr::name("zd_rt"),
value: jl::Expr::Type(Box::new(sample)),
},
jl::Statement::Assign {
place: jl::Expr::name("v"),
value: jl::Expr::free_call(&defaulter, Vec::new()),
},
];
statements.extend(body);
statements.push(Self::give_back(None));
statements
}
fn text(value: &str) -> jl::Expr {
jl::Expr::Literal(Literal::Str(value.to_string()))
}
}
impl CodecTarget for Julia {
type Expr = jl::Expr;
type Stmt = jl::Statement;
type Block = Vec<jl::Statement>;
type Type = jl::Type;
type Decl = jl::Decl;
fn scalar(kind: Scalar) -> Self::Type {
jl::Type::Primitive(match kind {
Scalar::Bool => jl::Primitive::Bool,
Scalar::Char | Scalar::UInt8 => jl::Primitive::UInt8,
Scalar::Int8 => jl::Primitive::Int8,
Scalar::Int16 => jl::Primitive::Int16,
Scalar::UInt16 => jl::Primitive::UInt16,
Scalar::Int32 => jl::Primitive::Int32,
Scalar::UInt32 => jl::Primitive::UInt32,
Scalar::UInt64 => jl::Primitive::UInt64,
Scalar::Int64 | Scalar::Index | Scalar::Size => jl::Primitive::Int64,
Scalar::Float => jl::Primitive::Float32,
Scalar::Double => jl::Primitive::Float64,
Scalar::LongDouble => jl::Primitive::String,
})
}
fn named(name: &str) -> Self::Type {
jl::Type::named(name)
}
fn nothing() -> Self::Type {
jl::Type::Primitive(jl::Primitive::Nothing)
}
fn handle(kind: Handle) -> Self::Type {
jl::Type::named(match kind {
Handle::DHeader => "ZdDHeader",
Handle::EmHeader => "ZdEmHeader",
Handle::Pid => "ZdPid",
Handle::TryConstruct => "ZdTryConstruct",
Handle::ReaderMark => "ZdReaderMark",
Handle::PidRead => "ZdPidRead",
Handle::EmHeaderRead => "ZdEmHeaderRead",
})
}
fn code_point(value: Self::Expr, _narrow: bool) -> Self::Expr {
value
}
fn constant(which: Constant) -> Self::Expr {
match which {
Constant::WcharWidth2 => jl::Expr::int(2),
Constant::WcharWidth4 => jl::Expr::int(4),
Constant::WcharFixed => Self::text("fixed"),
Constant::WcharGiopLengthPrefixed => Self::text("giop-length-prefixed"),
Constant::WstringUnitsStream => Self::text("stream"),
Constant::WstringUnitsBigEndianAlways => Self::text("big-endian-always"),
Constant::WstringUnitsByteOrderMark => Self::text("byte-order-mark"),
Constant::WstringUnitsHostUnswapped => Self::text("host-unswapped"),
Constant::WstringSpecification => Self::text("specification"),
Constant::WstringCharacterCount => Self::text("character-count"),
Constant::WstringCharacterCountTerminated => Self::text("character-count-terminated"),
Constant::WstringSpecificationWideUnits => Self::text("specification-wide-units"),
Constant::WstringRosTypeSupport => Self::text("ros-type-support"),
Constant::WstringOctetsTerminated2 => Self::text("octets/terminated/2"),
Constant::WstringOctetsTerminated4 => Self::text("octets/terminated/4"),
Constant::WstringCharactersTerminated2 => Self::text("characters/terminated/2"),
Constant::FixedDeclared => Self::text("declared"),
Constant::FixedValuePrecision => Self::text("value-precision"),
Constant::FixedValueStringDigits => Self::text("value-string-digits"),
Constant::ParameterMember => jl::Expr::name("ZD_PARAMETER_MEMBER"),
Constant::ParameterOptional => jl::Expr::name("ZD_PARAMETER_OPTIONAL"),
Constant::TryConstructDiscard => jl::Expr::name("ZD_TRY_CONSTRUCT_DISCARD"),
Constant::TryConstructTrim => jl::Expr::name("ZD_TRY_CONSTRUCT_TRIM"),
Constant::TryConstructUseDefault => jl::Expr::name("ZD_TRY_CONSTRUCT_USE_DEFAULT"),
Constant::StatusOk => jl::Expr::name("ZD_STATUS_OK"),
Constant::StatusDiscarded => jl::Expr::name("ZD_STATUS_DISCARDED"),
Constant::StatusImpossibleLength => jl::Expr::name("ZD_STATUS_IMPOSSIBLE_LENGTH"),
Constant::StatusOutOfRange => jl::Expr::name("ZD_STATUS_OUT_OF_RANGE"),
Constant::StatusTruncated => jl::Expr::name("ZD_STATUS_TRUNCATED"),
}
}
fn unsigned_holder(bits: u16) -> Self::Type {
jl::Type::Primitive(match bits {
0..=8 => jl::Primitive::UInt8,
9..=16 => jl::Primitive::UInt16,
17..=32 => jl::Primitive::UInt32,
_ => jl::Primitive::UInt64,
})
}
fn value_type(ast: &Ast, id: Id<TypeNode>) -> Self::Type {
julia_value_type(ast, id).unwrap_or(jl::Type::Primitive(jl::Primitive::Any))
}
fn type_name(ast: &Ast, id: Id<TypeNode>) -> String {
codec_tag(ast, id, 0)
}
fn map_entry_name(_ast: &Ast, _id: Id<TypeNode>) -> String {
MAP_ENTRY.to_string()
}
fn module_prefix(ast: &Ast, id: Id<TypeNode>) -> String {
julia_module_prefix(ast, id)
}
fn escape(name: &str) -> String {
escape_field(name)
}
fn inherited_path(depth: usize) -> Option<String> {
Some(format!("{}.", escape_field("parent")).repeat(depth))
}
fn name(text: &str) -> Self::Expr {
jl::Expr::name(text)
}
fn int(value: i128) -> Self::Expr {
jl::Expr::int(value)
}
fn wide_int(value: i128) -> Self::Expr {
jl::Expr::int(value)
}
fn wide_unsigned(value: u64) -> Self::Expr {
jl::Expr::convert(
jl::Type::Primitive(jl::Primitive::UInt64),
jl::Expr::int(i128::from(value)),
)
}
fn wide_signed(value: i128) -> Self::Expr {
jl::Expr::int(value)
}
fn unsigned_int(value: i128) -> Self::Expr {
jl::Expr::int(value)
}
fn literal(value: Literal) -> Self::Expr {
let Literal::Int(text) = &value else {
return jl::Expr::Literal(value);
};
match text.as_str() {
"true" => jl::Expr::Literal(Literal::Bool(true)),
"false" => jl::Expr::Literal(Literal::Bool(false)),
_ => jl::Expr::Literal(value),
}
}
fn call(callee: &str, args: Vec<Self::Expr>) -> Self::Expr {
if callee == "strcmp" {
return jl::Expr::free_call("Base.cmp", args);
}
jl::Expr::free_call(callee, 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 mut all = vec![jl::Expr::name(receiver)];
all.extend(args);
jl::Expr::free_call(method, all)
}
fn runtime_static(method: &str, args: Vec<Self::Expr>) -> Self::Expr {
jl::Expr::free_call(method, args)
}
fn carries_hdds_axes() -> bool {
true
}
fn member(base: &str, field: &str) -> Self::Expr {
jl::Expr::field(jl::Expr::name(base), field)
}
fn whole(base: &str) -> Self::Expr {
jl::Expr::name(base)
}
fn field_of(value: Self::Expr, field: &str) -> Self::Expr {
jl::Expr::field(value, field)
}
fn index(value: Self::Expr, at: Self::Expr) -> Self::Expr {
jl::Expr::index(
value,
jl::Expr::Binary {
op: "+",
lhs: Box::new(jl::Expr::Paren(Box::new(at))),
rhs: Box::new(jl::Expr::int(1)),
},
)
}
fn count_of(base: &str, fixed: Option<u64>) -> Self::Expr {
match fixed {
Some(bound) => jl::Expr::int(i128::from(bound)),
None => jl::Expr::free_call("Base.length", vec![jl::Expr::name(base)]),
}
}
fn elements(base: &str) -> Self::Expr {
jl::Expr::name(base)
}
fn case_member(base: &str, field: &str) -> Self::Expr {
Self::member(base, field)
}
fn entry_half(entry: Self::Expr, key: bool) -> Self::Expr {
jl::Expr::field(entry, if key { "key" } else { "value" })
}
fn absent() -> Self::Expr {
jl::Expr::Literal(Literal::Null)
}
fn materialised(_value: Self::Expr) -> Option<Self::Expr> {
None
}
fn optional_value(_ast: &Ast, _id: Id<TypeNode>, base: &str, field: &str) -> Self::Expr {
Self::member(base, field)
}
fn present(base: &str, field: &str) -> Self::Expr {
jl::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![jl::Statement::Assign {
place: Self::member(base, field),
value: Self::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(), Self::member(base, field))
}
fn binary(op: &'static str, lhs: Self::Expr, rhs: Self::Expr) -> Self::Expr {
jl::Expr::Binary {
op,
lhs: Box::new(lhs),
rhs: Box::new(rhs),
}
}
fn unary(op: &'static str, value: Self::Expr) -> Self::Expr {
jl::Expr::Unary {
op,
value: Box::new(value),
}
}
fn paren(value: Self::Expr) -> Self::Expr {
jl::Expr::Paren(Box::new(value))
}
fn cast(type_: Self::Type, value: Self::Expr) -> Self::Expr {
if type_ == jl::Type::named(SIGNED_CHAR) {
return signed_char(value);
}
if type_ == jl::Type::Primitive(jl::Primitive::Int64) {
return jl::Expr::free_call("zd_i64", vec![value]);
}
if type_ == jl::Type::Primitive(jl::Primitive::Bool) {
if let jl::Expr::Literal(Literal::Int(text)) = &value {
return jl::Expr::Literal(Literal::Bool(text != "0"));
}
}
match &type_ {
jl::Type::Primitive(
jl::Primitive::String | jl::Primitive::Nothing | jl::Primitive::Any,
) => value,
jl::Type::Primitive(_) | jl::Type::Named(_) => jl::Expr::convert(type_, value),
_ => value,
}
}
fn handoff(value: Self::Expr, _form: Handoff) -> Self::Expr {
value
}
fn declare(type_: Self::Type, name: &str, value: Option<Self::Expr>) -> Self::Stmt {
jl::Statement::Local {
name: Ident::new(name),
type_: Some(type_),
value,
}
}
fn perform(value: Self::Expr) -> Self::Stmt {
if let jl::Expr::Binary { op: "=", lhs, rhs } = value {
return jl::Statement::Assign {
place: *lhs,
value: *rhs,
};
}
jl::Statement::Expr(value)
}
fn give_back(value: Option<Self::Expr>) -> Self::Stmt {
jl::Statement::Return(Some(value.unwrap_or_else(|| {
jl::Expr::free_call(
"zd_freeze",
vec![jl::Expr::name("zd_rt"), jl::Expr::name("v")],
)
})))
}
fn when(
condition: Self::Expr,
then: Self::Block,
otherwise: Option<Self::Block>,
) -> Self::Stmt {
jl::Statement::If {
condition,
then: Self::suite(then),
otherwise: otherwise.filter(|body| !body.is_empty()),
}
}
fn count_up(counter: &str, bound: Self::Expr, body: Self::Block) -> Self::Stmt {
jl::Statement::For {
counter: Ident::new(counter),
iter: jl::Expr::range(
jl::Expr::int(0),
jl::Expr::Paren(Box::new(jl::Expr::Binary {
op: "-",
lhs: Box::new(jl::Expr::free_call("Int64", vec![bound])),
rhs: Box::new(jl::Expr::int(1)),
})),
),
body: Self::suite(body),
}
}
fn until(condition: Self::Expr, body: Self::Block) -> Self::Stmt {
jl::Statement::While {
condition,
body: Self::suite(body),
}
}
fn leave() -> Self::Stmt {
jl::Statement::Break
}
fn note(doc: Doc) -> Self::Stmt {
jl::Statement::Comment(doc)
}
fn drop_value(value: Self::Expr) -> Self::Stmt {
jl::Statement::Expr(value)
}
fn mark_used(_name: &str) -> Vec<Self::Stmt> {
Vec::new()
}
fn read_call(
callee: &str,
place: Self::Expr,
_form: Handoff,
behaviour: Self::Expr,
) -> Self::Stmt {
jl::Statement::Assign {
place,
value: jl::Expr::free_call(callee, vec![jl::Expr::name("r"), behaviour]),
}
}
fn read_forward(callee: &str) -> Self::Stmt {
jl::Statement::Assign {
place: jl::Expr::name("v"),
value: jl::Expr::free_call(callee, vec![jl::Expr::name("r"), jl::Expr::name("zd_tc")]),
}
}
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 maker = if assigned_whole {
"zd_nothing_vector"
} else {
"zd_entry_vector"
};
let mut out = vec![jl::Statement::Assign {
place: jl::Expr::name(base),
value: jl::Expr::free_call(maker, vec![count]),
}];
out.extend(fill);
out
}
fn keep_first(base: &str, count: Self::Expr) -> Vec<Self::Stmt> {
vec![jl::Statement::Assign {
place: jl::Expr::name(base),
value: jl::Expr::index(
jl::Expr::name(base),
jl::Expr::range(jl::Expr::int(1), jl::Expr::free_call("Int64", vec![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_ == jl::Type::named(MAP_ENTRY) {
return jl::Statement::Assign {
place: jl::Expr::name(name),
value: jl::Expr::free_call("zd_scratch_entry", Vec::new()),
};
}
Self::declare(type_, name, None)
}
fn header_field(value: Self::Expr, field: &str) -> Self::Expr {
let spelled = if field == "end" { "end_" } else { field };
jl::Expr::field(value, spelled)
}
fn routine(spec: Routine<Self>) -> Option<Self::Decl> {
let body = spec.body?;
let mut params = vec![jl::Param {
name: Ident::new(if spec.writing { "w" } else { "r" }),
type_: Some(jl::Type::named(if spec.writing {
"ZdWriter"
} else {
"ZdReader"
})),
}];
if spec.writing {
params.push(jl::Param {
name: Ident::new("v"),
type_: None,
});
}
params.extend(spec.extra.into_iter().map(|(type_, name)| jl::Param {
name: Ident::new(&name),
type_: Some(type_),
}));
let body = if spec.writing {
Self::suite(body)
} else {
Self::reading_body(&spec.name, spec.sample, body)
};
Some(jl::Decl::FunctionDef(jl::FunctionDef {
doc: spec.doc,
name: Ident::new(&spec.name),
params,
returns: None,
body,
}))
}
fn default_into(
ast: &Ast,
id: Id<TypeNode>,
place: Self::Expr,
) -> Result<Self::Stmt, EncodeError> {
Ok(crate::default_julia::default_into(ast, id, place))
}
fn member_default(
ast: &Ast,
node: &MemberNode,
type_: Id<TypeNode>,
place: Self::Expr,
) -> Result<Vec<Self::Stmt>, EncodeError> {
crate::default_julia::member_default(ast, node, type_, place)
}
fn enumerator_constant(ast: &Ast, id: Id<TypeNode>, enumerator: &str) -> Self::Expr {
jl::Expr::name(&format!(
"{}{}",
julia_module_prefix(ast, id),
escape_name(enumerator)
))
}
}
fn signed_char(value: jl::Expr) -> jl::Expr {
let wide = jl::Expr::free_call("Int64", vec![value]);
let wrapped = jl::Expr::Binary {
op: "%",
lhs: Box::new(jl::Expr::Paren(Box::new(jl::Expr::Binary {
op: "+",
lhs: Box::new(wide),
rhs: Box::new(jl::Expr::int(128)),
}))),
rhs: Box::new(jl::Expr::int(256)),
};
jl::Expr::Binary {
op: "-",
lhs: Box::new(jl::Expr::Paren(Box::new(wrapped))),
rhs: Box::new(jl::Expr::int(128)),
}
}
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(),
},
}
}
fn codec_tag(ast: &Ast, id: Id<TypeNode>, depth: usize) -> String {
if depth > MAX_TYPE_DEPTH {
return "deep".to_string();
}
let Ok(node) = ast.type_(id) else {
return "unknown".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),
codec_tag(ast, element, depth + 1)
),
None => format!("seq_unknown{}", bound_tag(ast, bound)),
},
TypeKind::Array { element, dims } => {
let mut name = match element.resolved() {
Some(element) => format!("arr_{}", codec_tag(ast, element, depth + 1)),
None => "arr_unknown".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 { bound, .. } => {
{
let entry = julia_map_entry_name(ast, id);
match bound_tag(ast, bound) {
bound if bound.is_empty() => entry,
bound => format!("map{bound}_{entry}"),
}
}
}
TypeKind::Alias { aliased } => match aliased.resolved() {
Some(target) => codec_tag(ast, target, depth + 1),
None => julia_type_name(ast, id),
},
_ => julia_type_name(ast, id),
}
}