1use runmat_builtins::{Tensor, Value};
2use runmat_macros::runtime_builtin;
3
4fn tensor_from_dims(dims: Vec<usize>) -> Result<Value, String> {
5 let len = dims.len();
7 let data: Vec<f64> = dims.into_iter().map(|d| d as f64).collect();
8 Ok(Value::Tensor(Tensor::new_2d(data, 1, len)?))
9}
10
11fn dims_of_value(v: &Value) -> Vec<usize> {
12 match v {
13 Value::Tensor(t) => {
14 if t.shape.is_empty() {
15 vec![1, 1]
16 } else if t.shape.len() == 1 {
17 vec![1, t.shape[0]]
18 } else {
19 t.shape.clone()
20 }
21 }
22 Value::LogicalArray(la) => {
23 if la.shape.is_empty() {
24 vec![1, 1]
25 } else if la.shape.len() == 1 {
26 vec![1, la.shape[0]]
27 } else {
28 la.shape.clone()
29 }
30 }
31 Value::StringArray(sa) => {
32 if sa.shape.is_empty() {
33 vec![1, 1]
34 } else if sa.shape.len() == 1 {
35 vec![1, sa.shape[0]]
36 } else {
37 sa.shape.clone()
38 }
39 }
40 Value::CharArray(ca) => vec![ca.rows, ca.cols],
41 _ => vec![1, 1],
43 }
44}
45
46fn numel_of_value(v: &Value) -> usize {
47 match v {
48 Value::Tensor(t) => t.data.len(),
49 Value::LogicalArray(la) => la.data.len(),
50 Value::StringArray(sa) => sa.data.len(),
51 Value::CharArray(ca) => ca.rows * ca.cols,
52 Value::Cell(ca) => ca.data.len(),
53 _ => 1,
55 }
56}
57
58fn ndims_of_value(v: &Value) -> usize {
59 let dims = dims_of_value(v);
60 if dims.len() < 2 {
62 2
63 } else {
64 dims.len()
65 }
66}
67
68#[runtime_builtin(name = "size")]
69fn size_builtin(a: Value, rest: Vec<Value>) -> Result<Value, String> {
70 if rest.is_empty() {
71 let dims = dims_of_value(&a);
72 return tensor_from_dims(dims);
73 }
74 let mut dims = dims_of_value(&a);
75 let dim: f64 = (&rest[0]).try_into()?;
76 let d = if dim < 1.0 { 1usize } else { dim as usize };
77 if dims.len() < 2 {
78 dims.resize(2, 1);
79 }
80 let out = if d == 0 {
81 1.0
82 } else if d <= dims.len() {
83 dims[d - 1] as f64
84 } else {
85 1.0
86 };
87 Ok(Value::Num(out))
88}
89
90#[runtime_builtin(name = "numel")]
91fn numel_builtin(a: Value) -> Result<f64, String> {
92 Ok(numel_of_value(&a) as f64)
93}
94
95#[runtime_builtin(name = "ndims")]
96fn ndims_builtin(a: Value) -> Result<f64, String> {
97 Ok(ndims_of_value(&a) as f64)
98}
99
100#[runtime_builtin(name = "isempty")]
101fn isempty_builtin(a: Value) -> Result<bool, String> {
102 Ok(match a {
103 Value::Tensor(t) => t.data.is_empty() || t.rows == 0 || t.cols == 0,
104 Value::LogicalArray(la) => la.data.is_empty() || la.shape.contains(&0),
105 Value::StringArray(sa) => sa.data.is_empty() || sa.rows == 0 || sa.cols == 0,
106 Value::CharArray(ca) => ca.rows == 0 || ca.cols == 0,
107 Value::Cell(ca) => ca.data.is_empty() || ca.rows == 0 || ca.cols == 0,
108 _ => false,
109 })
110}
111
112#[runtime_builtin(name = "islogical")]
113fn islogical_builtin(a: Value) -> Result<bool, String> {
114 Ok(matches!(a, Value::Bool(_) | Value::LogicalArray(_)))
115}
116
117#[runtime_builtin(name = "isnumeric")]
118fn isnumeric_builtin(a: Value) -> Result<bool, String> {
119 Ok(matches!(
120 a,
121 Value::Num(_)
122 | Value::Complex(_, _)
123 | Value::Int(_)
124 | Value::Tensor(_)
125 | Value::ComplexTensor(_)
126 ))
127}
128
129#[runtime_builtin(name = "ischar")]
130fn ischar_builtin(a: Value) -> Result<bool, String> {
131 Ok(matches!(a, Value::CharArray(_)))
132}
133
134#[runtime_builtin(name = "isstring")]
135fn isstring_builtin(a: Value) -> Result<bool, String> {
136 Ok(matches!(a, Value::String(_) | Value::StringArray(_)))
137}
138
139#[runtime_builtin(name = "class")]
140fn class_builtin(a: Value) -> Result<String, String> {
141 let s = match &a {
142 Value::Num(_) | Value::Tensor(_) => "double",
143 Value::ComplexTensor(_) => "double",
144 Value::Complex(_, _) => "double", Value::Int(iv) => iv.class_name(),
146 Value::Bool(_) | Value::LogicalArray(_) => "logical",
147 Value::String(_) | Value::StringArray(_) => "string",
148 Value::CharArray(_) => "char",
149 Value::Cell(_) => "cell",
150 Value::Struct(_) => "struct",
151 Value::GpuTensor(_) => "gpuArray",
152 Value::FunctionHandle(_) | Value::Closure(_) => "function_handle",
153 Value::HandleObject(_) => "handle",
154 Value::Listener(_) => "listener",
155 Value::Object(o) => o.class_name.as_str(),
156 Value::ClassRef(_) => "meta.class",
157 Value::MException(_) => "MException",
158 };
159 Ok(s.to_string())
160}
161
162#[runtime_builtin(name = "isa")]
163fn isa_builtin(a: Value, type_name: String) -> Result<bool, String> {
164 let t = type_name.to_lowercase();
165 let is = match &a {
166 Value::Num(_) | Value::Tensor(_) => t == "double" || t == "numeric",
167 Value::ComplexTensor(_) => t == "double" || t == "numeric",
168 Value::Complex(_, _) => t == "double" || t == "numeric",
169 Value::Int(iv) => t == iv.class_name() || t == "numeric",
170 Value::Bool(_) | Value::LogicalArray(_) => t == "logical",
171 Value::String(_) | Value::StringArray(_) => t == "string",
172 Value::CharArray(_) => t == "char",
173 Value::Cell(_) => t == "cell",
174 Value::Struct(_) => t == "struct",
175 Value::GpuTensor(_) => t == "gpuarray",
176 Value::FunctionHandle(_) | Value::Closure(_) => t == "function_handle",
177 Value::HandleObject(_) => t == "handle",
178 Value::Listener(_) => t == "listener",
179 Value::Object(o) => t == o.class_name.to_lowercase(),
180 Value::ClassRef(_) => t == "meta.class",
181 Value::MException(_) => t == "mexception",
182 };
183 Ok(is)
184}
185
186fn map_tensor_to_mask<F: Fn(f64) -> bool>(t: &Tensor, pred: F) -> Result<Value, String> {
191 let data: Vec<u8> = t
192 .data
193 .iter()
194 .map(|&x| if pred(x) { 1u8 } else { 0u8 })
195 .collect();
196 let out = runmat_builtins::LogicalArray::new(data, t.shape.clone())?;
197 Ok(Value::LogicalArray(out))
198}
199
200#[runtime_builtin(name = "isnan")]
201fn isnan_builtin(a: Value) -> Result<Value, String> {
202 Ok(match a {
203 Value::Num(x) => Value::Bool(x.is_nan()),
204 Value::Int(_) => Value::Bool(false),
205 Value::Tensor(t) => return map_tensor_to_mask(&t, |x| x.is_nan()),
206 Value::LogicalArray(la) => Value::LogicalArray(la),
207 _ => Value::Bool(false),
208 })
209}
210
211#[runtime_builtin(name = "isfinite")]
212fn isfinite_builtin(a: Value) -> Result<Value, String> {
213 Ok(match a {
214 Value::Num(x) => Value::Bool(x.is_finite()),
215 Value::Int(_) => Value::Bool(true),
216 Value::Tensor(t) => return map_tensor_to_mask(&t, |x| x.is_finite()),
217 Value::LogicalArray(la) => Value::LogicalArray(la),
218 _ => Value::Bool(false),
219 })
220}
221
222#[runtime_builtin(name = "isinf")]
223fn isinf_builtin(a: Value) -> Result<Value, String> {
224 Ok(match a {
225 Value::Num(x) => Value::Bool(x.is_infinite()),
226 Value::Int(_) => Value::Bool(false),
227 Value::Tensor(t) => return map_tensor_to_mask(&t, |x| x.is_infinite()),
228 Value::LogicalArray(la) => Value::LogicalArray(la),
229 _ => Value::Bool(false),
230 })
231}
232
233fn extract_scalar_string(v: &Value) -> Option<String> {
238 match v {
239 Value::String(s) => Some(s.clone()),
240 Value::CharArray(ca) => Some(ca.data.iter().collect()),
241 Value::StringArray(sa) => {
242 if sa.data.len() == 1 {
243 Some(sa.data[0].clone())
244 } else {
245 None
246 }
247 }
248 _ => None,
249 }
250}
251
252#[runtime_builtin(name = "strcmp")]
253fn strcmp_builtin(a: Value, b: Value) -> Result<bool, String> {
254 let sa = extract_scalar_string(&a)
255 .ok_or_else(|| "strcmp: expected string/char scalar inputs".to_string())?;
256 let sb = extract_scalar_string(&b)
257 .ok_or_else(|| "strcmp: expected string/char scalar inputs".to_string())?;
258 Ok(sa == sb)
259}
260
261#[runtime_builtin(name = "strcmpi")]
262fn strcmpi_builtin(a: Value, b: Value) -> Result<bool, String> {
263 let sa = extract_scalar_string(&a)
264 .ok_or_else(|| "strcmpi: expected string/char scalar inputs".to_string())?;
265 let sb = extract_scalar_string(&b)
266 .ok_or_else(|| "strcmpi: expected string/char scalar inputs".to_string())?;
267 Ok(sa.eq_ignore_ascii_case(&sb))
268}
269
270fn set_from_strings(v: &Value) -> Result<Vec<String>, String> {
271 match v {
272 Value::StringArray(sa) => Ok(sa.data.clone()),
273 Value::Cell(ca) => {
274 let mut out = Vec::new();
275 for p in &ca.data {
276 let elem = &**p;
277 if let Some(s) = extract_scalar_string(elem) {
278 out.push(s);
279 }
280 }
281 Ok(out)
282 }
283 Value::String(s) => Ok(vec![s.clone()]),
284 Value::CharArray(ca) => Ok(vec![ca.data.iter().collect()]),
285 _ => Err("ismember: expected set to be string array or cell of strings".to_string()),
286 }
287}
288
289#[runtime_builtin(name = "ismember")]
290fn ismember_strings(a: Value, set: Value) -> Result<Value, String> {
291 let set_vec = set_from_strings(&set)?;
292 match a {
293 Value::String(s) => Ok(Value::Bool(set_vec.iter().any(|x| x == &s))),
294 Value::CharArray(ca) => {
295 let s: String = ca.data.iter().collect();
296 Ok(Value::Bool(set_vec.iter().any(|x| x == &s)))
297 }
298 Value::StringArray(sa) => {
299 let mask: Vec<f64> = sa
300 .data
301 .iter()
302 .map(|s| {
303 if set_vec.iter().any(|x| x == s) {
304 1.0
305 } else {
306 0.0
307 }
308 })
309 .collect();
310 let out = Tensor::new_2d(mask, 1, sa.data.len())?;
312 Ok(Value::Tensor(out))
313 }
314 _ => Err("ismember: expected string or string array as first argument".to_string()),
315 }
316}