use serde::{Deserialize, Serialize};
use thiserror::Error;
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum BoundaryEnd {
First,
Last,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct BoundaryPublic {
pub column: usize,
pub end: BoundaryEnd,
pub public_value: usize,
}
impl BoundaryPublic {
pub const fn new(column: usize, end: BoundaryEnd, public_value: usize) -> Self {
Self {
column,
end,
public_value,
}
}
#[must_use]
pub const fn row(&self, height: usize) -> usize {
assert!(height > 0, "a boundary cell needs at least one trace row");
match self.end {
BoundaryEnd::First => 0,
BoundaryEnd::Last => height - 1,
}
}
}
#[derive(Copy, Clone, Debug, Error, PartialEq, Eq)]
pub enum BoundaryIoError {
#[error("boundary-IO column {column} is out of range for main width {width}")]
ColumnOutOfRange {
column: usize,
width: usize,
},
#[error(
"boundary-IO public value {index} is out of range for {num_public_values} public values"
)]
PublicValueOutOfRange {
index: usize,
num_public_values: usize,
},
#[error("boundary-IO declares column {column} twice on the {end:?} row")]
DuplicateCell {
column: usize,
end: BoundaryEnd,
},
}
pub fn validate(
cells: &[BoundaryPublic],
width: usize,
num_public_values: usize,
) -> Result<(), BoundaryIoError> {
for (index, cell) in cells.iter().enumerate() {
if cell.column >= width {
return Err(BoundaryIoError::ColumnOutOfRange {
column: cell.column,
width,
});
}
if cell.public_value >= num_public_values {
return Err(BoundaryIoError::PublicValueOutOfRange {
index: cell.public_value,
num_public_values,
});
}
if cells[..index]
.iter()
.any(|earlier| earlier.column == cell.column && earlier.end == cell.end)
{
return Err(BoundaryIoError::DuplicateCell {
column: cell.column,
end: cell.end,
});
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn row_reads_each_end_of_the_trace() {
let first = BoundaryPublic::new(0, BoundaryEnd::First, 0);
let last = BoundaryPublic::new(0, BoundaryEnd::Last, 0);
assert_eq!(first.row(8), 0);
assert_eq!(last.row(8), 7);
assert_eq!(first.row(1), 0);
assert_eq!(last.row(1), 0);
}
#[test]
fn validate_accepts_distinct_in_range_cells() {
let cells = [
BoundaryPublic::new(1, BoundaryEnd::First, 0),
BoundaryPublic::new(1, BoundaryEnd::Last, 1),
];
assert_eq!(validate(&cells, 2, 2), Ok(()));
}
#[test]
fn validate_accepts_an_empty_declaration() {
assert_eq!(validate(&[], 0, 0), Ok(()));
}
#[test]
fn validate_rejects_column_past_the_main_width() {
let cells = [BoundaryPublic::new(2, BoundaryEnd::First, 0)];
assert_eq!(
validate(&cells, 2, 1),
Err(BoundaryIoError::ColumnOutOfRange {
column: 2,
width: 2
})
);
}
#[test]
fn validate_rejects_public_value_past_the_declared_count() {
let cells = [BoundaryPublic::new(0, BoundaryEnd::First, 3)];
assert_eq!(
validate(&cells, 2, 1),
Err(BoundaryIoError::PublicValueOutOfRange {
index: 3,
num_public_values: 1
})
);
}
#[test]
fn validate_rejects_two_cells_on_one_trace_cell() {
let cells = [
BoundaryPublic::new(0, BoundaryEnd::First, 0),
BoundaryPublic::new(0, BoundaryEnd::First, 1),
];
assert_eq!(
validate(&cells, 2, 2),
Err(BoundaryIoError::DuplicateCell {
column: 0,
end: BoundaryEnd::First
})
);
}
}