runmat-runtime 0.6.0

Core runtime for RunMat with builtins, BLAS/LAPACK integration, and execution APIs
Documentation
use super::*;

pub(in crate::builtins::table) fn dictionary_from_args(args: Vec<Value>) -> BuiltinResult<Value> {
    let (keys, values) = match args.as_slice() {
        [] => (Vec::new(), Vec::new()),
        [keys, values] => {
            let keys = value_elements(keys)?;
            let values = value_elements(values)?;
            if keys.len() != values.len() {
                return Err(invalid_argument(
                    "dictionary: keys and values must have the same number of elements",
                ));
            }
            (keys, values)
        }
        _ if args.len().is_multiple_of(2) => {
            let mut keys = Vec::new();
            let mut values = Vec::new();
            let mut idx = 0usize;
            while idx < args.len() {
                keys.push(args[idx].clone());
                values.push(args[idx + 1].clone());
                idx += 2;
            }
            (keys, values)
        }
        _ => {
            return Err(invalid_argument(
                "dictionary: expected keys and values, or key/value pairs",
            ))
        }
    };
    let key_count = keys.len();
    let value_count = values.len();
    let mut object = ObjectInstance::new(DICTIONARY_CLASS.to_string());
    object.properties.insert(
        "Keys".to_string(),
        Value::Cell(CellArray::new(keys, 1, key_count).map_err(invalid_variable)?),
    );
    object.properties.insert(
        "Values".to_string(),
        Value::Cell(CellArray::new(values, 1, value_count).map_err(invalid_variable)?),
    );
    Ok(Value::Object(object))
}

pub(in crate::builtins::table) fn into_dictionary_object(
    value: Value,
    context: &str,
) -> BuiltinResult<ObjectInstance> {
    match value {
        Value::Object(object) if object.is_class(DICTIONARY_CLASS) => Ok(object),
        other => Err(invalid_argument(format!(
            "{context}: expected dictionary, got {other:?}"
        ))),
    }
}

pub(in crate::builtins::table) fn dictionary_cells<'a>(
    object: &'a ObjectInstance,
    field: &str,
) -> BuiltinResult<&'a CellArray> {
    match object.properties.get(field) {
        Some(Value::Cell(cell)) => Ok(cell),
        Some(other) => Err(invalid_variable(format!(
            "dictionary: {field} storage must be a cell array, got {other:?}"
        ))),
        None => Err(invalid_variable(format!(
            "dictionary: missing {field} storage"
        ))),
    }
}

pub(in crate::builtins::table) fn dictionary_lookup(
    object: &ObjectInstance,
    payload: &Value,
) -> BuiltinResult<Value> {
    let query_keys = selector_values(payload)?;
    let keys = dictionary_cells(object, "Keys")?;
    let values = dictionary_cells(object, "Values")?;
    let mut out = Vec::with_capacity(query_keys.len());
    for query in &query_keys {
        let idx = keys
            .data
            .iter()
            .position(|key| dictionary_keys_equal(key, query))
            .ok_or_else(|| invalid_index(format!("dictionary: key {query:?} not found")))?;
        out.push(
            values
                .data
                .get(idx)
                .cloned()
                .ok_or_else(|| invalid_index("dictionary: value index out of bounds"))?,
        );
    }
    if out.len() == 1 {
        Ok(out.remove(0))
    } else {
        let len = out.len();
        CellArray::new(out, 1, len)
            .map(Value::Cell)
            .map_err(invalid_variable)
    }
}

pub(in crate::builtins::table) fn dictionary_assign(
    mut object: ObjectInstance,
    payload: &Value,
    rhs: Value,
) -> BuiltinResult<Value> {
    let query_keys = selector_values(payload)?;
    if query_keys.len() != 1 {
        return Err(invalid_index(
            "dictionary: assignment currently expects one key",
        ));
    }
    let mut keys = dictionary_cells(&object, "Keys")?.clone();
    let mut values = dictionary_cells(&object, "Values")?.clone();
    if let Some(idx) = keys
        .data
        .iter()
        .position(|key| dictionary_keys_equal(key, &query_keys[0]))
    {
        values.data[idx] = rhs;
    } else {
        keys.data.push(query_keys[0].clone());
        values.data.push(rhs);
        keys.cols = keys.data.len();
        keys.rows = usize::from(!keys.data.is_empty());
        values.cols = values.data.len();
        values.rows = usize::from(!values.data.is_empty());
    }
    object
        .properties
        .insert("Keys".to_string(), Value::Cell(keys));
    object
        .properties
        .insert("Values".to_string(), Value::Cell(values));
    Ok(Value::Object(object))
}

pub(in crate::builtins::table) fn dictionary_keys_equal(left: &Value, right: &Value) -> bool {
    match (left, right) {
        (Value::String(a), Value::String(b)) => a == b,
        (Value::CharArray(a), Value::CharArray(b)) => a.data == b.data,
        (Value::String(a), Value::CharArray(b)) | (Value::CharArray(b), Value::String(a))
            if b.rows == 1 =>
        {
            b.data.iter().collect::<String>() == *a
        }
        (Value::Num(a), Value::Num(b)) => a == b,
        (Value::Int(a), Value::Int(b)) => a.to_i64() == b.to_i64(),
        (Value::Bool(a), Value::Bool(b)) => a == b,
        _ => left == right,
    }
}

pub(in crate::builtins::table) fn value_elements(value: &Value) -> BuiltinResult<Vec<Value>> {
    match value {
        Value::Cell(cell) => Ok(cell.data.clone()),
        Value::StringArray(array) => Ok(array.data.iter().cloned().map(Value::String).collect()),
        Value::Tensor(tensor) => Ok(tensor.data.iter().copied().map(Value::Num).collect()),
        Value::LogicalArray(array) => Ok(array
            .data
            .iter()
            .map(|flag| Value::Bool(*flag != 0))
            .collect()),
        Value::CharArray(array) => Ok(char_rows(array).into_iter().map(Value::String).collect()),
        other => Ok(vec![other.clone()]),
    }
}