1use runmat_value::{CellArray, CharArray, LogicalArray, ObjectArray, SymbolicArray, Value};
9
10use crate::indexing::plan::{build_index_plan, IndexPlan};
11use crate::indexing::read_slice::{
12 gather_string_slice, read_complex_slice_from_plan, read_gpu_slice_from_plan,
13 read_sparse_slice_from_plan, read_tensor_slice_from_plan,
14};
15use crate::indexing::selectors::SliceSelector;
16use crate::object::dispatch::call_object_index_descriptor_method;
17use crate::object::indexing::ObjectIndexDescriptor;
18use crate::{call_builtin_async, RuntimeError};
19
20#[derive(Debug, Clone)]
25pub struct ForColumnIterator {
26 source: Value,
27 next_column: usize,
28 column_count: usize,
29}
30
31impl ForColumnIterator {
32 pub async fn new(source: Value) -> Result<Self, RuntimeError> {
34 let size = call_builtin_async("size", &[source.clone(), Value::Num(2.0)]).await?;
35 let column_count = dimension_extent(&size)?;
36 Ok(Self {
37 source,
38 next_column: 0,
39 column_count,
40 })
41 }
42
43 pub async fn next(&mut self) -> Result<Option<Value>, RuntimeError> {
45 if self.next_column >= self.column_count {
46 return Ok(None);
47 }
48 let column = self.next_column;
49 self.next_column += 1;
50 read_column(&self.source, column).await.map(Some)
51 }
52}
53
54fn dimension_extent(value: &Value) -> Result<usize, RuntimeError> {
55 match value {
56 Value::Num(value)
57 if value.is_finite()
58 && value.fract() == 0.0
59 && *value >= 0.0
60 && *value <= usize::MAX as f64 =>
61 {
62 Ok(*value as usize)
63 }
64 Value::Int(value) => value.try_to_usize().ok_or_else(invalid_dimension),
65 other => Err(crate::runtime_error::semantic_error(
66 "InvalidLoopDimension",
67 format!("size returned a non-scalar loop dimension: {other:?}"),
68 )),
69 }
70}
71
72fn invalid_dimension() -> RuntimeError {
73 crate::runtime_error::semantic_error(
74 "InvalidLoopDimension",
75 "size returned a loop dimension outside platform limits",
76 )
77}
78
79async fn read_column(source: &Value, column: usize) -> Result<Value, RuntimeError> {
80 match source {
81 Value::Tensor(value) => {
82 read_tensor_slice_from_plan(value, &column_plan(&value.shape, column)?)
83 }
84 Value::ComplexTensor(value) => {
85 read_complex_slice_from_plan(value, &column_plan(&value.shape, column)?)
86 }
87 Value::SparseTensor(value) => {
88 read_sparse_slice_from_plan(value, &column_plan(&value.shape(), column)?)
89 }
90 Value::GpuTensor(value) => {
91 read_gpu_slice_from_plan(value, &column_plan(&value.shape, column)?)
92 }
93 Value::StringArray(value) => {
94 gather_string_slice(value, &column_plan(&value.shape, column)?)
95 }
96 Value::LogicalArray(value) => gather_logical(value, &column_plan(&value.shape, column)?),
97 Value::CharArray(value) => gather_char(value, &column_plan(value.shape(), column)?),
98 Value::Cell(value) => gather_cell(value, &column_plan(&value.shape, column)?),
99 Value::ObjectArray(value) => {
100 gather_object_array(value, &column_plan(value.shape(), column)?)
101 }
102 Value::SymbolicArray(value) => gather_symbolic(value, &column_plan(&value.shape, column)?),
103 Value::Object(_) | Value::HandleObject(_) => {
104 let descriptor = ObjectIndexDescriptor::subsref_paren_from_slice(
105 source.clone(),
106 2,
107 1,
108 0,
109 &[Value::Num((column + 1) as f64)],
110 )?;
111 call_object_index_descriptor_method(descriptor).await
112 }
113 value => Ok(value.clone()),
115 }
116}
117
118fn column_plan(shape: &[usize], column: usize) -> Result<IndexPlan, RuntimeError> {
119 build_index_plan(
120 &[SliceSelector::Colon, SliceSelector::Scalar(column + 1)],
121 2,
122 shape,
123 )
124}
125
126fn gather_logical(value: &LogicalArray, plan: &IndexPlan) -> Result<Value, RuntimeError> {
127 let data = gather(&value.data, plan, "logical")?;
128 if data.len() == 1 {
129 return Ok(Value::Bool(data[0] != 0));
130 }
131 LogicalArray::new(data, plan.output_shape.clone())
132 .map(Value::LogicalArray)
133 .map_err(shape_error)
134}
135
136fn gather_char(value: &CharArray, plan: &IndexPlan) -> Result<Value, RuntimeError> {
137 let data = gather(&value.to_column_major(), plan, "character")?;
138 CharArray::from_column_major(data, plan.output_shape.clone())
139 .map(Value::CharArray)
140 .map_err(shape_error)
141}
142
143fn gather_cell(value: &CellArray, plan: &IndexPlan) -> Result<Value, RuntimeError> {
144 let indices = plan
145 .indices
146 .iter()
147 .map(|index| *index as usize + 1)
148 .collect::<Vec<_>>();
149 crate::object::cell::gather_cell_paren_linear_indices(value, &indices, &plan.output_shape)
150}
151
152fn gather_object_array(value: &ObjectArray, plan: &IndexPlan) -> Result<Value, RuntimeError> {
153 if let [index] = plan.indices.as_slice() {
154 return value.get_linear(*index as usize).cloned().ok_or_else(|| {
155 crate::runtime_error::semantic_error("IndexOutOfBounds", "Index out of bounds")
156 });
157 }
158 let indices = plan
159 .indices
160 .iter()
161 .map(|index| *index as usize)
162 .collect::<Vec<_>>();
163 value
164 .select_linear(&indices, plan.output_shape.clone())
165 .map(Value::ObjectArray)
166 .map_err(shape_error)
167}
168
169fn gather_symbolic(value: &SymbolicArray, plan: &IndexPlan) -> Result<Value, RuntimeError> {
170 let data = gather(&value.data, plan, "symbolic")?;
171 SymbolicArray::new(data, plan.output_shape.clone())
172 .map(Value::SymbolicArray)
173 .map_err(shape_error)
174}
175
176fn gather<T: Clone>(data: &[T], plan: &IndexPlan, kind: &str) -> Result<Vec<T>, RuntimeError> {
177 plan.indices
178 .iter()
179 .map(|index| {
180 data.get(*index as usize).cloned().ok_or_else(|| {
181 crate::runtime_error::semantic_error(
182 "IndexOutOfBounds",
183 format!("{kind} loop column index is out of bounds"),
184 )
185 })
186 })
187 .collect()
188}
189
190fn shape_error(error: impl std::fmt::Display) -> RuntimeError {
191 crate::runtime_error::semantic_error("ShapeMismatch", error.to_string())
192}
193
194#[cfg(test)]
195mod tests {
196 use std::rc::Rc;
197
198 use futures::executor::block_on;
199 use runmat_value::{CellArray, IntValue, IntegerStorage, Tensor, Value};
200
201 use super::ForColumnIterator;
202 use crate::context::RuntimeContext;
203 use crate::execution::RuntimeExecutionService;
204
205 fn collect(source: Value) -> Vec<Value> {
206 let runtime = RuntimeContext::new(Rc::new(RuntimeExecutionService::new()));
207 block_on(runtime.scope(async move {
208 let mut columns = ForColumnIterator::new(source).await.unwrap();
209 let mut values = Vec::new();
210 while let Some(value) = columns.next().await.unwrap() {
211 values.push(value);
212 }
213 values
214 }))
215 }
216
217 #[test]
218 fn iterates_dense_columns_without_losing_integer_storage() {
219 let tensor = Tensor::new_integer(
220 IntegerStorage::U64(vec![1_u64 << 63, u64::MAX, 7, 8]),
221 vec![2, 2],
222 )
223 .unwrap();
224 let columns = collect(Value::Tensor(tensor));
225 assert_eq!(columns.len(), 2);
226 let Value::Tensor(first) = &columns[0] else {
227 panic!("expected a tensor column")
228 };
229 assert_eq!(
230 first.integer_storage(),
231 Some(&IntegerStorage::U64(vec![1_u64 << 63, u64::MAX]))
232 );
233 }
234
235 #[test]
236 fn iterates_cell_columns_with_matlab_paren_shape() {
237 let cell = CellArray::from_column_major(
238 vec![
239 Value::Int(IntValue::I32(1)),
240 Value::Int(IntValue::I32(2)),
241 Value::Int(IntValue::I32(3)),
242 Value::Int(IntValue::I32(4)),
243 ],
244 vec![2, 2],
245 )
246 .unwrap();
247 let columns = collect(Value::Cell(cell));
248 let Value::Cell(second) = &columns[1] else {
249 panic!("expected a cell column")
250 };
251 assert_eq!(second.shape, vec![2, 1]);
252 assert_eq!(
253 second.to_column_major(),
254 vec![Value::Int(IntValue::I32(3)), Value::Int(IntValue::I32(4))]
255 );
256 }
257}