use super::{CodecTarget, Constant, Handle, Handoff, Routine, Scalar};
use crate::adapter::node::field_name;
use crate::encode::EncodeError;
use zerodds_ast::{Ast, MemberNode, TypeKind, TypeNode};
use zerodds_ast_core::Id;
use zerodds_lang_cst::common::{Doc, Ident, Literal};
use zerodds_lang_cst::node::{self, Expr, Function, Stat};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Node;
const MAX_TYPE_DEPTH: usize = 64;
const RUNTIME: &str = "zdwire";
const SIGNED_CHAR: &str = "signed char";
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NodeType(pub String);
impl NodeType {
fn tag(text: &str) -> Self {
Self(text.to_string())
}
}
impl Node {
fn camel(method: &str) -> String {
let mut out = String::new();
let mut rising = false;
for character in method.chars() {
if character == '_' {
rising = true;
continue;
}
if rising {
out.extend(character.to_uppercase());
rising = false;
} else {
out.push(character);
}
}
out
}
fn runtime_name(path: &str) -> Expr {
let mut segments = path.split('.');
let mut expr = Expr::member(Expr::name(RUNTIME), segments.next().unwrap_or(path));
for segment in segments {
expr = Expr::member(expr, segment);
}
expr
}
fn reading_body(name: &str, body: Vec<Stat>) -> Vec<Stat> {
let defaulter = crate::default_node::defaulter_from_decoder(name);
let mut statements = vec![Stat::Declare {
doc: Doc::none(),
kind: node::VarKind::Let,
name: Ident::new("v"),
value: Some(Expr::call(&defaulter, Vec::new())),
}];
statements.extend(body);
statements.push(Stat::Return(Some(Expr::name("v"))));
statements
}
}
impl CodecTarget for Node {
type Expr = Expr;
type Stmt = Stat;
type Block = Vec<Stat>;
type Type = NodeType;
type Decl = Stat;
fn scalar(kind: Scalar) -> Self::Type {
NodeType::tag(match kind {
Scalar::Bool => "boolean",
Scalar::Char => "string",
Scalar::Int8
| Scalar::UInt8
| Scalar::Int16
| Scalar::UInt16
| Scalar::Int32
| Scalar::UInt32
| Scalar::Index
| Scalar::Size
| Scalar::Float
| Scalar::Double => "number",
Scalar::Int64 | Scalar::UInt64 => "bigint",
Scalar::LongDouble => "float128",
})
}
fn named(name: &str) -> Self::Type {
NodeType::tag(name)
}
fn nothing() -> Self::Type {
NodeType::tag("nothing")
}
fn handle(_kind: Handle) -> Self::Type {
NodeType::tag("object")
}
fn code_point(value: Self::Expr, _narrow: bool) -> Self::Expr {
value
}
fn constant(which: Constant) -> Self::Expr {
Self::runtime_name(match which {
Constant::WcharWidth2 => "WcharWidth.Octets2",
Constant::WcharWidth4 => "WcharWidth.Octets4",
Constant::WcharFixed => "WcharFraming.Fixed",
Constant::WcharGiopLengthPrefixed => "WcharFraming.GiopLengthPrefixed",
Constant::WstringUnitsStream => "WstringUnitOrder.Stream",
Constant::WstringUnitsBigEndianAlways => "WstringUnitOrder.BigEndianAlways",
Constant::WstringUnitsByteOrderMark => "WstringUnitOrder.ByteOrderMark",
Constant::WstringUnitsHostUnswapped => "WstringUnitOrder.HostUnswapped",
Constant::WstringSpecification => "WstringLengthPolicy.Specification",
Constant::WstringCharacterCount => "WstringLengthPolicy.CharacterCount",
Constant::WstringCharacterCountTerminated => {
"WstringLengthPolicy.CharacterCountTerminated"
}
Constant::WstringSpecificationWideUnits => "WstringLengthPolicy.SpecificationWideUnits",
Constant::WstringRosTypeSupport => "WstringLengthPolicy.RosTypeSupport",
Constant::WstringOctetsTerminated2 => "WstringLengthPolicy.OctetsTerminated2",
Constant::WstringOctetsTerminated4 => "WstringLengthPolicy.OctetsTerminated4",
Constant::WstringCharactersTerminated2 => "WstringLengthPolicy.CharactersTerminated2",
Constant::FixedDeclared => "FixedDigitCount.Declared",
Constant::FixedValuePrecision => "FixedDigitCount.ValuePrecision",
Constant::FixedValueStringDigits => "FixedDigitCount.ValueStringDigits",
Constant::ParameterMember => "ParameterRole.Member",
Constant::ParameterOptional => "ParameterRole.Optional",
Constant::TryConstructDiscard => "TryConstruct.Discard",
Constant::TryConstructTrim => "TryConstruct.Trim",
Constant::TryConstructUseDefault => "TryConstruct.UseDefault",
Constant::StatusOk => "Status.Ok",
Constant::StatusDiscarded => "Status.Discarded",
Constant::StatusImpossibleLength => "Status.ImpossibleLength",
Constant::StatusOutOfRange => "Status.OutOfRange",
Constant::StatusTruncated => "Status.Truncated",
})
}
fn unsigned_holder(bits: u16) -> Self::Type {
NodeType::tag(if bits > 32 { "bigint" } else { "number" })
}
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 {
crate::adapter::node::MAP_ENTRY.to_string()
}
fn module_prefix(_ast: &Ast, _id: Id<TypeNode>) -> String {
String::new()
}
fn escape(name: &str) -> String {
field_name(name)
}
fn name(text: &str) -> Self::Expr {
Expr::name(text)
}
fn int(value: i128) -> Self::Expr {
Expr::int(value)
}
fn literal(value: Literal) -> Self::Expr {
if let Literal::Int(text) = &value {
match text.as_str() {
"true" => return Expr::bool(true),
"false" => return Expr::bool(false),
_ => {}
}
}
Expr::Literal(value)
}
fn wide_int(value: i128) -> Self::Expr {
Expr::Literal(Literal::Int(format!("{value}n")))
}
fn unsigned_int(value: i128) -> Self::Expr {
Expr::int(value)
}
fn wide_unsigned(value: u64) -> Self::Expr {
Expr::Literal(Literal::Int(format!("{value}n")))
}
fn wide_signed(value: i128) -> Self::Expr {
Expr::Literal(Literal::Int(format!("{value}n")))
}
fn header_field(value: Self::Expr, field: &str) -> Self::Expr {
Self::field_of(
value,
match field {
"pid" => "pid",
"end" => "end",
"ignore" => "ignore",
"length" => "length",
"member_id" => "memberId",
"must_understand" => "mustUnderstand",
"extent" => "extent",
other => other,
},
)
}
fn call(callee: &str, args: Vec<Self::Expr>) -> Self::Expr {
if callee == "strcmp" {
if let [left, right] = args.as_slice() {
return Expr::Paren(Box::new(Expr::Ternary {
condition: Box::new(Expr::Binary {
op: "<",
lhs: Box::new(left.clone()),
rhs: Box::new(right.clone()),
}),
then: Box::new(Self::int(-1)),
otherwise: Box::new(Expr::Ternary {
condition: Box::new(Expr::Binary {
op: ">",
lhs: Box::new(left.clone()),
rhs: Box::new(right.clone()),
}),
then: Box::new(Self::int(1)),
otherwise: Box::new(Self::int(0)),
}),
}));
}
}
Expr::call(callee, args)
}
fn runtime(receiver: &str, method: &str, args: Vec<Self::Expr>) -> Self::Expr {
Expr::Call {
callee: Box::new(Expr::member(Expr::name(receiver), &Self::camel(method))),
args,
}
}
fn carries_hdds_axes() -> bool {
true
}
fn runtime_static(method: &str, args: Vec<Self::Expr>) -> Self::Expr {
Expr::Call {
callee: Box::new(Expr::member(Expr::name(RUNTIME), &Self::camel(method))),
args,
}
}
fn member(base: &str, field: &str) -> Self::Expr {
let mut place = Expr::name(base);
for segment in field.split('.') {
place = Expr::member(place, segment);
}
place
}
fn inherited_path(depth: usize) -> Option<String> {
Some("parent.".repeat(depth))
}
fn whole(base: &str) -> Self::Expr {
Expr::name(base)
}
fn field_of(value: Self::Expr, field: &str) -> Self::Expr {
Expr::member(value, field)
}
fn index(value: Self::Expr, at: Self::Expr) -> Self::Expr {
Expr::Index {
object: Box::new(value),
index: Box::new(at),
}
}
fn count_of(base: &str, fixed: Option<u64>) -> Self::Expr {
match fixed {
Some(bound) => Expr::int(i128::from(bound)),
None => Expr::member(Expr::name(base), "length"),
}
}
fn elements(base: &str) -> Self::Expr {
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 {
Expr::member(entry, if key { "key" } else { "value" })
}
fn absent() -> Self::Expr {
Expr::Undefined
}
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 {
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![Stat::Expr(Expr::Unary {
op: "delete ",
value: Box::new(Self::member(base, field)),
})]
}
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 {
Expr::Binary {
op: match op {
"==" => "===",
"!=" => "!==",
other => other,
},
lhs: Box::new(lhs),
rhs: Box::new(rhs),
}
}
fn unary(op: &'static str, value: Self::Expr) -> Self::Expr {
Expr::Unary {
op,
value: Box::new(value),
}
}
fn paren(value: Self::Expr) -> Self::Expr {
Expr::Paren(Box::new(value))
}
fn cast(type_: Self::Type, value: Self::Expr) -> Self::Expr {
if let Expr::Literal(Literal::Int(text)) = &value {
if type_.0 == "boolean" {
let digits = text.strip_suffix('n').unwrap_or(text);
if !digits.is_empty() && digits.bytes().all(|b| b.is_ascii_digit() || b == b'-') {
return Expr::bool(digits != "0");
}
}
}
if type_.0 == SIGNED_CHAR {
let point = Expr::Call {
callee: Box::new(Expr::member(Expr::Paren(Box::new(value)), "charCodeAt")),
args: vec![Self::int(0)],
};
return Self::binary(
"-",
Expr::Paren(Box::new(Self::binary(
"%",
Expr::Paren(Box::new(Self::binary("+", point, Self::int(128)))),
Self::int(256),
))),
Self::int(128),
);
}
let coercion = match type_.0.as_str() {
"number" => Some("zdNumber"),
"bigint" => Some("zdBigInt"),
"string" => Some("zdChar"),
_ => None,
};
match coercion {
Some(callee) if is_call_to(&value, callee) => value,
Some(callee) => Self::runtime_static(callee, vec![value]),
None => value,
}
}
fn store(place: Self::Expr, type_: Self::Type, value: Self::Expr) -> Self::Stmt {
Self::perform(Self::binary("=", place, Self::cast(type_, value)))
}
fn handoff(value: Self::Expr, _form: Handoff) -> Self::Expr {
value
}
fn read_call(
callee: &str,
place: Self::Expr,
_form: Handoff,
behaviour: Self::Expr,
) -> Self::Stmt {
Self::perform(Self::binary(
"=",
place,
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 declare(type_: Self::Type, name: &str, value: Option<Self::Expr>) -> Self::Stmt {
let value = value.map(|value| match &value {
Expr::Literal(Literal::Int(text)) if type_.0 == "bigint" && !text.ends_with('n') => {
Expr::Literal(Literal::Int(format!("{text}n")))
}
_ => value,
});
Stat::Declare {
doc: Doc::none(),
kind: node::VarKind::Let,
name: Ident::new(name),
value,
}
}
fn perform(value: Self::Expr) -> Self::Stmt {
Stat::Expr(value)
}
fn give_back(value: Option<Self::Expr>) -> Self::Stmt {
Stat::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 {
Stat::Expr(Expr::Unary {
op: "void ",
value: Box::new(value),
})
}
fn when(
condition: Self::Expr,
then: Self::Block,
otherwise: Option<Self::Block>,
) -> Self::Stmt {
Stat::If {
condition,
then,
otherwise,
}
}
fn count_up(counter: &str, bound: Self::Expr, body: Self::Block) -> Self::Stmt {
Stat::For {
counter: Ident::new(counter),
bound,
body,
}
}
fn until(condition: Self::Expr, body: Self::Block) -> Self::Stmt {
Stat::While { condition, body }
}
fn leave() -> Self::Stmt {
Stat::Break
}
fn note(doc: Doc) -> Self::Stmt {
Stat::Comment(doc)
}
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![Stat::Assign {
doc: Doc::none(),
place: Self::whole(base),
value: Expr::New {
callee: Ident::new("Array"),
args: vec![count],
},
}];
if !assigned_whole {
out.push(Self::count_up(
"zd_e",
Self::count_of(base, None),
vec![Stat::Assign {
doc: Doc::none(),
place: Expr::Index {
object: Box::new(Self::whole(base)),
index: Box::new(Self::whole("zd_e")),
},
value: Expr::Object(Vec::new()),
}],
));
}
out.extend(fill);
out
}
fn storage(_type_: Self::Type, name: &str) -> Self::Stmt {
Stat::Declare {
doc: Doc::none(),
kind: node::VarKind::Let,
name: Ident::new(name),
value: Some(Expr::Object(Vec::new())),
}
}
fn keep_first(base: &str, count: Self::Expr) -> Vec<Self::Stmt> {
vec![Stat::Assign {
doc: Doc::none(),
place: Expr::member(Self::whole(base), "length"),
value: 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![Ident::new(if spec.writing { "w" } else { "r" })];
if spec.writing {
params.push(Ident::new("v"));
}
for (_, name) in &spec.extra {
params.push(Ident::new(name));
}
let body = if spec.writing {
body
} else {
Self::reading_body(&spec.name, body)
};
Some(Stat::Function(Function {
doc: spec.doc,
name: Ident::new(&spec.name),
params,
body,
}))
}
fn default_into(
ast: &Ast,
id: Id<TypeNode>,
place: Self::Expr,
) -> Result<Self::Stmt, EncodeError> {
Ok(crate::default_node::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_node::member_default(ast, node, type_, place)
}
fn enumerator_constant(ast: &Ast, id: Id<TypeNode>, enumerator: &str) -> Self::Expr {
Expr::member(
Expr::name(&crate::adapter::node::node_type_name(ast, id)),
&field_name(enumerator),
)
}
}
fn is_call_to(value: &Expr, name: &str) -> bool {
let Expr::Call { callee, .. } = value else {
return false;
};
let Expr::Member {
object,
name: field,
} = callee.as_ref()
else {
return false;
};
if field.as_str() != name {
return false;
}
matches!(object.as_ref(), Expr::Name(root) if root.as_str() == RUNTIME)
}
fn value_type_of(ast: &Ast, id: Id<TypeNode>, depth: usize) -> NodeType {
use zerodds_ast::PrimitiveKind;
if depth > MAX_TYPE_DEPTH {
return NodeType::tag("object");
}
let Ok(node) = ast.type_(id) else {
return NodeType::tag("object");
};
match &node.kind {
TypeKind::Primitive(kind) => match kind {
PrimitiveKind::Boolean => NodeType::tag("boolean"),
PrimitiveKind::Char | PrimitiveKind::WChar => NodeType::tag("string"),
PrimitiveKind::Int64 | PrimitiveKind::UInt64 => NodeType::tag("bigint"),
PrimitiveKind::Float128 => NodeType::tag("float128"),
_ => NodeType::tag("number"),
},
TypeKind::String { .. } | TypeKind::Fixed { .. } => NodeType::tag("string"),
TypeKind::Enumeration { .. } => NodeType::tag("number"),
TypeKind::Bitmask { .. } => NodeType::tag(if ast.effective_bit_bound(id) > 32 {
"bigint"
} else {
"number"
}),
TypeKind::Alias { aliased } => aliased.resolved().map_or_else(
|| NodeType::tag("object"),
|target| value_type_of(ast, target, depth + 1),
),
_ => NodeType::tag("object"),
}
}
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 type_tag(ast: &Ast, id: Id<TypeNode>, depth: usize) -> String {
use zerodds_ast::CharWidth;
if depth > MAX_TYPE_DEPTH {
return "unresolved".to_string();
}
let Ok(node) = ast.type_(id) else {
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 => crate::adapter::node::node_type_name(ast, id),
TypeKind::Any => "any".to_string(),
TypeKind::ValueBase => "valuebase".to_string(),
TypeKind::ObjectRef { .. } => "objectref".to_string(),
TypeKind::AnnotationType { .. } => "annotation".to_string(),
}
}