use crate::error::{err_number, VBError, VBResult};
use crate::types::VBType;
use crate::value::VBVariant;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ArrayDimension {
pub lower: i32,
pub upper: i32,
}
impl ArrayDimension {
pub fn new(lower: i32, upper: i32) -> Self {
Self { lower, upper }
}
pub fn len(&self) -> usize {
(self.upper as i64 - self.lower as i64 + 1).max(0) as usize
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct ArrayValue {
element_type: VBType,
dimensions: Vec<ArrayDimension>,
data: Vec<VBVariant>,
}
impl ArrayValue {
pub fn new_fixed(element_type: VBType, dimensions: &[ArrayDimension]) -> VBResult<Self> {
let mut size: usize = 1;
for dimension in dimensions {
size = size
.checked_mul(dimension.len())
.ok_or_else(VBError::out_of_memory)?;
}
let default = VBVariant::default_for_type(&element_type);
Ok(Self {
element_type,
dimensions: dimensions.to_vec(),
data: vec![default; size],
})
}
pub fn new_dynamic(element_type: VBType) -> Self {
Self {
element_type,
dimensions: Vec::new(),
data: Vec::new(),
}
}
pub fn from_vec(element_type: VBType, data: Vec<VBVariant>) -> Self {
let len = data.len() as i32;
Self {
element_type,
dimensions: vec![ArrayDimension::new(1, len)],
data,
}
}
pub fn from_vec_with_bounds(element_type: VBType, data: Vec<VBVariant>, lower: i32) -> Self {
let upper = if data.is_empty() {
lower - 1
} else {
lower + (data.len() as i32 - 1)
};
Self {
element_type,
dimensions: vec![ArrayDimension::new(lower, upper)],
data,
}
}
pub fn element_type(&self) -> &VBType {
&self.element_type
}
pub fn rank(&self) -> usize {
self.dimensions.len()
}
pub fn is_initialized(&self) -> bool {
!self.dimensions.is_empty()
}
pub fn dimensions(&self) -> &[ArrayDimension] {
&self.dimensions
}
pub fn len(&self) -> usize {
self.data.len()
}
pub fn is_empty(&self) -> bool {
self.data.is_empty()
}
pub fn lower_bound(&self, dimension: usize) -> VBResult<i32> {
self.dimensions
.get(dimension)
.map(|d| d.lower)
.ok_or_else(VBError::subscript_out_of_range)
}
pub fn upper_bound(&self, dimension: usize) -> VBResult<i32> {
self.dimensions
.get(dimension)
.map(|d| d.upper)
.ok_or_else(VBError::subscript_out_of_range)
}
pub fn get(&self, indices: &[i32]) -> VBResult<&VBVariant> {
let offset = self.offset(indices)?;
self.data
.get(offset)
.ok_or_else(VBError::subscript_out_of_range)
}
pub fn get_mut(&mut self, indices: &[i32]) -> VBResult<&mut VBVariant> {
let offset = self.offset(indices)?;
self.data
.get_mut(offset)
.ok_or_else(VBError::subscript_out_of_range)
}
pub fn set(&mut self, indices: &[i32], value: VBVariant) -> VBResult<()> {
let offset = self.offset(indices)?;
self.data[offset] = value;
Ok(())
}
pub fn as_slice(&self) -> &[VBVariant] {
&self.data
}
fn offset(&self, indices: &[i32]) -> VBResult<usize> {
if !self.is_initialized() {
return Err(VBError::new(err_number::SUBSCRIPT_OUT_OF_RANGE));
}
if indices.len() != self.dimensions.len() {
return Err(VBError::new(err_number::WRONG_NUMBER_OF_ARGUMENTS));
}
let mut offset: usize = 0;
for (i, &index) in indices.iter().enumerate() {
let dimension = &self.dimensions[i];
if index < dimension.lower || index > dimension.upper {
return Err(VBError::subscript_out_of_range());
}
offset = offset * dimension.len() + (index - dimension.lower) as usize;
}
Ok(offset)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn dimension_length_is_inclusive() {
assert_eq!(ArrayDimension::new(1, 5).len(), 5);
assert_eq!(ArrayDimension::new(0, 0).len(), 1);
assert_eq!(ArrayDimension::new(5, 1).len(), 0);
}
#[test]
fn fixed_array_uses_default_values() {
let arr = ArrayValue::new_fixed(VBType::Integer, &[ArrayDimension::new(1, 3)]).unwrap();
assert_eq!(arr.len(), 3);
assert_eq!(arr.get(&[2]).unwrap(), &VBVariant::Integer(0));
assert_eq!(arr.lower_bound(0).unwrap(), 1);
assert_eq!(arr.upper_bound(0).unwrap(), 3);
}
#[test]
fn arbitrary_bounds_are_supported() {
let mut arr = ArrayValue::new_fixed(VBType::String, &[ArrayDimension::new(-2, 0)]).unwrap();
assert_eq!(arr.lower_bound(0).unwrap(), -2);
assert_eq!(arr.upper_bound(0).unwrap(), 0);
arr.set(&[-2], VBVariant::String("a".into())).unwrap();
arr.set(&[0], VBVariant::String("c".into())).unwrap();
assert_eq!(arr.get(&[-2]).unwrap(), &VBVariant::String("a".into()));
assert_eq!(arr.get(&[0]).unwrap(), &VBVariant::String("c".into()));
}
#[test]
fn out_of_range_indexes_error() {
let arr = ArrayValue::new_fixed(VBType::Long, &[ArrayDimension::new(1, 2)]).unwrap();
assert_eq!(
arr.get(&[0]).unwrap_err().number,
err_number::SUBSCRIPT_OUT_OF_RANGE
);
assert_eq!(
arr.get(&[3]).unwrap_err().number,
err_number::SUBSCRIPT_OUT_OF_RANGE
);
}
#[test]
fn wrong_rank_errors() {
let arr = ArrayValue::new_fixed(VBType::Long, &[ArrayDimension::new(1, 2)]).unwrap();
assert_eq!(
arr.get(&[1, 2]).unwrap_err().number,
err_number::WRONG_NUMBER_OF_ARGUMENTS
);
}
#[test]
fn dynamic_array_requires_sizing() {
let arr = ArrayValue::new_dynamic(VBType::Integer);
assert!(!arr.is_initialized());
assert_eq!(
arr.get(&[1]).unwrap_err().number,
err_number::SUBSCRIPT_OUT_OF_RANGE
);
}
#[test]
fn multidimension_offset_is_row_major() {
let dims = [ArrayDimension::new(1, 2), ArrayDimension::new(1, 3)];
let mut arr = ArrayValue::new_fixed(VBType::Integer, &dims).unwrap();
for i in 1..=2 {
for j in 1..=3 {
arr.set(&[i, j], VBVariant::Long(i * 10 + j)).unwrap();
}
}
assert_eq!(arr.get(&[2, 3]).unwrap(), &VBVariant::Long(23));
assert_eq!(arr.len(), 6);
}
}