use super::{CodecTarget, Constant, Handle, Handoff, Routine, Scalar};
use crate::adapter::python::{declared_path, type_tag};
use crate::encode::EncodeError;
use zerodds_ast::{Ast, MemberNode, TypeNode};
use zerodds_ast_core::Id;
use zerodds_lang_cst::common::{Doc, Ident, Literal, Path};
use zerodds_lang_cst::python::{self as py, escape_name};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Python;
const MAX_TYPE_DEPTH: usize = 64;
const SIGNED_CHAR: &str = "signed char";
impl Python {
fn path(name: &str) -> Path {
Path::new(&name.split('.').collect::<Vec<_>>())
}
fn runtime_name(name: &str) -> Path {
Path::single(name)
}
fn suite(mut body: Vec<py::Statement>) -> Vec<py::Statement> {
if body.is_empty() {
body.push(py::Statement::Pass);
}
body
}
fn reading_body(name: &str, body: Vec<py::Statement>) -> Vec<py::Statement> {
let defaulter = crate::default_python::defaulter_from_decoder(name);
let mut statements = vec![py::Statement::Set {
place: py::Expr::name("v"),
value: Self::call(&defaulter, Vec::new()),
}];
statements.extend(body);
statements.push(py::Statement::Return(Some(py::Expr::name("v"))));
statements
}
fn keywords_for(method: &str) -> Option<[&'static str; 2]> {
match method {
"write_enum" | "write_bitmask" => Some(["bit_bound", "value"]),
"write_bitset" => Some(["anchor", "value"]),
_ => None,
}
}
}
impl CodecTarget for Python {
type Expr = py::Expr;
type Stmt = py::Statement;
type Block = Vec<py::Statement>;
type Type = py::Type;
type Decl = py::Statement;
fn scalar(kind: Scalar) -> Self::Type {
match kind {
Scalar::Bool => py::Type::name("bool"),
Scalar::Char => py::Type::name("str"),
Scalar::Int8
| Scalar::UInt8
| Scalar::Int16
| Scalar::UInt16
| Scalar::Int32
| Scalar::UInt32
| Scalar::Int64
| Scalar::UInt64
| Scalar::Index
| Scalar::Size => py::Type::name("int"),
Scalar::Float | Scalar::Double => py::Type::name("float"),
Scalar::LongDouble => py::Type::name("Float128"),
}
}
fn named(name: &str) -> Self::Type {
py::Type::Name(Self::path(name))
}
fn nothing() -> Self::Type {
py::Type::name("None")
}
fn handle(kind: Handle) -> Self::Type {
py::Type::name(match kind {
Handle::DHeader => "DHeader",
Handle::EmHeader => "EmHeader",
Handle::Pid => "Pid",
Handle::TryConstruct => "TryConstruct",
Handle::ReaderMark => "ReaderMark",
Handle::PidRead => "PidRead",
Handle::EmHeaderRead => "EmHeaderRead",
})
}
fn code_point(value: Self::Expr, _narrow: bool) -> Self::Expr {
value
}
fn constant(which: Constant) -> Self::Expr {
let text = |value: &str| py::Expr::Literal(Literal::Str(value.to_string()));
match which {
Constant::WcharWidth2 => py::Expr::int(2),
Constant::WcharWidth4 => py::Expr::int(4),
Constant::WcharFixed => text("fixed"),
Constant::WcharGiopLengthPrefixed => text("giop-length-prefixed"),
Constant::WstringUnitsStream => text("stream"),
Constant::WstringUnitsBigEndianAlways => text("big-endian-always"),
Constant::WstringUnitsByteOrderMark => text("byte-order-mark"),
Constant::WstringUnitsHostUnswapped => text("host-unswapped"),
Constant::WstringSpecification => text("specification"),
Constant::WstringCharacterCount => text("character-count"),
Constant::WstringCharacterCountTerminated => text("character-count-terminated"),
Constant::WstringSpecificationWideUnits => text("specification-wide-units"),
Constant::WstringRosTypeSupport => text("ros-type-support"),
Constant::WstringOctetsTerminated2 => text("octets/terminated/2"),
Constant::WstringOctetsTerminated4 => text("octets/terminated/4"),
Constant::WstringCharactersTerminated2 => text("characters/terminated/2"),
Constant::FixedDeclared => text("declared"),
Constant::FixedValuePrecision => text("value-precision"),
Constant::FixedValueStringDigits => text("value-string-digits"),
Constant::ParameterMember => py::Expr::Path(Self::path("ParameterRole.MEMBER")),
Constant::ParameterOptional => py::Expr::Path(Self::path("ParameterRole.OPTIONAL")),
Constant::TryConstructDiscard => py::Expr::Path(Self::path("TryConstruct.DISCARD")),
Constant::TryConstructTrim => py::Expr::Path(Self::path("TryConstruct.TRIM")),
Constant::TryConstructUseDefault => {
py::Expr::Path(Self::path("TryConstruct.USE_DEFAULT"))
}
Constant::StatusOk => py::Expr::Path(Self::path("Status.OK")),
Constant::StatusDiscarded => py::Expr::Path(Self::path("Status.DISCARDED")),
Constant::StatusImpossibleLength => {
py::Expr::Path(Self::path("Status.IMPOSSIBLE_LENGTH"))
}
Constant::StatusOutOfRange => py::Expr::Path(Self::path("Status.OUT_OF_RANGE")),
Constant::StatusTruncated => py::Expr::Path(Self::path("Status.TRUNCATED")),
}
}
fn unsigned_holder(_bits: u16) -> Self::Type {
py::Type::name("int")
}
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).unwrap_or_else(|_| format!("unresolved_type_{}", id.slot()))
}
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 {
escape_name(name)
}
fn inherited_path(depth: usize) -> Option<String> {
Some(format!("{}.", escape_name("parent")).repeat(depth))
}
fn name(text: &str) -> Self::Expr {
py::Expr::Path(Self::path(text))
}
fn int(value: i128) -> Self::Expr {
py::Expr::int(value)
}
fn wide_int(value: i128) -> Self::Expr {
py::Expr::int(value)
}
fn wide_unsigned(value: u64) -> Self::Expr {
py::Expr::int(i128::from(value))
}
fn wide_signed(value: i128) -> Self::Expr {
py::Expr::int(value)
}
fn unsigned_int(value: i128) -> Self::Expr {
py::Expr::int(value)
}
fn literal(value: Literal) -> Self::Expr {
let Literal::Int(text) = &value else {
return py::Expr::Literal(value);
};
match text.as_str() {
"true" => py::Expr::Literal(Literal::Bool(true)),
"false" => py::Expr::Literal(Literal::Bool(false)),
_ => py::Expr::Literal(value),
}
}
fn call(callee: &str, args: Vec<Self::Expr>) -> Self::Expr {
if callee == "strcmp" {
return strcmp_sign(args);
}
py::Expr::Call {
func: Self::path(callee),
args: args.into_iter().map(py::Arg::Positional).collect(),
}
}
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 func = Path::new(&[receiver, method]);
let Some(names) = Python::keywords_for(method) else {
return py::Expr::Call {
func,
args: args.into_iter().map(py::Arg::Positional).collect(),
};
};
if args.len() != names.len() {
return py::Expr::Call {
func,
args: args.into_iter().map(py::Arg::Positional).collect(),
};
}
py::Expr::Call {
func,
args: args
.into_iter()
.zip(names)
.map(|(value, name)| {
py::Arg::Keyword(py::Keyword {
name: Ident::new(name),
value,
})
})
.collect(),
}
}
fn carries_hdds_axes() -> bool {
true
}
fn runtime_static(method: &str, args: Vec<Self::Expr>) -> Self::Expr {
if method == "pid_is_user_member" {
if let [py::Expr::Path(header)] = args.as_slice() {
let mut segments: Vec<String> = header
.segments()
.iter()
.map(|segment| segment.as_str().to_string())
.collect();
segments.push("is_user_member".to_string());
return py::Expr::Call {
func: Path::new(&segments.iter().map(String::as_str).collect::<Vec<&str>>()),
args: Vec::new(),
};
}
}
py::Expr::Call {
func: Self::runtime_name(method),
args: args.into_iter().map(py::Arg::Positional).collect(),
}
}
fn member(base: &str, field: &str) -> Self::Expr {
py::Expr::Attribute {
value: Box::new(py::Expr::name(base)),
name: Ident::new(field),
}
}
fn whole(base: &str) -> Self::Expr {
py::Expr::name(base)
}
fn field_of(value: Self::Expr, field: &str) -> Self::Expr {
py::Expr::Attribute {
value: Box::new(value),
name: Ident::new(field),
}
}
fn index(value: Self::Expr, at: Self::Expr) -> Self::Expr {
py::Expr::Index {
value: Box::new(value),
index: Box::new(at),
}
}
fn count_of(base: &str, fixed: Option<u64>) -> Self::Expr {
match fixed {
Some(bound) => py::Expr::int(i128::from(bound)),
None => py::Expr::Call {
func: Path::single("len"),
args: vec![py::Arg::Positional(py::Expr::name(base))],
},
}
}
fn elements(base: &str) -> Self::Expr {
py::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 {
py::Expr::Index {
value: Box::new(entry),
index: Box::new(py::Expr::int(i128::from(!key))),
}
}
fn absent() -> Self::Expr {
py::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 {
py::Expr::Binary {
op: "is not",
lhs: Box::new(Self::member(base, field)),
rhs: Box::new(Self::absent()),
}
}
fn clear_union_cases(base: &str, fields: &[String]) -> Vec<Self::Stmt> {
fields
.iter()
.map(|field| py::Statement::Set {
place: Self::member(base, field),
value: Self::absent(),
})
.collect()
}
fn open_array(base: &str, lengths: &[i128]) -> Vec<Self::Stmt> {
array_rows(base, lengths, 0, false)
}
fn close_array(base: &str, lengths: &[i128]) -> Vec<Self::Stmt> {
array_rows(base, lengths, 0, true)
}
fn set_present(base: &str, field: &str, present: bool) -> Vec<Self::Stmt> {
if present {
return Vec::new();
}
vec![py::Statement::Set {
place: Self::member(base, field),
value: Self::absent(),
}]
}
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 {
py::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 {
py::Expr::Unary {
op: match op {
"!" => "not ",
other => other,
},
value: Box::new(value),
}
}
fn paren(value: Self::Expr) -> Self::Expr {
py::Expr::Paren(Box::new(value))
}
fn cast(type_: Self::Type, value: Self::Expr) -> Self::Expr {
if type_ == py::Type::name(SIGNED_CHAR) {
return signed_char(value);
}
let py::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_ == py::Type::name("str") {
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) => py::Expr::Literal(Literal::Char(point)),
None => py::Expr::Call {
func: Path::single("chr"),
args: vec![py::Arg::Positional(py::Expr::int(i128::from(code)))],
},
},
);
}
if type_ == py::Type::name("bool") {
return py::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 {
py::Statement::Annotated(py::Annotated {
doc: Doc::none(),
name: Ident::new(name),
type_,
value,
})
}
fn perform(value: Self::Expr) -> Self::Stmt {
if let py::Expr::Binary { op: "=", lhs, rhs } = value {
return py::Statement::Set {
place: *lhs,
value: *rhs,
};
}
py::Statement::Expr(value)
}
fn give_back(value: Option<Self::Expr>) -> Self::Stmt {
py::Statement::Return(Some(value.unwrap_or_else(|| py::Expr::name("v"))))
}
fn when(
condition: Self::Expr,
then: Self::Block,
otherwise: Option<Self::Block>,
) -> Self::Stmt {
py::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 {
py::Statement::For {
counter: Ident::new(counter),
bound,
body: Self::suite(body),
}
}
fn until(condition: Self::Expr, body: Self::Block) -> Self::Stmt {
py::Statement::While {
condition,
body: Self::suite(body),
}
}
fn leave() -> Self::Stmt {
py::Statement::Break
}
fn note(doc: Doc) -> Self::Stmt {
py::Statement::Comment(doc)
}
fn drop_value(value: Self::Expr) -> Self::Stmt {
py::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 {
py::Statement::Set {
place,
value: Self::call(callee, vec![py::Expr::name("r"), behaviour]),
}
}
fn read_forward(callee: &str) -> Self::Stmt {
Self::read_call(
callee,
py::Expr::name("v"),
Handoff::Value,
py::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 slot = |index: py::Expr| py::Expr::Index {
value: Box::new(py::Expr::name(base)),
index: Box::new(index),
};
let mut out = Vec::new();
if assigned_whole {
out.push(py::Statement::Set {
place: py::Expr::name(base),
value: py::Expr::Binary {
op: "*",
lhs: Box::new(py::Expr::List(vec![py::Expr::Literal(Literal::Null)])),
rhs: Box::new(count.clone()),
},
});
out.extend(fill);
return out;
}
out.push(py::Statement::Set {
place: py::Expr::name(base),
value: py::Expr::List(Vec::new()),
});
out.push(py::Statement::For {
counter: Ident::new(ENTRY_INDEX),
bound: count.clone(),
body: vec![py::Statement::Expr(py::Expr::Call {
func: Path::new(&[base, "append"]),
args: vec![py::Arg::Positional(py::Expr::List(vec![
py::Expr::Literal(Literal::Null),
py::Expr::Literal(Literal::Null),
]))],
})],
});
out.extend(fill);
out.push(py::Statement::For {
counter: Ident::new(ENTRY_INDEX),
bound: count,
body: vec![py::Statement::Set {
place: slot(py::Expr::name(ENTRY_INDEX)),
value: py::Expr::Call {
func: Path::single("tuple"),
args: vec![py::Arg::Positional(slot(py::Expr::name(ENTRY_INDEX)))],
},
}],
});
out
}
fn keep_first(base: &str, count: Self::Expr) -> Vec<Self::Stmt> {
vec![py::Statement::Set {
place: py::Expr::name(base),
value: py::Expr::Index {
value: Box::new(py::Expr::name(base)),
index: Box::new(py::Expr::Slice {
start: None,
end: Some(Box::new(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_ == py::Type::name(MAP_ENTRY) {
return py::Statement::Set {
place: py::Expr::name(name),
value: py::Expr::List(vec![
py::Expr::Literal(Literal::Null),
py::Expr::Literal(Literal::Null),
]),
};
}
Self::declare(type_, name, None)
}
fn routine(spec: Routine<Self>) -> Option<Self::Decl> {
let body = spec.body?;
let mut params = vec![py::Param {
name: Ident::new(if spec.writing { "w" } else { "r" }),
type_: Some(py::Type::name(if spec.writing {
"Writer"
} else {
"Reader"
})),
default: None,
}];
if spec.writing {
params.push(py::Param {
name: Ident::new("v"),
type_: Some(spec.sample.clone()),
default: None,
});
}
params.extend(spec.extra.into_iter().map(|(type_, name)| py::Param {
name: Ident::new(&name),
type_: Some(type_),
default: None,
}));
let (returns, body) = if spec.writing {
(Self::nothing(), body)
} else {
(spec.sample, Self::reading_body(&spec.name, body))
};
Some(py::Statement::Function(py::Function {
doc: spec.doc,
decorators: Vec::new(),
name: Ident::new(&spec.name),
params,
returns: Some(returns),
body: Self::suite(body),
}))
}
fn initialized_reader(spec: Routine<Self>, initializer: Self::Stmt) -> Option<Self::Decl> {
if spec.writing {
return None;
}
let mut body = vec![initializer];
body.extend(spec.body?);
body.push(py::Statement::Return(Some(py::Expr::name("v"))));
let mut params = vec![py::Param {
name: Ident::new("r"),
type_: Some(py::Type::name("Reader")),
default: None,
}];
params.extend(spec.extra.into_iter().map(|(type_, name)| py::Param {
name: Ident::new(&name),
type_: Some(type_),
default: None,
}));
Some(py::Statement::Function(py::Function {
doc: spec.doc,
decorators: Vec::new(),
name: Ident::new(&spec.name),
params,
returns: Some(spec.sample),
body: Self::suite(body),
}))
}
fn default_into(
ast: &Ast,
id: Id<TypeNode>,
place: Self::Expr,
) -> Result<Self::Stmt, EncodeError> {
Ok(crate::default_python::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_python::member_default(ast, node, type_, place)
}
fn enumerator_constant(ast: &Ast, id: Id<TypeNode>, enumerator: &str) -> Self::Expr {
let Ok(path) = declared_path(ast, id) else {
return Self::name(&escape_name(enumerator));
};
let mut segments: Vec<String> = path
.segments()
.iter()
.map(|segment| segment.as_str().to_string())
.collect();
segments.push(escape_name(enumerator));
py::Expr::Path(Path::new(
&segments.iter().map(String::as_str).collect::<Vec<&str>>(),
))
}
}
const ENTRY_INDEX: &str = "zd_e";
const MAP_ENTRY: &str = "_zd_map_entry";
fn signed_char(value: py::Expr) -> py::Expr {
let point = py::Expr::Call {
func: Path::single("ord"),
args: vec![py::Arg::Positional(value)],
};
let wrapped = py::Expr::Binary {
op: "%",
lhs: Box::new(py::Expr::Binary {
op: "+",
lhs: Box::new(point),
rhs: Box::new(py::Expr::int(128)),
}),
rhs: Box::new(py::Expr::int(256)),
};
py::Expr::Binary {
op: "-",
lhs: Box::new(wrapped),
rhs: Box::new(py::Expr::int(128)),
}
}
fn strcmp_sign(args: Vec<py::Expr>) -> py::Expr {
let [left, right] = <[py::Expr; 2]>::try_from(args).unwrap_or_else(|args| {
[
py::Expr::Call {
func: Path::single("strcmp"),
args: args.into_iter().map(py::Arg::Positional).collect(),
},
py::Expr::int(0),
]
});
let side = |op: &'static str| {
py::Expr::Paren(Box::new(py::Expr::Binary {
op,
lhs: Box::new(left.clone()),
rhs: Box::new(right.clone()),
}))
};
py::Expr::Paren(Box::new(py::Expr::Binary {
op: "-",
lhs: Box::new(side(">")),
rhs: Box::new(side("<")),
}))
}
fn value_type_of(ast: &Ast, id: Id<TypeNode>, depth: usize) -> py::Type {
use zerodds_ast::{PrimitiveKind, TypeKind};
let unknown = || py::Type::name("object");
if depth > MAX_TYPE_DEPTH {
return unknown();
}
let Ok(node) = ast.type_(id) else {
return unknown();
};
let declared = || declared_path(ast, id).map_or_else(|_| unknown(), py::Type::Name);
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 => Python::scalar(Scalar::Bool),
PrimitiveKind::Char | PrimitiveKind::WChar => Python::scalar(Scalar::Char),
PrimitiveKind::Float32 | PrimitiveKind::Float64 => Python::scalar(Scalar::Double),
PrimitiveKind::Float128 => Python::scalar(Scalar::LongDouble),
_ => Python::scalar(Scalar::Int32),
},
TypeKind::String { .. } | TypeKind::Fixed { .. } => py::Type::name("str"),
TypeKind::Enumeration { .. }
| TypeKind::Bitmask { .. }
| TypeKind::Bitset { .. }
| TypeKind::Structure { .. }
| TypeKind::Union { .. }
| TypeKind::Valuetype { .. }
| TypeKind::Native => declared(),
TypeKind::Sequence { element, .. } => {
py::Type::subscript(Path::single("list"), vec![inner(element.resolved())])
}
TypeKind::Array { element, dims } => {
let mut out = inner(element.resolved());
for dim in dims.iter().rev() {
let Some(length) = ast
.eval_integral(*dim)
.ok()
.and_then(|length| usize::try_from(length).ok())
else {
return unknown();
};
out = py::Type::subscript(Path::single("tuple"), vec![out; length]);
}
out
}
TypeKind::Map { key, value, .. } => py::Type::subscript(
Path::single("list"),
vec![py::Type::subscript(
Path::single("tuple"),
vec![inner(key.resolved()), inner(value.resolved())],
)],
),
TypeKind::Alias { aliased } => inner(aliased.resolved()),
TypeKind::ValueBox { boxed } => py::Type::Optional(Box::new(inner(boxed.resolved()))),
TypeKind::Any
| TypeKind::ValueBase
| TypeKind::ObjectRef { .. }
| TypeKind::AnnotationType { .. } => unknown(),
}
}
fn array_row(base: &str, depth: usize) -> py::Expr {
let mut out = py::Expr::name(base);
for level in 0..depth {
out = py::Expr::Index {
value: Box::new(out),
index: Box::new(py::Expr::name(&format!("zd_i{level}"))),
};
}
out
}
fn array_rows(base: &str, lengths: &[i128], depth: usize, into_tuple: bool) -> Vec<py::Statement> {
let func = if into_tuple { "tuple" } else { "list" };
let convert = |level: usize| {
let row = array_row(base, level);
py::Statement::Set {
place: row.clone(),
value: py::Expr::Call {
func: Path::single(func),
args: vec![py::Arg::Positional(row)],
},
}
};
let innermost = lengths.len().saturating_sub(1).max(depth);
let mut rows = vec![convert(innermost)];
for level in (depth..innermost).rev() {
let inward = py::Statement::For {
counter: Ident::new(&format!("zd_i{level}")),
bound: py::Expr::int(lengths[level]),
body: Python::suite(rows),
};
rows = if into_tuple {
vec![inward, convert(level)]
} else {
vec![convert(level), inward]
};
}
rows
}