use super::{CodecTarget, Constant, Handle, Handoff, Routine, Scalar};
use crate::adapter::fsharp::{
MODULE_NAME, field_name, fsharp_map_entry_name, fsharp_type_name, fsharp_value_type, typed_int,
};
use crate::default_fsharp;
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::fsharp;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Fsharp;
const MAX_TYPE_DEPTH: usize = 64;
const RUNTIME: &str = "ZdWire";
const SIGNED_CHAR: &str = "signed char";
#[derive(Debug, Clone)]
pub enum FsharpExpr {
Plain(fsharp::Expr),
OptionalSlot(fsharp::Expr),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FsharpType {
pub tree: fsharp::Type,
pub enum_like: bool,
pub bitmask_holder: Option<fsharp::Primitive>,
}
impl FsharpType {
fn plain(tree: fsharp::Type) -> Self {
Self {
tree,
enum_like: false,
bitmask_holder: None,
}
}
}
fn shift_amount(value: fsharp::Expr) -> fsharp::Expr {
if let fsharp::Expr::Literal(Literal::Int(digits)) = &value {
let bare = digits.trim_end_matches(char::is_alphabetic);
if !bare.is_empty() && bare != "-" {
return fsharp::Expr::Literal(Literal::Int(bare.to_string()));
}
}
fsharp::Expr::call("int", vec![value])
}
fn plain(e: fsharp::Expr) -> FsharpExpr {
FsharpExpr::Plain(e)
}
fn inner(expr: FsharpExpr) -> fsharp::Expr {
match expr {
FsharpExpr::Plain(e) | FsharpExpr::OptionalSlot(e) => e,
}
}
impl Fsharp {
fn runtime_name(name: &str) -> fsharp::Expr {
fsharp::Expr::dot(fsharp::Expr::Name(Ident::new(RUNTIME)), name)
}
fn pascal(method: &str) -> String {
let mut out = String::new();
for segment in method.split('_') {
let mut chars = segment.chars();
if let Some(first) = chars.next() {
out.extend(first.to_uppercase());
out.push_str(chars.as_str());
}
}
out
}
fn camel(method: &str) -> String {
let mut out = String::new();
for (index, segment) in method.split('_').enumerate() {
if index == 0 {
out.push_str(segment);
continue;
}
let mut chars = segment.chars();
if let Some(first) = chars.next() {
out.extend(first.to_uppercase());
out.push_str(chars.as_str());
}
}
out
}
fn text(value: &str) -> FsharpExpr {
plain(fsharp::Expr::Literal(Literal::Str(value.to_string())))
}
fn runtime_case(type_: &str, case: &str) -> FsharpExpr {
let _ = type_;
plain(Self::runtime_name(case))
}
fn is_enumeration(ast: &Ast, id: Id<TypeNode>) -> bool {
let mut current = id;
for _ in 0..MAX_TYPE_DEPTH {
match ast.type_(current).map(|node| node.kind.clone()) {
Ok(TypeKind::Enumeration { .. }) => return true,
Ok(TypeKind::Alias { aliased }) => match aliased.resolved() {
Some(target) => current = target,
None => return false,
},
_ => return false,
}
}
false
}
fn bitmask_holder(ast: &Ast, id: Id<TypeNode>) -> Option<fsharp::Primitive> {
let mut current = id;
for _ in 0..MAX_TYPE_DEPTH {
match ast.type_(current).map(|node| node.kind.clone()) {
Ok(TypeKind::Bitmask { .. }) => {
let bound = ast.effective_bit_bound(current);
return Some(crate::adapter::fsharp::fsharp_holder(bound));
}
Ok(TypeKind::Alias { aliased }) => match aliased.resolved() {
Some(target) => current = target,
None => return None,
},
_ => return None,
}
}
None
}
fn widen_literal(tree: &fsharp::Type, value: fsharp::Expr) -> fsharp::Expr {
let (fsharp::Type::Primitive(primitive), fsharp::Expr::Literal(Literal::Int(_))) =
(tree, &value)
else {
return value;
};
typed_int(*primitive, value)
}
fn empty_value(tree: &fsharp::Type) -> fsharp::Expr {
match tree {
fsharp::Type::Primitive(primitive) => match primitive {
fsharp::Primitive::Bool => fsharp::Expr::Literal(Literal::Bool(false)),
fsharp::Primitive::Float32 | fsharp::Primitive::Double => {
fsharp::Expr::Literal(Literal::Float("0.0".to_string()))
}
fsharp::Primitive::String => fsharp::Expr::Literal(Literal::Str(String::new())),
fsharp::Primitive::Unit => fsharp::Expr::Name(Ident::new("()")),
other => typed_int(*other, fsharp::Expr::int(0)),
},
fsharp::Type::Named(name) if name.as_str() == "ZdWire.PidRead" => {
Self::runtime_nothing("noPidRead")
}
fsharp::Type::Named(name) if name.as_str() == "ZdWire.EmHeaderRead" => {
Self::runtime_nothing("noEmheaderRead")
}
_ => fsharp::Expr::None_,
}
}
fn runtime_nothing(name: &str) -> fsharp::Expr {
fsharp::Expr::CurriedCall {
func: Box::new(Self::runtime_name(name)),
args: Vec::new(),
}
}
fn integer_conversion(tree: &fsharp::Type) -> Option<&'static str> {
let fsharp::Type::Primitive(primitive) = tree else {
return None;
};
use fsharp::Primitive as P;
Some(match primitive {
P::SByte => "sbyte",
P::Byte => "byte",
P::Int16 => "int16",
P::UInt16 => "uint16",
P::Int32 => "int32",
P::UInt32 => "uint32",
P::Int64 => "int64",
P::UInt64 => "uint64",
_ => return None,
})
}
}
impl CodecTarget for Fsharp {
type Expr = FsharpExpr;
type Stmt = fsharp::Stmt;
type Block = Vec<fsharp::Stmt>;
type Type = FsharpType;
type Decl = fsharp::Decl;
fn scalar(kind: Scalar) -> Self::Type {
use fsharp::Primitive as P;
FsharpType::plain(match kind {
Scalar::Bool => fsharp::Type::Primitive(P::Bool),
Scalar::Char | Scalar::UInt8 => fsharp::Type::Primitive(P::Byte),
Scalar::Int8 => fsharp::Type::Primitive(P::SByte),
Scalar::Int16 => fsharp::Type::Primitive(P::Int16),
Scalar::UInt16 => fsharp::Type::Primitive(P::UInt16),
Scalar::Int32 => fsharp::Type::Primitive(P::Int32),
Scalar::UInt32 | Scalar::Index => fsharp::Type::Primitive(P::UInt32),
Scalar::Int64 => fsharp::Type::Primitive(P::Int64),
Scalar::UInt64 => fsharp::Type::Primitive(P::UInt64),
Scalar::Float => fsharp::Type::Primitive(P::Float32),
Scalar::Double => fsharp::Type::Primitive(P::Double),
Scalar::LongDouble => fsharp::Type::text(),
Scalar::Size => fsharp::Type::Primitive(P::Int32),
})
}
fn named(name: &str) -> Self::Type {
FsharpType::plain(fsharp::Type::named(name))
}
fn nothing() -> Self::Type {
FsharpType::plain(fsharp::Type::Primitive(fsharp::Primitive::Unit))
}
fn handle(kind: Handle) -> Self::Type {
FsharpType::plain(fsharp::Type::named(match kind {
Handle::DHeader => "ZdWire.DHeader",
Handle::EmHeader => "ZdWire.EmHeader",
Handle::Pid => "ZdWire.Pid",
Handle::TryConstruct => "ZdWire.TryConstruct",
Handle::ReaderMark => "ZdWire.ReaderMark",
Handle::PidRead => "ZdWire.PidRead",
Handle::EmHeaderRead => "ZdWire.EmHeaderRead",
}))
}
fn code_point(value: Self::Expr, narrow: bool) -> Self::Expr {
if narrow {
return value;
}
plain(fsharp::Expr::call("int", vec![inner(value)]))
}
fn constant(which: Constant) -> Self::Expr {
match which {
Constant::WcharWidth2 => plain(fsharp::Expr::int(2)),
Constant::WcharWidth4 => plain(fsharp::Expr::int(4)),
Constant::WcharFixed => plain(fsharp::Expr::Literal(Literal::Bool(false))),
Constant::WcharGiopLengthPrefixed => plain(fsharp::Expr::Literal(Literal::Bool(true))),
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 => Self::runtime_case("ParameterRole", "Member"),
Constant::ParameterOptional => Self::runtime_case("ParameterRole", "Optional"),
Constant::TryConstructDiscard => Self::runtime_case("TryConstruct", "Discard"),
Constant::TryConstructTrim => Self::runtime_case("TryConstruct", "Trim"),
Constant::TryConstructUseDefault => Self::runtime_case("TryConstruct", "UseDefault"),
Constant::StatusOk => Self::runtime_case("Status", "Ok"),
Constant::StatusDiscarded => Self::runtime_case("Status", "Discarded"),
Constant::StatusImpossibleLength => Self::runtime_case("Status", "ImpossibleLength"),
Constant::StatusOutOfRange => Self::runtime_case("Status", "OutOfRange"),
Constant::StatusTruncated => Self::runtime_case("Status", "Truncated"),
}
}
fn unsigned_holder(bits: u16) -> Self::Type {
FsharpType::plain(fsharp::Type::Primitive(
crate::adapter::fsharp::fsharp_holder(bits),
))
}
fn carries_hdds_axes() -> bool {
true
}
fn value_type(ast: &Ast, id: Id<TypeNode>) -> Self::Type {
let tree = fsharp_value_type(ast, id, "codec")
.unwrap_or_else(|_| fsharp::Type::named("ZdNoFsharpForm"));
FsharpType {
tree,
enum_like: Self::is_enumeration(ast, id),
bitmask_holder: Self::bitmask_holder(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 {
fsharp_map_entry_name(ast, id).unwrap_or_else(|_| format!("ZdUnresolvedEntry{}", id.slot()))
}
fn module_prefix(_ast: &Ast, _id: Id<TypeNode>) -> String {
String::new()
}
fn escape(name: &str) -> String {
field_name(name)
}
fn inherited_path(depth: usize) -> Option<String> {
Some(format!("{}.", field_name("parent")).repeat(depth))
}
fn name(text: &str) -> Self::Expr {
plain(fsharp::Expr::Name(Ident::new(text)))
}
fn int(value: i128) -> Self::Expr {
plain(fsharp::Expr::int(value))
}
fn literal(value: Literal) -> Self::Expr {
let Literal::Int(text) = &value else {
return plain(fsharp::Expr::Literal(value));
};
match text.as_str() {
"true" => plain(fsharp::Expr::Literal(Literal::Bool(true))),
"false" => plain(fsharp::Expr::Literal(Literal::Bool(false))),
_ => plain(fsharp::Expr::Literal(value)),
}
}
fn wide_int(value: i128) -> Self::Expr {
if let Ok(narrow) = i64::try_from(value) {
plain(typed_int(
fsharp::Primitive::Int64,
fsharp::Expr::int(narrow.into()),
))
} else {
plain(typed_int(
fsharp::Primitive::UInt64,
fsharp::Expr::int(value),
))
}
}
fn unsigned_int(value: i128) -> Self::Expr {
plain(typed_int(
fsharp::Primitive::UInt32,
fsharp::Expr::int(value),
))
}
fn wide_unsigned(value: u64) -> Self::Expr {
plain(typed_int(
fsharp::Primitive::UInt64,
fsharp::Expr::int(i128::from(value)),
))
}
fn wide_signed(value: i128) -> Self::Expr {
plain(typed_int(
fsharp::Primitive::Int64,
fsharp::Expr::int(value),
))
}
fn member(base: &str, field: &str) -> Self::Expr {
plain(fsharp::Expr::dot(
fsharp::Expr::Name(Ident::new(base)),
field,
))
}
fn whole(base: &str) -> Self::Expr {
plain(fsharp::Expr::Name(Ident::new(base)))
}
fn field_of(value: Self::Expr, field: &str) -> Self::Expr {
plain(fsharp::Expr::dot(inner(value), field))
}
fn header_field(value: Self::Expr, field: &str) -> Self::Expr {
plain(fsharp::Expr::dot(
inner(value),
match field {
"pid" | "member_id" => "Id",
"end" => "ListEnd",
"ignore" => "Ignore",
"length" => "Length",
"must_understand" => "MustUnderstand",
"extent" => "Extent",
other => other,
},
))
}
fn index(value: Self::Expr, at: Self::Expr) -> Self::Expr {
plain(fsharp::Expr::Index {
value: Box::new(inner(value)),
index: Box::new(fsharp::Expr::call("int", vec![inner(at)])),
})
}
fn count_of(base: &str, fixed: Option<u64>) -> Self::Expr {
match fixed {
Some(bound) => plain(typed_int(
fsharp::Primitive::UInt32,
fsharp::Expr::int(i128::from(bound)),
)),
None => plain(fsharp::Expr::call(
"uint32",
vec![fsharp::Expr::dot(
fsharp::Expr::Name(Ident::new(base)),
"Length",
)],
)),
}
}
fn elements(base: &str) -> Self::Expr {
plain(fsharp::Expr::Name(Ident::new(base)))
}
fn case_member(base: &str, field: &str) -> Self::Expr {
Self::member(base, field)
}
fn entry_half(entry: Self::Expr, key: bool) -> Self::Expr {
plain(fsharp::Expr::dot(
inner(entry),
if key { "Key" } else { "Value" },
))
}
fn absent() -> Self::Expr {
plain(fsharp::Expr::None_)
}
fn materialised(_value: Self::Expr) -> Option<Self::Expr> {
None
}
fn optional_value(_ast: &Ast, _id: Id<TypeNode>, base: &str, field: &str) -> Self::Expr {
plain(fsharp::Expr::dot(inner(Self::member(base, field)), "Value"))
}
fn present(base: &str, field: &str) -> Self::Expr {
plain(fsharp::Expr::dot(
inner(Self::member(base, field)),
"IsSome",
))
}
fn set_present(base: &str, field: &str, present: bool) -> Vec<Self::Stmt> {
if present {
return Vec::new();
}
vec![fsharp::Stmt::Assign {
place: inner(Self::member(base, field)),
value: fsharp::Expr::None_,
}]
}
fn optional_destination(base: &str, field: &str) -> (Vec<Self::Stmt>, Self::Expr) {
(
Vec::new(),
FsharpExpr::OptionalSlot(inner(Self::member(base, field))),
)
}
fn binary(op: &'static str, lhs: Self::Expr, rhs: Self::Expr) -> Self::Expr {
let spelled = match op {
"<<" => "<<<",
">>" => ">>>",
"&" => "&&&",
"|" => "|||",
"^" => "^^^",
"==" => "=",
"!=" => "<>",
other => other,
};
let right = match op {
"<<" | ">>" => shift_amount(inner(rhs)),
_ => inner(rhs),
};
plain(fsharp::Expr::Binary {
op: spelled,
lhs: Box::new(inner(lhs)),
rhs: Box::new(right),
})
}
fn unary(op: &'static str, value: Self::Expr) -> Self::Expr {
let operand = inner(value);
let spelled = match op {
"!" => "not ",
"~" => "~~~",
other => other,
};
plain(fsharp::Expr::Unary {
op: spelled,
value: Box::new(operand),
})
}
fn paren(value: Self::Expr) -> Self::Expr {
plain(fsharp::Expr::Paren(Box::new(inner(value))))
}
fn cast(type_: Self::Type, value: Self::Expr) -> Self::Expr {
let operand = inner(value);
if type_.tree == fsharp::Type::named(SIGNED_CHAR) {
return plain(fsharp::Expr::call("sbyte", vec![operand]));
}
if type_.tree == fsharp::Type::Primitive(fsharp::Primitive::Bool) {
if let fsharp::Expr::Literal(Literal::Int(text)) = &operand {
return plain(fsharp::Expr::Literal(Literal::Bool(text != "0")));
}
}
if type_.enum_like {
let fsharp::Type::Named(name) = &type_.tree else {
return plain(operand);
};
return plain(fsharp::Expr::call(
&format!("enum<{}>", name.as_str()),
vec![fsharp::Expr::call("int", vec![operand])],
));
}
if type_.tree == fsharp::Type::Primitive(fsharp::Primitive::Int64) {
return plain(fsharp::Expr::Call {
func: Box::new(Self::runtime_name("asI64")),
args: vec![operand],
});
}
if type_.tree == fsharp::Type::Primitive(fsharp::Primitive::UInt64) {
return plain(fsharp::Expr::Call {
func: Box::new(Self::runtime_name("asU64")),
args: vec![operand],
});
}
if let Some(conversion) = Self::integer_conversion(&type_.tree) {
return plain(fsharp::Expr::call(conversion, vec![operand]));
}
if let Some(holder) = type_.bitmask_holder {
return plain(typed_int(holder, operand));
}
plain(operand)
}
fn handoff(value: Self::Expr, _form: Handoff) -> Self::Expr {
value
}
fn call(callee: &str, args: Vec<Self::Expr>) -> Self::Expr {
let arguments: Vec<fsharp::Expr> = args.into_iter().map(inner).collect();
if callee == "strcmp" {
return plain(fsharp::Expr::CurriedCall {
func: Box::new(Self::runtime_name("strcmp")),
args: arguments,
});
}
plain(fsharp::Expr::curried_call(callee, arguments))
}
fn runtime(receiver: &str, method: &str, args: Vec<Self::Expr>) -> Self::Expr {
plain(fsharp::Expr::Call {
func: Box::new(fsharp::Expr::dot(
fsharp::Expr::Name(Ident::new(receiver)),
&Self::pascal(method),
)),
args: args.into_iter().map(inner).collect(),
})
}
fn runtime_static(method: &str, args: Vec<Self::Expr>) -> Self::Expr {
if method == "pid_is_user_member" {
let mut arguments = args.into_iter().map(inner);
if let Some(header) = arguments.next() {
return plain(fsharp::Expr::dot(header, "IsUserMember"));
}
return plain(fsharp::Expr::dot(
Self::runtime_name("PidRead"),
"IsUserMember",
));
}
let call = fsharp::Expr::CurriedCall {
func: Box::new(Self::runtime_name(&Self::camel(method))),
args: args.into_iter().map(inner).collect(),
};
plain(call)
}
fn declare(type_: Self::Type, name: &str, value: Option<Self::Expr>) -> Self::Stmt {
let value = match value {
Some(value) => Self::widen_literal(&type_.tree, inner(value)),
None => Self::empty_value(&type_.tree),
};
fsharp::Stmt::Let {
name: Ident::new(name),
type_: Some(type_.tree),
value,
mutable: false,
}
}
fn perform(value: Self::Expr) -> Self::Stmt {
match value {
FsharpExpr::Plain(fsharp::Expr::Binary { op: "=", lhs, rhs }) => fsharp::Stmt::Assign {
place: *lhs,
value: *rhs,
},
other => fsharp::Stmt::Expr(inner(other)),
}
}
fn give_back(value: Option<Self::Expr>) -> Self::Stmt {
fsharp::Stmt::Return(match value {
Some(value) => inner(value),
None => fsharp::Expr::Name(Ident::new("v")),
})
}
fn when(
condition: Self::Expr,
then: Self::Block,
otherwise: Option<Self::Block>,
) -> Self::Stmt {
fsharp::Stmt::If {
condition: inner(condition),
then,
otherwise: otherwise.filter(|body| !body.is_empty()),
}
}
fn count_up(counter: &str, bound: Self::Expr, body: Self::Block) -> Self::Stmt {
let mut inside = Vec::with_capacity(body.len() + 1);
inside.push(fsharp::Stmt::Let {
name: Ident::new(counter),
type_: Some(fsharp::Type::Primitive(fsharp::Primitive::UInt32)),
value: fsharp::Expr::call("uint32", vec![fsharp::Expr::Name(Ident::new(counter))]),
mutable: false,
});
inside.extend(body);
fsharp::Stmt::CountUp {
counter: Ident::new(counter),
type_: Box::new(fsharp::Type::Primitive(fsharp::Primitive::Int32)),
bound: Box::new(fsharp::Expr::call("int", vec![inner(bound)])),
body: inside,
}
}
fn until(condition: Self::Expr, body: Self::Block) -> Self::Stmt {
fsharp::Stmt::Until {
condition: inner(condition),
body,
}
}
fn leave() -> Self::Stmt {
fsharp::Stmt::Leave
}
fn note(doc: Doc) -> Self::Stmt {
fsharp::Stmt::Comment(doc)
}
fn drop_value(value: Self::Expr) -> Self::Stmt {
fsharp::Stmt::Expr(fsharp::Expr::Call {
func: Box::new(fsharp::Expr::Name(Ident::new("ignore"))),
args: vec![inner(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 {
let decoded = fsharp::Expr::curried_call(
callee,
vec![fsharp::Expr::Name(Ident::new("r")), inner(behaviour)],
);
match place {
FsharpExpr::OptionalSlot(field) => fsharp::Stmt::Assign {
place: field,
value: fsharp::Expr::Some_(Box::new(decoded)),
},
FsharpExpr::Plain(field) => fsharp::Stmt::Assign {
place: field,
value: decoded,
},
}
}
fn read_forward(callee: &str) -> Self::Stmt {
Self::read_call(
callee,
Self::whole("v"),
Handoff::Value,
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 mut out = vec![fsharp::Stmt::Assign {
place: fsharp::Expr::Name(Ident::new(base)),
value: fsharp::Expr::ArrayZeroCreate {
element: Box::new(element.tree.clone()),
count: Box::new(fsharp::Expr::call("int", vec![inner(count)])),
},
}];
if !assigned_whole {
if let fsharp::Type::Named(name) = &element.tree {
out.push(Self::count_up(
"zd_e",
Self::count_of(base, None),
vec![fsharp::Stmt::Assign {
place: fsharp::Expr::Index {
value: Box::new(fsharp::Expr::Name(Ident::new(base))),
index: Box::new(fsharp::Expr::call(
"int",
vec![fsharp::Expr::Name(Ident::new("zd_e"))],
)),
},
value: fsharp::Expr::New {
type_: name.clone(),
args: Vec::new(),
},
}],
));
}
}
out.extend(fill);
out
}
fn keep_first(base: &str, count: Self::Expr) -> Vec<Self::Stmt> {
vec![fsharp::Stmt::Assign {
place: fsharp::Expr::Name(Ident::new(base)),
value: fsharp::Expr::curried_call(
"Array.sub",
vec![
fsharp::Expr::Name(Ident::new(base)),
fsharp::Expr::int(0),
fsharp::Expr::call("int", vec![inner(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 routine(spec: Routine<Self>) -> Option<Self::Decl> {
let body = spec.body?;
let mut params = vec![fsharp::FnParam {
name: Ident::new(if spec.writing { "w" } else { "r" }),
type_: fsharp::Type::named(if spec.writing {
"ZdWire.Writer"
} else {
"ZdWire.Reader"
}),
}];
if spec.writing {
params.push(fsharp::FnParam {
name: Ident::new("v"),
type_: spec.sample.tree.clone(),
});
}
for (type_, name) in spec.extra {
params.push(fsharp::FnParam {
name: Ident::new(&name),
type_: type_.tree,
});
}
let (returns, body) = if spec.writing {
(fsharp::Type::Primitive(fsharp::Primitive::Unit), body)
} else {
let defaulter = default_fsharp::defaulter_from_decoder(&spec.name);
let mut statements = vec![fsharp::Stmt::Let {
name: Ident::new("v"),
type_: Some(spec.sample.tree.clone()),
value: fsharp::Expr::call(&defaulter, Vec::new()),
mutable: true,
}];
if returns_inside_a_loop(&body) {
let mut guarded = body;
give_back_instead_of_returning(&mut guarded);
statements.push(fsharp::Stmt::Guarded { body: guarded });
} else {
statements.extend(body);
}
statements.push(fsharp::Stmt::Return(fsharp::Expr::Name(Ident::new("v"))));
(spec.sample.tree, statements)
};
let body = hoist_early_returns(body);
let mut body = body;
let assigned = assigned_roots(&body);
fix_mutability(&mut body, &assigned);
Some(fsharp::Decl::Let(fsharp::LetDecl {
doc: spec.doc,
name: Ident::new(&spec.name),
params,
type_: Some(returns),
body,
}))
}
fn default_into(
ast: &Ast,
id: Id<TypeNode>,
place: Self::Expr,
) -> Result<Self::Stmt, EncodeError> {
let value = fsharp::Expr::call(&default_fsharp::defaulter_name(ast, id), Vec::new());
Ok(match place {
FsharpExpr::OptionalSlot(field) => fsharp::Stmt::Assign {
place: field,
value: fsharp::Expr::Some_(Box::new(value)),
},
FsharpExpr::Plain(field) => fsharp::Stmt::Assign {
place: field,
value,
},
})
}
fn member_default(
ast: &Ast,
node: &MemberNode,
type_: Id<TypeNode>,
place: Self::Expr,
) -> Result<Vec<Self::Stmt>, EncodeError> {
let value = default_fsharp::member_value(ast, node, type_)?;
Ok(match place {
FsharpExpr::OptionalSlot(field) => vec![fsharp::Stmt::Assign {
place: field,
value: fsharp::Expr::Some_(Box::new(value)),
}],
FsharpExpr::Plain(field) => vec![fsharp::Stmt::Assign {
place: field,
value,
}],
})
}
fn enumerator_constant(ast: &Ast, id: Id<TypeNode>, enumerator: &str) -> Self::Expr {
plain(fsharp::Expr::dot(
fsharp::Expr::Name(Ident::new(&fsharp_type_name(ast, id))),
&field_name(enumerator),
))
}
}
fn hoist_early_returns(body: Vec<fsharp::Stmt>) -> Vec<fsharp::Stmt> {
let mut acc: Vec<fsharp::Stmt> = Vec::new();
for stmt in body.into_iter().rev() {
let stmt = recurse_into_nested(stmt);
if contains_return(core::slice::from_ref(&stmt)) {
acc = splice_tail(vec![stmt], &acc);
} else {
acc.insert(0, stmt);
}
}
acc
}
fn recurse_into_nested(stmt: fsharp::Stmt) -> fsharp::Stmt {
match stmt {
fsharp::Stmt::If {
condition,
then,
otherwise,
} => fsharp::Stmt::If {
condition,
then: hoist_early_returns(then),
otherwise: otherwise.map(hoist_early_returns),
},
fsharp::Stmt::CountUp {
counter,
type_,
bound,
body,
} => fsharp::Stmt::CountUp {
counter,
type_,
bound,
body: hoist_early_returns(body),
},
fsharp::Stmt::Until { condition, body } => fsharp::Stmt::Until {
condition,
body: hoist_early_returns(body),
},
other => other,
}
}
fn splice_tail(mut body: Vec<fsharp::Stmt>, tail: &[fsharp::Stmt]) -> Vec<fsharp::Stmt> {
if tail.is_empty() || always_returns(&body) {
return body;
}
match body.pop() {
Some(fsharp::Stmt::If {
condition,
then,
otherwise,
}) => {
let returns_inside = contains_return(&then)
|| otherwise
.as_ref()
.is_some_and(|otherwise| contains_return(otherwise));
if !returns_inside {
body.push(fsharp::Stmt::If {
condition,
then,
otherwise,
});
body.extend_from_slice(tail);
return body;
}
let then = splice_tail(then, tail);
let otherwise = splice_tail(otherwise.unwrap_or_default(), tail);
body.push(fsharp::Stmt::If {
condition,
then,
otherwise: Some(otherwise),
});
body
}
Some(last) => {
body.push(last);
body.extend_from_slice(tail);
body
}
None => tail.to_vec(),
}
}
fn contains_return(body: &[fsharp::Stmt]) -> bool {
body.iter().any(|statement| match statement {
fsharp::Stmt::Return(_) => true,
fsharp::Stmt::If {
then, otherwise, ..
} => {
contains_return(then)
|| otherwise
.as_ref()
.is_some_and(|otherwise| contains_return(otherwise))
}
fsharp::Stmt::CountUp { body, .. }
| fsharp::Stmt::Until { body, .. }
| fsharp::Stmt::Guarded { body } => contains_return(body),
fsharp::Stmt::Let { .. }
| fsharp::Stmt::Assign { .. }
| fsharp::Stmt::Expr(_)
| fsharp::Stmt::Leave
| fsharp::Stmt::GiveBack
| fsharp::Stmt::Comment(_) => false,
})
}
fn returns_inside_a_loop(body: &[fsharp::Stmt]) -> bool {
body.iter().any(|statement| match statement {
fsharp::Stmt::CountUp { body, .. } | fsharp::Stmt::Until { body, .. } => {
contains_return(body)
}
fsharp::Stmt::If {
then, otherwise, ..
} => {
returns_inside_a_loop(then)
|| otherwise
.as_ref()
.is_some_and(|otherwise| returns_inside_a_loop(otherwise))
}
fsharp::Stmt::Guarded { body } => returns_inside_a_loop(body),
_ => false,
})
}
fn give_back_instead_of_returning(body: &mut [fsharp::Stmt]) {
for statement in body.iter_mut() {
match statement {
fsharp::Stmt::Return(_) => *statement = fsharp::Stmt::GiveBack,
fsharp::Stmt::If {
then, otherwise, ..
} => {
give_back_instead_of_returning(then);
if let Some(otherwise) = otherwise {
give_back_instead_of_returning(otherwise);
}
}
fsharp::Stmt::CountUp { body, .. }
| fsharp::Stmt::Until { body, .. }
| fsharp::Stmt::Guarded { body } => give_back_instead_of_returning(body),
_ => {}
}
}
}
fn always_returns(body: &[fsharp::Stmt]) -> bool {
match body.last() {
Some(fsharp::Stmt::Return(_)) => true,
Some(fsharp::Stmt::If {
then,
otherwise: Some(otherwise),
..
}) => always_returns(then) && always_returns(otherwise),
_ => false,
}
}
fn assigned_roots(body: &[fsharp::Stmt]) -> Vec<String> {
let mut out = Vec::new();
collect_assigned(body, &mut out);
out
}
fn collect_assigned(body: &[fsharp::Stmt], out: &mut Vec<String>) {
for statement in body {
match statement {
fsharp::Stmt::Assign { place, .. } => {
if let Some(root) = root_name(place) {
if !out.contains(&root) {
out.push(root);
}
}
}
fsharp::Stmt::If {
then, otherwise, ..
} => {
collect_assigned(then, out);
if let Some(otherwise) = otherwise {
collect_assigned(otherwise, out);
}
}
fsharp::Stmt::CountUp { body, .. }
| fsharp::Stmt::Until { body, .. }
| fsharp::Stmt::Guarded { body } => {
collect_assigned(body, out);
}
_ => {}
}
}
}
fn root_name(place: &fsharp::Expr) -> Option<String> {
match place {
fsharp::Expr::Name(name) => Some(name.as_str().to_string()),
fsharp::Expr::Dot { value, .. }
| fsharp::Expr::Index { value, .. }
| fsharp::Expr::Paren(value) => root_name(value),
_ => None,
}
}
fn fix_mutability(body: &mut [fsharp::Stmt], assigned: &[String]) {
for statement in body {
match statement {
fsharp::Stmt::Let { name, mutable, .. } => {
if assigned.iter().any(|root| root == name.as_str()) {
*mutable = true;
}
}
fsharp::Stmt::If {
then, otherwise, ..
} => {
fix_mutability(then, assigned);
if let Some(otherwise) = otherwise {
fix_mutability(otherwise, assigned);
}
}
fsharp::Stmt::CountUp { body, .. }
| fsharp::Stmt::Until { body, .. }
| fsharp::Stmt::Guarded { body } => {
fix_mutability(body, assigned);
}
_ => {}
}
}
}
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 => fsharp_type_name(ast, id),
TypeKind::Any => "any".to_string(),
TypeKind::ValueBase => "valuebase".to_string(),
TypeKind::ObjectRef { .. } => "objectref".to_string(),
TypeKind::AnnotationType { .. } => "annotation".to_string(),
}
}
#[must_use]
pub fn module_name() -> &'static str {
MODULE_NAME
}