runmat_runtime/
indexing.rs

1//! Matrix indexing and slicing operations
2//!
3//! Implements language-style matrix indexing and access patterns.
4
5use runmat_builtins::{Tensor, Value};
6
7/// Get a single element from a matrix (1-based indexing like language)
8pub fn matrix_get_element(matrix: &Tensor, row: usize, col: usize) -> Result<f64, String> {
9    if row == 0 || col == 0 {
10        return Err("MATLAB uses 1-based indexing".to_string());
11    }
12    matrix.get2(row - 1, col - 1) // Convert to 0-based
13}
14
15/// Set a single element in a matrix (1-based indexing like language)
16pub fn matrix_set_element(
17    matrix: &mut Tensor,
18    row: usize,
19    col: usize,
20    value: f64,
21) -> Result<(), String> {
22    if row == 0 || col == 0 {
23        return Err("The MATLAB language uses 1-based indexing".to_string());
24    }
25    matrix.set2(row - 1, col - 1, value) // Convert to 0-based
26}
27
28/// Get a row from a matrix
29pub fn matrix_get_row(matrix: &Tensor, row: usize) -> Result<Tensor, String> {
30    if row == 0 || row > matrix.rows() {
31        return Err(format!(
32            "Row index {} out of bounds for {}x{} matrix",
33            row,
34            matrix.rows(),
35            matrix.cols()
36        ));
37    }
38
39    // Column-major: row slice picks every element spaced by rows across columns
40    let mut row_data = Vec::with_capacity(matrix.cols());
41    for c in 0..matrix.cols() {
42        row_data.push(matrix.data[(row - 1) + c * matrix.rows()]);
43    }
44    Tensor::new_2d(row_data, 1, matrix.cols())
45}
46
47/// Get a column from a matrix
48pub fn matrix_get_col(matrix: &Tensor, col: usize) -> Result<Tensor, String> {
49    if col == 0 || col > matrix.cols() {
50        return Err(format!(
51            "Column index {} out of bounds for {}x{} matrix",
52            col,
53            matrix.rows(),
54            matrix.cols()
55        ));
56    }
57
58    let mut col_data = Vec::with_capacity(matrix.rows());
59    for row in 0..matrix.rows() {
60        col_data.push(matrix.data[row + (col - 1) * matrix.rows()]);
61    }
62    Tensor::new_2d(col_data, matrix.rows(), 1)
63}
64
65/// Array indexing operation (used by all interpreters/compilers)
66/// In MATLAB, indexing is 1-based and supports:
67/// - Single element: A(i) for vectors, A(i,j) for matrices
68/// - Multiple indices: A(i1, i2, ..., iN)
69pub fn perform_indexing(base: &Value, indices: &[f64]) -> Result<Value, String> {
70    match base {
71        Value::Tensor(matrix) => {
72            if indices.is_empty() {
73                return Err("At least one index is required".to_string());
74            }
75
76            if indices.len() == 1 {
77                // Linear indexing (1-based)
78                let idx = indices[0] as usize;
79                if idx < 1 || idx > matrix.data.len() {
80                    return Err(format!(
81                        "Index {} out of bounds (1 to {})",
82                        idx,
83                        matrix.data.len()
84                    ));
85                }
86                Ok(Value::Num(matrix.data[idx - 1])) // Convert to 0-based
87            } else if indices.len() == 2 {
88                // Row-column indexing (1-based)
89                let row = indices[0] as usize;
90                let col = indices[1] as usize;
91
92                if row < 1 || row > matrix.rows {
93                    return Err(format!(
94                        "Row index {} out of bounds (1 to {})",
95                        row, matrix.rows
96                    ));
97                }
98                if col < 1 || col > matrix.cols {
99                    return Err(format!(
100                        "Column index {} out of bounds (1 to {})",
101                        col, matrix.cols
102                    ));
103                }
104
105                let linear_idx = (row - 1) + (col - 1) * matrix.rows; // Convert to 0-based, column-major
106                Ok(Value::Num(matrix.data[linear_idx]))
107            } else {
108                Err(format!(
109                    "Matrices support 1 or 2 indices, got {}",
110                    indices.len()
111                ))
112            }
113        }
114        Value::StringArray(sa) => {
115            if indices.is_empty() {
116                return Err("At least one index is required".to_string());
117            }
118            if indices.len() == 1 {
119                let idx = indices[0] as usize;
120                let total = sa.data.len();
121                if idx < 1 || idx > total {
122                    return Err(format!("Index {idx} out of bounds (1 to {total})"));
123                }
124                Ok(Value::String(sa.data[idx - 1].clone()))
125            } else if indices.len() == 2 {
126                let row = indices[0] as usize;
127                let col = indices[1] as usize;
128                if row < 1 || row > sa.rows || col < 1 || col > sa.cols {
129                    return Err("StringArray subscript out of bounds".to_string());
130                }
131                let idx = (row - 1) + (col - 1) * sa.rows;
132                Ok(Value::String(sa.data[idx].clone()))
133            } else {
134                Err(format!(
135                    "StringArray supports 1 or 2 indices, got {}",
136                    indices.len()
137                ))
138            }
139        }
140        Value::Num(_) | Value::Int(_) => {
141            if indices.len() == 1 && indices[0] == 1.0 {
142                // Scalar indexing with A(1) returns the scalar itself
143                Ok(base.clone())
144            } else {
145                Err("MATLAB:SliceNonTensor: Slicing only supported on tensors".to_string())
146            }
147        }
148        Value::Cell(ca) => {
149            if indices.is_empty() {
150                return Err("At least one index is required".to_string());
151            }
152            if indices.len() == 1 {
153                let idx = indices[0] as usize;
154                if idx < 1 || idx > ca.data.len() {
155                    return Err(format!(
156                        "Cell index {} out of bounds (1 to {})",
157                        idx,
158                        ca.data.len()
159                    ));
160                }
161                Ok((*ca.data[idx - 1]).clone())
162            } else if indices.len() == 2 {
163                let row = indices[0] as usize;
164                let col = indices[1] as usize;
165                if row < 1 || row > ca.rows || col < 1 || col > ca.cols {
166                    return Err("Cell subscript out of bounds".to_string());
167                }
168                Ok((*ca.data[(row - 1) * ca.cols + (col - 1)]).clone())
169            } else {
170                Err(format!(
171                    "Cell arrays support 1 or 2 indices, got {}",
172                    indices.len()
173                ))
174            }
175        }
176        _ => Err(format!("Cannot index value of type {base:?}")),
177    }
178}