1use crate::builtins::common::validation;
4use crate::object::cell::expand_all_cell_values;
5use crate::runtime_error::semantic_error;
6use crate::RuntimeError;
7use runmat_types::{
8 FunctionArgDefaultValue, FunctionArgDim, FunctionArgSizeSpec, FunctionArgValidationLiteral,
9 FunctionArgValidator,
10};
11use runmat_value::{CellArray, IntValue, Tensor, Value};
12
13#[derive(Clone, Copy, Debug)]
18pub struct FunctionInputSpec<'a> {
19 pub input_index: usize,
20 pub size: Option<&'a FunctionArgSizeSpec>,
21 pub class_name: Option<&'a str>,
22 pub validators: &'a [FunctionArgValidator],
23 pub default_value: Option<&'a FunctionArgDefaultValue>,
24}
25
26#[derive(Clone, Debug)]
27pub struct PreparedFunctionInputs {
28 pub fixed: Vec<Option<Value>>,
31 pub varargin: Option<CellArray>,
32 pub nargin: usize,
33}
34
35pub fn prepare_function_inputs(
36 function_name: &str,
37 supplied: &[Value],
38 fixed_input_count: usize,
39 accepts_varargin: bool,
40 specs: &[FunctionInputSpec<'_>],
41) -> Result<PreparedFunctionInputs, RuntimeError> {
42 let preparation = prepare_function_inputs_async(
43 function_name,
44 supplied,
45 fixed_input_count,
46 accepts_varargin,
47 specs,
48 );
49 #[cfg(not(target_arch = "wasm32"))]
50 {
51 pollster::block_on(preparation)
52 }
53 #[cfg(target_arch = "wasm32")]
54 {
55 futures::executor::block_on(preparation)
56 }
57}
58
59pub async fn prepare_function_inputs_async(
60 function_name: &str,
61 supplied: &[Value],
62 fixed_input_count: usize,
63 accepts_varargin: bool,
64 specs: &[FunctionInputSpec<'_>],
65) -> Result<PreparedFunctionInputs, RuntimeError> {
66 if supplied.len() > fixed_input_count && !accepts_varargin {
67 return Err(semantic_error(
68 "TooManyInputs",
69 format!(
70 "semantic function {function_name} expected {fixed_input_count} inputs, got {}",
71 supplied.len()
72 ),
73 ));
74 }
75 if specs
76 .iter()
77 .any(|spec| spec.input_index >= fixed_input_count)
78 {
79 return Err(semantic_error(
80 "InvalidInputSlot",
81 "function argument slot out of bounds",
82 ));
83 }
84
85 let mut fixed = vec![None; fixed_input_count];
86 for (target, value) in fixed.iter_mut().zip(supplied) {
87 *target = Some(value.clone());
88 }
89 for spec in specs {
90 if fixed[spec.input_index].is_none() {
91 if let Some(default) = spec.default_value {
92 fixed[spec.input_index] = Some(default_value(default));
93 }
94 }
95 if let Some(value) = &fixed[spec.input_index] {
96 validate_input(function_name, spec.input_index, value, spec).await?;
97 }
98 }
99
100 let varargin = if accepts_varargin {
101 let values = supplied
102 .get(fixed_input_count..)
103 .unwrap_or_default()
104 .to_vec();
105 let columns = values.len();
106 Some(
107 CellArray::new(values, 1, columns)
108 .map_err(|error| semantic_error("VararginPack", format!("varargin: {error}")))?,
109 )
110 } else {
111 None
112 };
113 Ok(PreparedFunctionInputs {
114 fixed,
115 varargin,
116 nargin: supplied.len(),
117 })
118}
119
120pub fn collect_function_outputs(
121 function_name: &str,
122 fixed_outputs: &[Value],
123 varargout: Option<&Value>,
124 requested_outputs: usize,
125) -> Result<Vec<Value>, RuntimeError> {
126 if requested_outputs > fixed_outputs.len() && varargout.is_none() {
127 return Err(semantic_error(
128 "TooManyOutputs",
129 format!(
130 "semantic function {function_name} expected {} outputs, got {requested_outputs}",
131 fixed_outputs.len()
132 ),
133 ));
134 }
135 let mut values = fixed_outputs
136 .iter()
137 .take(requested_outputs)
138 .cloned()
139 .collect::<Vec<_>>();
140 if values.len() < requested_outputs {
141 if let Some(varargout) = varargout {
142 let expanded = match varargout {
143 Value::Cell(cell) => expand_all_cell_values(cell)?,
144 _ => Vec::new(),
145 };
146 let available = expanded.len();
147 values.extend(expanded.into_iter().take(requested_outputs - values.len()));
148 if values.len() < requested_outputs {
149 let needed = requested_outputs - fixed_outputs.len();
150 return Err(semantic_error(
151 "VarargoutMismatch",
152 format!(
153 "Function '{function_name}' returned {available} varargout values, {needed} requested"
154 ),
155 ));
156 }
157 }
158 }
159 values.resize(requested_outputs, Value::Num(0.0));
160 Ok(values)
161}
162
163fn default_value(default: &FunctionArgDefaultValue) -> Value {
164 match default {
165 FunctionArgDefaultValue::Number(value) => Value::Num(*value),
166 FunctionArgDefaultValue::Integer(value) => Value::Int(IntValue::from(value)),
167 FunctionArgDefaultValue::Bool(value) => Value::Bool(*value),
168 FunctionArgDefaultValue::String(value) => Value::String(value.clone()),
169 FunctionArgDefaultValue::EmptyArray => Value::Tensor(
170 Tensor::new(Vec::new(), vec![0, 0]).expect("empty default tensor is always valid"),
171 ),
172 }
173}
174
175async fn validate_input(
176 function_name: &str,
177 input_index: usize,
178 value: &Value,
179 spec: &FunctionInputSpec<'_>,
180) -> Result<(), RuntimeError> {
181 if let Some(size) = spec.size {
182 let (rows, columns) = validation::value_shape_2d(value);
183 if !dim_matches(&size.rows, rows) || !dim_matches(&size.cols, columns) {
184 return Err(semantic_error(
185 "ArgumentValidationSize",
186 format!(
187 "Function '{function_name}' argument #{} failed size validation",
188 input_index + 1
189 ),
190 ));
191 }
192 }
193 if let Some(class_name) = spec.class_name {
194 if !validation::value_matches_class(value, class_name) {
195 return Err(semantic_error(
196 "ArgumentValidationClass",
197 format!(
198 "Function '{function_name}' argument #{} failed class validation (expected {class_name})",
199 input_index + 1
200 ),
201 ));
202 }
203 }
204 for validator in spec.validators {
205 if !validator_passes(value, validator).await? {
206 return Err(semantic_error(
207 "ArgumentValidationFunction",
208 format!(
209 "Function '{function_name}' argument #{} failed {} validation",
210 input_index + 1,
211 validator_name(validator)
212 ),
213 ));
214 }
215 }
216 Ok(())
217}
218
219fn dim_matches(dim: &FunctionArgDim, actual: usize) -> bool {
220 matches!(dim, FunctionArgDim::Any)
221 || matches!(dim, FunctionArgDim::Exact(expected) if *expected == actual)
222}
223
224async fn validator_passes(
225 value: &Value,
226 validator: &FunctionArgValidator,
227) -> Result<bool, RuntimeError> {
228 use FunctionArgValidator as V;
229 Ok(match validator {
230 V::A(names) => validation::must_be_a(value, names.clone())?,
231 V::Column => validation::value_is_column(value),
232 V::Finite => {
233 validation::ensure_resident_extension(value, "mustBeFinite")?;
234 validation::value_is_finite_async(value).await?
235 }
236 V::Float => validation::value_is_float(value),
237 V::Folder => validation::dispatch_validator_async("mustBeFolder", vec![value.clone()])
238 .await
239 .is_ok(),
240 V::File => validation::dispatch_validator_async("mustBeFile", vec![value.clone()])
241 .await
242 .is_ok(),
243 V::NumericOrLogical => validation::value_is_numeric_or_logical(value),
244 V::Numeric => validation::value_is_numeric(value),
245 V::Text => validation::value_is_text(value),
246 V::TextScalar => validation::value_is_text_scalar(value),
247 V::NonzeroLengthText => validation::value_is_nonzero_length_text(value),
248 V::Nonempty => !validation::value_is_empty(value),
249 V::ScalarOrEmpty => validation::value_is_scalar_or_empty(value),
250 V::Real => validation::value_is_real_async(value).await?,
251 V::Integer => {
252 validation::ensure_resident_extension(value, "mustBeInteger")?;
253 validation::value_is_integer_async(value).await?
254 }
255 V::Vector { allow_all_empties } => {
256 validation::value_satisfies_vector_validator(value, *allow_all_empties)?
257 }
258 V::Positive => validation::value_is_positive_async(value).await?,
259 V::Negative => validation::value_is_negative_async(value).await?,
260 V::Nonnegative => validation::value_is_nonnegative_async(value).await?,
261 V::Nonmissing => validation::value_is_nonmissing_async(value).await?,
262 V::NonNan => {
263 validation::ensure_resident_extension(value, "mustBeNonNan")?;
264 validation::value_is_non_nan_async(value).await?
265 }
266 V::Nonzero => {
267 validation::ensure_resident_extension(value, "mustBeNonzero")?;
268 validation::value_is_nonzero_async(value).await?
269 }
270 V::Nonpositive => validation::value_is_nonpositive_async(value).await?,
271 V::Nonsparse => {
272 validation::dispatch_validator_async("mustBeNonsparse", vec![value.clone()])
273 .await
274 .is_ok()
275 }
276 V::Sparse => validation::dispatch_validator_async("mustBeSparse", vec![value.clone()])
277 .await
278 .is_ok(),
279 V::ValidVariableName => validation::isvarname_value(value),
280 V::UnderlyingType(names) => {
281 validation::value_underlying_type_matches(value, names.clone())?
282 }
283 V::Member(literals) => {
284 let allowed = literals.iter().map(literal_atom).collect::<Vec<_>>();
285 validation::value_is_member_atoms_async(value, &allowed).await?
286 }
287 V::InRange(lower, upper, inclusivity) => {
288 validation::value_is_in_range_documented_async(
289 value,
290 &Value::Num(*lower),
291 &Value::Num(*upper),
292 validation::RangeInclusivity {
293 lower: inclusivity.lower,
294 upper: inclusivity.upper,
295 },
296 )
297 .await?
298 }
299 V::GreaterThanOrEqual(threshold) => {
300 validation::value_is_greater_than_or_equal_values_async(value, &Value::Num(*threshold))
301 .await?
302 }
303 V::LessThanOrEqual(threshold) => {
304 validation::value_is_less_than_or_equal_values_async(value, &Value::Num(*threshold))
305 .await?
306 }
307 V::GreaterThan(threshold) => {
308 validation::value_is_greater_than_values_async(value, &Value::Num(*threshold)).await?
309 }
310 V::LessThan(threshold) => {
311 validation::value_is_less_than_values_async(value, &Value::Num(*threshold)).await?
312 }
313 })
314}
315
316fn literal_atom(literal: &FunctionArgValidationLiteral) -> validation::ValidationAtom {
317 match literal {
318 FunctionArgValidationLiteral::Number(value) => validation::ValidationAtom::Number(*value),
319 FunctionArgValidationLiteral::Integer(value) => {
320 validation::ValidationAtom::Integer(IntValue::from(value))
321 }
322 FunctionArgValidationLiteral::Text(value) => {
323 validation::ValidationAtom::Text(value.clone())
324 }
325 FunctionArgValidationLiteral::Bool(value) => validation::ValidationAtom::Bool(*value),
326 }
327}
328
329fn validator_name(validator: &FunctionArgValidator) -> &'static str {
330 use FunctionArgValidator as V;
331 match validator {
332 V::A(_) => "mustBeA",
333 V::Column => "mustBeColumn",
334 V::Finite => "mustBeFinite",
335 V::Float => "mustBeFloat",
336 V::Folder => "mustBeFolder",
337 V::File => "mustBeFile",
338 V::NumericOrLogical => "mustBeNumericOrLogical",
339 V::Numeric => "mustBeNumeric",
340 V::Text => "mustBeText",
341 V::TextScalar => "mustBeTextScalar",
342 V::NonzeroLengthText => "mustBeNonzeroLengthText",
343 V::Nonempty => "mustBeNonempty",
344 V::ScalarOrEmpty => "mustBeScalarOrEmpty",
345 V::Real => "mustBeReal",
346 V::Integer => "mustBeInteger",
347 V::Vector { .. } => "mustBeVector",
348 V::Positive => "mustBePositive",
349 V::Negative => "mustBeNegative",
350 V::Nonnegative => "mustBeNonnegative",
351 V::Nonmissing => "mustBeNonmissing",
352 V::NonNan => "mustBeNonNan",
353 V::Nonzero => "mustBeNonzero",
354 V::Nonpositive => "mustBeNonpositive",
355 V::Nonsparse => "mustBeNonsparse",
356 V::Sparse => "mustBeSparse",
357 V::ValidVariableName => "mustBeValidVariableName",
358 V::UnderlyingType(_) => "mustBeUnderlyingType",
359 V::Member(_) => "mustBeMember",
360 V::InRange(_, _, _) => "mustBeInRange",
361 V::GreaterThanOrEqual(_) => "mustBeGreaterThanOrEqual",
362 V::LessThanOrEqual(_) => "mustBeLessThanOrEqual",
363 V::GreaterThan(_) => "mustBeGreaterThan",
364 V::LessThan(_) => "mustBeLessThan",
365 }
366}
367
368#[cfg(test)]
369mod tests {
370 use super::*;
371
372 #[test]
373 fn prepares_defaults_varargin_and_nargin() {
374 let positive = [FunctionArgValidator::Positive];
375 let default = FunctionArgDefaultValue::Number(3.0);
376 let specs = [FunctionInputSpec {
377 input_index: 1,
378 size: None,
379 class_name: None,
380 validators: &positive,
381 default_value: Some(&default),
382 }];
383 let prepared = prepare_function_inputs("sample", &[Value::Num(1.0)], 2, true, &specs)
384 .expect("prepare inputs");
385 assert_eq!(
386 prepared.fixed,
387 vec![Some(Value::Num(1.0)), Some(Value::Num(3.0))]
388 );
389 assert_eq!(prepared.nargin, 1);
390 assert_eq!(prepared.varargin.unwrap().data.len(), 0);
391 }
392
393 #[test]
394 fn preserves_omitted_unassigned_inputs_and_rejects_validation_failures() {
395 let positive = [FunctionArgValidator::Positive];
396 let specs = [FunctionInputSpec {
397 input_index: 0,
398 size: None,
399 class_name: None,
400 validators: &positive,
401 default_value: None,
402 }];
403 assert_eq!(
404 prepare_function_inputs("sample", &[], 1, false, &specs)
405 .unwrap()
406 .fixed,
407 vec![None]
408 );
409 let error =
410 prepare_function_inputs("sample", &[Value::Num(-1.0)], 1, false, &specs).unwrap_err();
411 assert_eq!(
412 error.identifier(),
413 Some("RunMat:ArgumentValidationFunction")
414 );
415 }
416
417 #[test]
418 fn collects_fixed_and_variadic_outputs_exactly() {
419 let cell = CellArray::new(vec![Value::Num(2.0), Value::Num(3.0)], 1, 2).unwrap();
420 assert_eq!(
421 collect_function_outputs("sample", &[Value::Num(1.0)], Some(&Value::Cell(cell)), 3,)
422 .unwrap(),
423 vec![Value::Num(1.0), Value::Num(2.0), Value::Num(3.0)]
424 );
425 }
426}