use super::{CodecTarget, Constant, Handle, Handoff, Routine, Scalar};
use crate::adapter::elixir::type_module_path;
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, Path};
use zerodds_lang_cst::elixir::{self as ex, atom_name};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Elixir;
const MAX_TYPE_DEPTH: usize = 64;
const SIGNED_CHAR: &str = "signed char";
const MAP_ENTRY: &str = "_zd_map_entry";
const W_MARK: &str = "ZdW";
const R_MARK: &str = "ZdR";
thread_local! {
static NEXT_TEMPORARY: core::cell::Cell<u64> = const { core::cell::Cell::new(0) };
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum OpShape {
Pure,
Mutates,
MutatesWithValue,
}
fn writer_op(method: &str) -> OpShape {
match method {
"bitfield" => OpShape::Pure,
"write_dheader"
| "write_emheader"
| "write_pid"
| "write_pid_extended"
| "begin_packed_element" => OpShape::MutatesWithValue,
_ => OpShape::Mutates,
}
}
fn reader_op(method: &str) -> OpShape {
match method {
"reader_status" | "reader_position" | "reader_remaining" | "reader_save" | "trim_utf8"
| "trim_utf16" => OpShape::Pure,
"reader_refuse"
| "reader_restore"
| "reader_skip_to"
| "finish_pid_read"
| "reader_finish_packed_element" => OpShape::Mutates,
_ => OpShape::MutatesWithValue,
}
}
impl Elixir {
fn runtime_path(marker: &str, method: &str) -> Path {
let module = if marker == W_MARK { "Writer" } else { "Reader" };
Path::new(&["ZeroddsWire", module, method])
}
fn receiver(marker: &str) -> &'static str {
if marker == W_MARK { "w" } else { "r" }
}
fn fresh(prefix: &str) -> Ident {
let number = NEXT_TEMPORARY.with(|next| {
let number = next.get();
next.set(number.wrapping_add(1));
number
});
Ident::new(&format!("{prefix}{number}"))
}
fn reset_temporaries() {
NEXT_TEMPORARY.with(|next| next.set(0));
}
fn lower(expr: ex::Expr) -> (Vec<ex::Statement>, ex::Expr) {
let mut statements = Vec::new();
let mut temporaries = 0usize;
let value = Self::lower_into(expr, &mut statements, &mut temporaries);
(statements, value)
}
fn lower_into(
expr: ex::Expr,
statements: &mut Vec<ex::Statement>,
temporaries: &mut usize,
) -> ex::Expr {
match expr {
ex::Expr::Call { func, args } => {
let marker = func
.segments()
.first()
.map(|segment| segment.as_str().to_string());
let args: Vec<ex::Expr> = args
.into_iter()
.map(|argument| Self::lower_into(argument, statements, temporaries))
.collect();
let Some(marker) = marker.filter(|m| m == W_MARK || m == R_MARK) else {
return ex::Expr::Call { func, args };
};
let method = func
.segments()
.get(1)
.map(Ident::as_str)
.unwrap_or_default()
.to_string();
let shape = if marker == W_MARK {
writer_op(&method)
} else {
reader_op(&method)
};
let receiver = Self::receiver(&marker);
let mut full_args = vec![ex::Expr::Var(Ident::new(receiver))];
full_args.extend(args);
let call = ex::Expr::Call {
func: Self::runtime_path(&marker, &method),
args: full_args,
};
match shape {
OpShape::Pure => call,
OpShape::Mutates => ex::Expr::Call {
func: Path::single(&format!("zd_unlowered_{method}")),
args: Vec::new(),
},
OpShape::MutatesWithValue => {
*temporaries += 1;
let name = Self::fresh("zd_rv");
statements.push(ex::Statement::BindTuple {
names: vec![name.clone(), Ident::new(receiver)],
value: call,
});
ex::Expr::Var(name)
}
}
}
ex::Expr::Binary { op, lhs, rhs } => {
let lhs = Self::lower_into(*lhs, statements, temporaries);
let rhs = Self::lower_into(*rhs, statements, temporaries);
ex::Expr::Binary {
op,
lhs: Box::new(lhs),
rhs: Box::new(rhs),
}
}
ex::Expr::Unary { op, value } => ex::Expr::Unary {
op,
value: Box::new(Self::lower_into(*value, statements, temporaries)),
},
ex::Expr::Paren(value) => {
ex::Expr::Paren(Box::new(Self::lower_into(*value, statements, temporaries)))
}
ex::Expr::Field { value, name } => ex::Expr::Field {
value: Box::new(Self::lower_into(*value, statements, temporaries)),
name,
},
ex::Expr::Index { value, index } => ex::Expr::Index {
value: Box::new(Self::lower_into(*value, statements, temporaries)),
index: Box::new(Self::lower_into(*index, statements, temporaries)),
},
ex::Expr::IfElse {
condition,
then,
otherwise,
} => ex::Expr::IfElse {
condition: Box::new(Self::lower_into(*condition, statements, temporaries)),
then: Box::new(Self::lower_into(*then, statements, temporaries)),
otherwise: Box::new(Self::lower_into(*otherwise, statements, temporaries)),
},
other @ (ex::Expr::Literal(_)
| ex::Expr::Atom(_)
| ex::Expr::Var(_)
| ex::Expr::Attr(_)
| ex::Expr::Module(_)
| ex::Expr::List(_)
| ex::Expr::Tuple(_)
| ex::Expr::Keyword(_)
| ex::Expr::StructLiteral { .. }) => other,
}
}
fn place_of(place: &ex::Expr) -> Option<(Ident, Vec<ex::Access>)> {
match place {
ex::Expr::Var(name) => Some((name.clone(), Vec::new())),
ex::Expr::Field { value, name } => {
let (base, mut path) = Self::place_of(value)?;
path.push(ex::Access::Key(name.clone()));
Some((base, path))
}
ex::Expr::Index { value, index } => {
let (base, mut path) = Self::place_of(value)?;
path.push(ex::Access::At((**index).clone()));
Some((base, path))
}
_ => None,
}
}
fn refused_place(place: ex::Expr, value: ex::Expr) -> ex::Statement {
ex::Statement::Bind {
name: Ident::new("_"),
value: ex::Expr::Call {
func: Path::single("zd_refused_not_a_place"),
args: vec![place, value],
},
}
}
fn assign(place: ex::Expr, value: ex::Expr) -> Vec<ex::Statement> {
let (mut statements, value) = Self::lower(value);
let Some((base, path)) = Self::place_of(&place) else {
statements.push(Self::refused_place(place, value));
return statements;
};
if path.is_empty() {
statements.push(ex::Statement::Bind { name: base, value });
} else {
statements.push(ex::Statement::Update { base, path, value });
}
statements
}
fn seq(mut statements: Vec<ex::Statement>) -> ex::Statement {
if statements.len() == 1 {
return statements.remove(0);
}
ex::Statement::Seq(statements)
}
fn is_encoder_call(func: &Path) -> bool {
func.segments().len() == 1
&& func.segments().first().is_some_and(|name| {
let name = name.as_str();
name.starts_with("zd_enc_")
|| name.starts_with("zd_kenc_")
|| name.starts_with("zd_keyholder_")
})
}
}
impl CodecTarget for Elixir {
type Expr = ex::Expr;
type Stmt = ex::Statement;
type Block = Vec<ex::Statement>;
type Type = ex::Type;
type Decl = ex::CodecFunction;
fn scalar(kind: Scalar) -> Self::Type {
match kind {
Scalar::Bool => ex::Type::Basic(ex::Basic::Boolean),
Scalar::Char => ex::Type::Atom("char".to_string()),
Scalar::Float | Scalar::Double => ex::Type::Basic(ex::Basic::Float),
Scalar::LongDouble => ex::Type::Atom("float128".to_string()),
Scalar::Int8
| Scalar::UInt8
| Scalar::Int16
| Scalar::UInt16
| Scalar::Int32
| Scalar::UInt32
| Scalar::Int64
| Scalar::UInt64
| Scalar::Index
| Scalar::Size => ex::Type::Basic(ex::Basic::Integer),
}
}
fn named(name: &str) -> Self::Type {
ex::Type::Atom(name.to_string())
}
fn nothing() -> Self::Type {
ex::Type::Basic(ex::Basic::Nil)
}
fn handle(kind: Handle) -> Self::Type {
ex::Type::Atom(
match kind {
Handle::DHeader => "dheader",
Handle::EmHeader => "emheader",
Handle::Pid => "pid",
Handle::TryConstruct => "try_construct",
Handle::ReaderMark => "reader_mark",
Handle::PidRead => "pid_read",
Handle::EmHeaderRead => "emheader_read",
}
.to_string(),
)
}
fn code_point(value: Self::Expr, _narrow: bool) -> Self::Expr {
value
}
fn constant(which: Constant) -> Self::Expr {
let atom = |name: &str| ex::Expr::Atom(name.to_string());
match which {
Constant::WcharWidth2 => ex::Expr::int(2),
Constant::WcharWidth4 => ex::Expr::int(4),
Constant::WcharFixed => atom("fixed"),
Constant::WcharGiopLengthPrefixed => atom("giop-length-prefixed"),
Constant::WstringUnitsStream => atom("stream"),
Constant::WstringUnitsBigEndianAlways => atom("big-endian-always"),
Constant::WstringUnitsByteOrderMark => atom("byte-order-mark"),
Constant::WstringUnitsHostUnswapped => atom("host-unswapped"),
Constant::WstringSpecification => atom("specification"),
Constant::WstringCharacterCount => atom("character-count"),
Constant::WstringCharacterCountTerminated => atom("character-count-terminated"),
Constant::WstringSpecificationWideUnits => atom("specification-wide-units"),
Constant::WstringRosTypeSupport => atom("ros-type-support"),
Constant::WstringOctetsTerminated2 => atom("octets/terminated/2"),
Constant::WstringOctetsTerminated4 => atom("octets/terminated/4"),
Constant::WstringCharactersTerminated2 => atom("characters/terminated/2"),
Constant::FixedDeclared => atom("declared"),
Constant::FixedValuePrecision => atom("value-precision"),
Constant::FixedValueStringDigits => atom("value-string-digits"),
Constant::ParameterMember => atom("member"),
Constant::ParameterOptional => atom("optional"),
Constant::TryConstructDiscard => atom("discard"),
Constant::TryConstructTrim => atom("trim"),
Constant::TryConstructUseDefault => atom("use_default"),
Constant::StatusOk => atom("ok"),
Constant::StatusDiscarded => atom("discarded"),
Constant::StatusImpossibleLength => atom("impossible_length"),
Constant::StatusOutOfRange => atom("out_of_range"),
Constant::StatusTruncated => atom("truncated"),
}
}
fn unsigned_holder(_bits: u16) -> Self::Type {
ex::Type::Basic(ex::Basic::Integer)
}
fn value_type(ast: &Ast, id: Id<TypeNode>) -> Self::Type {
value_type_of(ast, id, 0)
}
fn type_name(ast: &Ast, id: Id<TypeNode>) -> String {
type_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 {
String::new()
}
fn escape(name: &str) -> String {
atom_name(name)
}
fn name(text: &str) -> Self::Expr {
ex::Expr::Var(Ident::new(text))
}
fn int(value: i128) -> Self::Expr {
ex::Expr::int(value)
}
fn wide_int(value: i128) -> Self::Expr {
ex::Expr::int(value)
}
fn wide_unsigned(value: u64) -> Self::Expr {
ex::Expr::int(i128::from(value))
}
fn wide_signed(value: i128) -> Self::Expr {
ex::Expr::int(value)
}
fn unsigned_int(value: i128) -> Self::Expr {
ex::Expr::int(value)
}
fn literal(value: Literal) -> Self::Expr {
let Literal::Int(text) = &value else {
return ex::Expr::Literal(value);
};
match text.as_str() {
"true" => ex::Expr::Literal(Literal::Bool(true)),
"false" => ex::Expr::Literal(Literal::Bool(false)),
_ => ex::Expr::Literal(value),
}
}
fn call(callee: &str, args: Vec<Self::Expr>) -> Self::Expr {
if callee == "strcmp" {
return strcmp_sign(args);
}
ex::Expr::Call {
func: Path::single(callee),
args,
}
}
fn runtime(receiver: &str, method: &str, args: Vec<Self::Expr>) -> Self::Expr {
let marker = if receiver == "w" { W_MARK } else { R_MARK };
ex::Expr::Call {
func: Path::new(&[marker, method]),
args,
}
}
fn runtime_static(method: &str, args: Vec<Self::Expr>) -> Self::Expr {
ex::Expr::Call {
func: Path::new(&["ZeroddsWire", method]),
args,
}
}
fn carries_hdds_axes() -> bool {
true
}
fn member(base: &str, field: &str) -> Self::Expr {
let mut place = Self::name(base);
for segment in field.split('.') {
place = ex::Expr::Field {
value: Box::new(place),
name: Ident::new(segment),
};
}
place
}
fn inherited_path(depth: usize) -> Option<String> {
Some(format!("{}.", atom_name("parent")).repeat(depth))
}
fn whole(base: &str) -> Self::Expr {
Self::name(base)
}
fn field_of(value: Self::Expr, field: &str) -> Self::Expr {
ex::Expr::Field {
value: Box::new(value),
name: Ident::new(field),
}
}
fn header_field(value: Self::Expr, field: &str) -> Self::Expr {
let named = if field == "end" { "list_end" } else { field };
Self::field_of(value, named)
}
fn index(value: Self::Expr, at: Self::Expr) -> Self::Expr {
ex::Expr::Index {
value: Box::new(value),
index: Box::new(at),
}
}
fn count_of(base: &str, fixed: Option<u64>) -> Self::Expr {
match fixed {
Some(bound) => ex::Expr::int(i128::from(bound)),
None => ex::Expr::Call {
func: Path::single("length"),
args: vec![Self::name(base)],
},
}
}
fn elements(base: &str) -> Self::Expr {
Self::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 {
ex::Expr::Index {
value: Box::new(entry),
index: Box::new(ex::Expr::int(i128::from(!key))),
}
}
fn absent() -> Self::Expr {
ex::Expr::nil()
}
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 {
ex::Expr::Binary {
op: "!=",
lhs: Box::new(Self::member(base, field)),
rhs: Box::new(Self::absent()),
}
}
fn open_array(base: &str, lengths: &[i128]) -> Vec<Self::Stmt> {
vec![ex::Statement::Bind {
name: Ident::new(base),
value: Self::runtime_static(
"array_to_lists",
vec![Self::whole(base), ex::Expr::int(lengths.len() as i128)],
),
}]
}
fn close_array(base: &str, lengths: &[i128]) -> Vec<Self::Stmt> {
vec![ex::Statement::Bind {
name: Ident::new(base),
value: Self::runtime_static(
"array_to_tuples",
vec![Self::whole(base), ex::Expr::int(lengths.len() as i128)],
),
}]
}
fn set_present(base: &str, field: &str, present: bool) -> Vec<Self::Stmt> {
if present {
return Vec::new();
}
vec![ex::Statement::Update {
base: Ident::new(base),
path: vec![ex::Access::Key(Ident::new(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 {
ex::Expr::Binary {
op: match op {
"&&" => "and",
"||" => "or",
"&" => "&&&",
"|" => "|||",
">>" => ">>>",
"<<" => "<<<",
other => other,
},
lhs: Box::new(lhs),
rhs: Box::new(rhs),
}
}
fn unary(op: &'static str, value: Self::Expr) -> Self::Expr {
if op == "~" {
return ex::Expr::Call {
func: Path::new(&["Bitwise", "bnot"]),
args: vec![value],
};
}
ex::Expr::Unary {
op: match op {
"!" => "not ",
other => other,
},
value: Box::new(value),
}
}
fn paren(value: Self::Expr) -> Self::Expr {
ex::Expr::Paren(Box::new(value))
}
fn cast(type_: Self::Type, value: Self::Expr) -> Self::Expr {
if type_ == Self::named(SIGNED_CHAR) {
return signed_char(value);
}
let ex::Expr::Literal(Literal::Int(text)) = &value else {
return value;
};
let digits = text.strip_prefix('-').unwrap_or(text);
if digits.is_empty() || !digits.bytes().all(|byte| byte.is_ascii_digit()) {
return value;
}
if type_ == Self::scalar(Scalar::Char) {
return text
.parse::<u32>()
.ok()
.filter(|code| *code <= u32::from(u16::MAX))
.map_or_else(
|| value.clone(),
|code| match char::from_u32(code) {
Some(point) => ex::Expr::Literal(Literal::Str(point.to_string())),
None => ex::Expr::Atom(format!("zd_surrogate_{code:x}")),
},
);
}
if type_ == Self::scalar(Scalar::Bool) {
return ex::Expr::Literal(Literal::Bool(text != "0"));
}
value
}
fn handoff(value: Self::Expr, _form: Handoff) -> Self::Expr {
value
}
fn declare(_type: Self::Type, name: &str, value: Option<Self::Expr>) -> Self::Stmt {
match value {
Some(value) => Self::seq(Self::assign(Self::name(name), value)),
None => ex::Statement::Bind {
name: Ident::new(name),
value: Self::absent(),
},
}
}
fn perform(value: Self::Expr) -> Self::Stmt {
if let ex::Expr::Binary { op: "=", lhs, rhs } = value {
return Self::seq(Self::assign(*lhs, *rhs));
}
if let ex::Expr::Call { func, args } = &value {
let head = func.segments().first().map(Ident::as_str);
if head == Some(W_MARK) || head == Some(R_MARK) {
let marker = head.unwrap_or_default().to_string();
let method = func
.segments()
.get(1)
.map(Ident::as_str)
.unwrap_or_default()
.to_string();
let shape = if marker == W_MARK {
writer_op(&method)
} else {
reader_op(&method)
};
let receiver = Self::receiver(&marker);
let mut statements = Vec::new();
let mut temporaries = 0usize;
let args: Vec<ex::Expr> = args
.iter()
.cloned()
.map(|argument| Self::lower_into(argument, &mut statements, &mut temporaries))
.collect();
let mut full_args = vec![ex::Expr::Var(Ident::new(receiver))];
full_args.extend(args);
let call = ex::Expr::Call {
func: Self::runtime_path(&marker, &method),
args: full_args,
};
statements.push(match shape {
OpShape::Mutates => ex::Statement::Bind {
name: Ident::new(receiver),
value: call,
},
OpShape::MutatesWithValue => ex::Statement::BindTuple {
names: vec![Ident::new("_"), Ident::new(receiver)],
value: call,
},
OpShape::Pure => ex::Statement::Bind {
name: Ident::new("_"),
value: call,
},
});
return Self::seq(statements);
}
if Self::is_encoder_call(func) {
let (mut statements, lowered) = Self::lower(value.clone());
statements.push(ex::Statement::Bind {
name: Ident::new("w"),
value: lowered,
});
return Self::seq(statements);
}
}
let (mut statements, lowered) = Self::lower(value);
statements.push(ex::Statement::Bind {
name: Ident::new("_"),
value: lowered,
});
Self::seq(statements)
}
fn give_back(value: Option<Self::Expr>) -> Self::Stmt {
ex::Statement::Return(ex::Expr::Tuple(vec![
value.unwrap_or_else(|| Self::name("v")),
Self::name("r"),
]))
}
fn when(
condition: Self::Expr,
then: Self::Block,
otherwise: Option<Self::Block>,
) -> Self::Stmt {
let (mut statements, condition) = Self::lower(condition);
statements.push(ex::Statement::If {
condition,
then,
otherwise: otherwise.unwrap_or_default(),
});
Self::seq(statements)
}
fn count_up(counter: &str, bound: Self::Expr, body: Self::Block) -> Self::Stmt {
let (mut statements, bound) = Self::lower(bound);
statements.push(ex::Statement::For {
counter: Ident::new(counter),
bound,
body,
});
Self::seq(statements)
}
fn until(condition: Self::Expr, body: Self::Block) -> Self::Stmt {
let (mut statements, condition) = Self::lower(condition);
if !statements.is_empty() {
statements.push(ex::Statement::Bind {
name: Ident::new("_"),
value: ex::Expr::Call {
func: Path::single("zd_refused_stateful_loop_condition"),
args: Vec::new(),
},
});
}
statements.push(ex::Statement::While { condition, body });
Self::seq(statements)
}
fn leave() -> Self::Stmt {
ex::Statement::Break
}
fn note(doc: Doc) -> Self::Stmt {
ex::Statement::Comment(doc)
}
fn drop_value(value: Self::Expr) -> Self::Stmt {
Self::perform(value)
}
fn mark_used(name: &str) -> Vec<Self::Stmt> {
vec![ex::Statement::Bind {
name: Ident::new("_"),
value: Self::name(name),
}]
}
fn read_call(
callee: &str,
place: Self::Expr,
_form: Handoff,
behaviour: Self::Expr,
) -> Self::Stmt {
let call = ex::Expr::Call {
func: Path::single(callee),
args: vec![Self::name("r"), behaviour],
};
let Some((base, path)) = Self::place_of(&place) else {
return Self::refused_place(place, call);
};
if path.is_empty() {
return ex::Statement::BindTuple {
names: vec![base, Ident::new("r")],
value: call,
};
}
let temporary = Self::fresh("zd_rd");
Self::seq(vec![
ex::Statement::BindTuple {
names: vec![temporary.clone(), Ident::new("r")],
value: call,
},
ex::Statement::Update {
base,
path,
value: ex::Expr::Var(temporary),
},
])
}
fn read_forward(callee: &str) -> Self::Stmt {
ex::Statement::BindTuple {
names: vec![Ident::new("v"), Ident::new("r")],
value: ex::Expr::Call {
func: Path::single(callee),
args: vec![Self::name("r"), Self::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 slot = if assigned_whole {
Self::absent()
} else {
ex::Expr::List(vec![Self::absent(), Self::absent()])
};
let mut out = vec![ex::Statement::Bind {
name: Ident::new(base),
value: ex::Expr::Call {
func: Path::new(&["List", "duplicate"]),
args: vec![slot, count],
},
}];
out.extend(fill);
out
}
fn keep_first(base: &str, count: Self::Expr) -> Vec<Self::Stmt> {
vec![ex::Statement::Bind {
name: Ident::new(base),
value: ex::Expr::Call {
func: Path::new(&["Enum", "take"]),
args: vec![Self::name(base), 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_ == ex::Type::Atom(MAP_ENTRY.to_string()) {
return ex::Statement::Bind {
name: Ident::new(name),
value: ex::Expr::List(vec![Self::absent(), Self::absent()]),
};
}
Self::declare(type_, name, None)
}
fn routine(spec: Routine<Self>) -> Option<Self::Decl> {
Self::reset_temporaries();
let body = spec.body?;
let mut params = Vec::new();
if spec.writing {
params.push(Ident::new("w"));
params.push(Ident::new("v"));
} else {
params.push(Ident::new("r"));
}
params.extend(spec.extra.iter().map(|(_, name)| Ident::new(name)));
let (body, result) = if spec.writing {
let mut statements = vec![ex::Statement::Bind {
name: Ident::new("_"),
value: Self::name("v"),
}];
statements.extend(body);
(statements, Self::name("w"))
} else {
let defaulter = crate::default_elixir::defaulter_from_decoder(&spec.name);
let mut statements = vec![
ex::Statement::Bind {
name: Ident::new("v"),
value: ex::Expr::Call {
func: Path::single(&defaulter),
args: Vec::new(),
},
},
ex::Statement::Bind {
name: Ident::new("_"),
value: Self::name("v"),
},
];
statements.extend(body);
(
statements,
ex::Expr::Tuple(vec![Self::name("v"), Self::name("r")]),
)
};
Some(ex::CodecFunction {
doc: spec.doc,
name: Ident::new(&spec.name),
params,
body,
result,
})
}
fn default_into(
ast: &Ast,
id: Id<TypeNode>,
place: Self::Expr,
) -> Result<Self::Stmt, EncodeError> {
Ok(crate::default_elixir::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_elixir::member_default(ast, node, type_, place)
}
fn enumerator_constant(ast: &Ast, id: Id<TypeNode>, enumerator: &str) -> Self::Expr {
let module = type_module_path(ast, id);
let mut segments: Vec<String> = module
.segments()
.iter()
.map(|segment| segment.as_str().to_string())
.collect();
segments.push(atom_name(enumerator));
ex::Expr::Call {
func: Path::new(&segments.iter().map(String::as_str).collect::<Vec<&str>>()),
args: Vec::new(),
}
}
}
fn signed_char(value: ex::Expr) -> ex::Expr {
let point = ex::Expr::Call {
func: Path::single("hd"),
args: vec![ex::Expr::Call {
func: Path::new(&["String", "to_charlist"]),
args: vec![value],
}],
};
let wrapped = ex::Expr::Call {
func: Path::single("rem"),
args: vec![
ex::Expr::Binary {
op: "+",
lhs: Box::new(point),
rhs: Box::new(ex::Expr::int(128)),
},
ex::Expr::int(256),
],
};
ex::Expr::Binary {
op: "-",
lhs: Box::new(ex::Expr::Paren(Box::new(wrapped))),
rhs: Box::new(ex::Expr::int(128)),
}
}
fn strcmp_sign(args: Vec<ex::Expr>) -> ex::Expr {
let [left, right] = <[ex::Expr; 2]>::try_from(args).unwrap_or_else(|args| {
[
ex::Expr::Call {
func: Path::single("strcmp"),
args,
},
ex::Expr::int(0),
]
});
let side = |op: &'static str| {
ex::Expr::Paren(Box::new(ex::Expr::IfElse {
condition: Box::new(ex::Expr::Binary {
op,
lhs: Box::new(left.clone()),
rhs: Box::new(right.clone()),
}),
then: Box::new(ex::Expr::int(1)),
otherwise: Box::new(ex::Expr::int(0)),
}))
};
ex::Expr::Paren(Box::new(ex::Expr::Binary {
op: "-",
lhs: Box::new(side(">")),
rhs: Box::new(side("<")),
}))
}
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(),
},
}
}
pub(crate) fn type_tag(ast: &Ast, id: Id<TypeNode>, depth: usize) -> String {
if depth > MAX_TYPE_DEPTH {
return "unknown".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),
type_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_{}", type_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 { key, value, bound } => {
let half = |half: Option<Id<TypeNode>>| match half {
Some(target) => type_tag(ast, target, depth + 1),
None => "unknown".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 => "unknown".to_string(),
},
TypeKind::ValueBox { boxed } => match boxed.resolved() {
Some(target) => format!("box_{}", type_tag(ast, target, depth + 1)),
None => "box_unknown".to_string(),
},
TypeKind::Structure { .. }
| TypeKind::Union { .. }
| TypeKind::Enumeration { .. }
| TypeKind::Bitmask { .. }
| TypeKind::Bitset { .. }
| TypeKind::Valuetype { .. }
| TypeKind::Native => type_module_path(ast, id)
.segments()
.iter()
.map(|segment| segment.as_str())
.collect::<Vec<&str>>()
.join("_"),
TypeKind::Any => "any".to_string(),
TypeKind::ValueBase => "valuebase".to_string(),
TypeKind::ObjectRef { .. } => "objectref".to_string(),
TypeKind::AnnotationType { .. } => "annotation".to_string(),
}
}
fn value_type_of(ast: &Ast, id: Id<TypeNode>, depth: usize) -> ex::Type {
use zerodds_ast::PrimitiveKind;
let unknown = || ex::Type::Basic(ex::Basic::Any);
if depth > MAX_TYPE_DEPTH {
return unknown();
}
let Ok(node) = ast.type_(id) else {
return unknown();
};
let inner = |half: Option<Id<TypeNode>>| {
half.map_or_else(unknown, |target| value_type_of(ast, target, depth + 1))
};
match &node.kind {
TypeKind::Primitive(kind) => match kind {
PrimitiveKind::Boolean => Elixir::scalar(Scalar::Bool),
PrimitiveKind::Char | PrimitiveKind::WChar => Elixir::scalar(Scalar::Char),
PrimitiveKind::Float32 | PrimitiveKind::Float64 => Elixir::scalar(Scalar::Double),
PrimitiveKind::Float128 => Elixir::scalar(Scalar::LongDouble),
_ => Elixir::scalar(Scalar::Int32),
},
TypeKind::String { .. } | TypeKind::Fixed { .. } => ex::Type::Basic(ex::Basic::Binary),
TypeKind::Enumeration { .. } | TypeKind::Bitmask { .. } => {
ex::Type::Basic(ex::Basic::Integer)
}
TypeKind::Bitset { .. }
| TypeKind::Structure { .. }
| TypeKind::Union { .. }
| TypeKind::Valuetype { .. }
| TypeKind::Native => ex::Type::Atom(type_tag(ast, id, depth)),
TypeKind::Sequence { element, .. } => ex::Type::list(inner(element.resolved())),
TypeKind::Array { element, dims } => {
let mut out = inner(element.resolved());
for _ in dims {
out = ex::Type::list(out);
}
out
}
TypeKind::Map { key, value, .. } => ex::Type::list(ex::Type::Tuple(vec![
inner(key.resolved()),
inner(value.resolved()),
])),
TypeKind::Alias { aliased } => inner(aliased.resolved()),
TypeKind::ValueBox { boxed } => inner(boxed.resolved()).or_nil(),
TypeKind::Any
| TypeKind::ValueBase
| TypeKind::ObjectRef { .. }
| TypeKind::AnnotationType { .. } => unknown(),
}
}