Skip to main content

runmat_runtime/
condition.rs

1//! Executor-neutral condition conversion.
2//!
3//! Bytecode, native, and browser executors must agree on which runtime values
4//! are valid scalar conditions and how accelerator-backed values are gathered.
5
6use runmat_value::Value;
7
8use crate::builtins::common::tensor::{is_scalar_tensor, tensor_element_len, tensor_value_f64};
9use crate::{gather_if_needed_async, runtime_error::semantic_error, RuntimeError};
10
11/// Convert a runtime value to the scalar truth value required by control flow.
12pub async fn logical_truth_from_value(value: &Value, label: &str) -> Result<bool, RuntimeError> {
13    match value {
14        Value::Bool(flag) => Ok(*flag),
15        Value::Int(integer) => Ok(!integer.is_zero()),
16        Value::Num(number) => Ok(*number != 0.0),
17        Value::LogicalArray(array) if array.data.len() == 1 => Ok(array.data[0] != 0),
18        Value::LogicalArray(array) => Err(invalid_condition(
19            label,
20            format!("logical array with {} elements", array.data.len()),
21        )),
22        Value::Tensor(tensor) if is_scalar_tensor(tensor) => Ok(tensor_value_f64(tensor, 0) != 0.0),
23        Value::Tensor(tensor) => Err(invalid_condition(
24            label,
25            format!("numeric array with {} elements", tensor_element_len(tensor)),
26        )),
27        Value::GpuTensor(_) => {
28            let gathered = gather_if_needed_async(value)
29                .await
30                // Preserve the VM-era diagnostic behavior while centralizing
31                // the conversion: gather failures are execution failures, not
32                // invalid-condition semantic errors.
33                .map_err(|error| RuntimeError::new(format!("{label}: {error}")))?;
34            Box::pin(logical_truth_from_value(&gathered, label)).await
35        }
36        other => Err(invalid_condition(label, format!("{other:?}"))),
37    }
38}
39
40fn invalid_condition(label: &str, actual: String) -> RuntimeError {
41    semantic_error(
42        "InvalidConditionType",
43        format!("{label}: expected scalar logical or numeric value, got {actual}"),
44    )
45}
46
47#[cfg(test)]
48mod tests {
49    use futures::executor::block_on;
50    use runmat_value::{IntValue, LogicalArray, Tensor};
51
52    use super::*;
53
54    #[test]
55    fn accepts_scalar_logical_and_numeric_values() {
56        assert!(!block_on(logical_truth_from_value(&Value::Bool(false), "condition")).unwrap());
57        assert!(block_on(logical_truth_from_value(
58            &Value::Int(IntValue::I32(-2)),
59            "condition"
60        ))
61        .unwrap());
62        assert!(!block_on(logical_truth_from_value(&Value::Num(0.0), "condition")).unwrap());
63        assert!(block_on(logical_truth_from_value(
64            &Value::LogicalArray(LogicalArray::new(vec![1], vec![1, 1]).unwrap()),
65            "condition"
66        ))
67        .unwrap());
68        assert!(block_on(logical_truth_from_value(
69            &Value::Tensor(Tensor::new_2d(vec![3.0], 1, 1).unwrap()),
70            "condition"
71        ))
72        .unwrap());
73    }
74
75    #[test]
76    fn rejects_nonscalar_and_non_numeric_values_with_semantic_identity() {
77        let error = block_on(logical_truth_from_value(
78            &Value::Tensor(Tensor::new_2d(vec![1.0, 2.0], 1, 2).unwrap()),
79            "if condition",
80        ))
81        .unwrap_err();
82        assert_eq!(error.identifier(), Some("RunMat:InvalidConditionType"));
83        assert!(error
84            .to_string()
85            .contains("if condition: expected scalar logical or numeric value"));
86    }
87}