use alloc::vec::Vec;
use core::fmt;
use core::str::SplitWhitespace;
use vize_l0::cstr;
use vize_l0::dump::value::DumpValue;
use vize_l0::dump::{Dump, Error as DumpError};
use super::{Placement, PlacementRecord, PlacementSet};
use crate::op::{OpId, Program};
#[derive(Debug, Clone, Default, PartialEq, Eq, Dump)]
#[dump(name = "s3-placement-folio")]
pub struct Page {
pub placements: Vec<Record>,
}
impl Page {
#[must_use]
pub fn of(program: &Program<'_>) -> Self {
Self {
placements: program
.placements
.iter()
.map(|record| Record(*record))
.collect(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Record(pub PlacementRecord);
impl DumpValue for Record {
fn print_value<W: fmt::Write>(&self, w: &mut W) -> fmt::Result {
let record = self.0;
write!(
w,
"op={} alternatives={} leader=",
record.op.index(),
record.alternatives
)?;
match record.leader {
Some(leader) => write!(w, "{}", leader.index())?,
None => w.write_str("-")?,
}
write!(w, " chosen={}", record.chosen)
}
fn parse_value(text: &str, line: usize) -> Result<Self, DumpError> {
let mut fields = text.split_whitespace();
let op = parse_u32(field(&mut fields, "op", line)?, "op", line)?;
let alternatives = parse_set(field(&mut fields, "alternatives", line)?, line)?;
let leader = match field(&mut fields, "leader", line)? {
"-" => None,
value => Some(OpId::new(parse_u32(value, "leader", line)?)),
};
let chosen = parse_placement(field(&mut fields, "chosen", line)?, line)?;
if let Some(extra) = fields.next() {
return Err(DumpError::new(line, cstr!("unexpected field `{extra}`")));
}
Ok(Self(PlacementRecord {
op: OpId::new(op),
alternatives,
leader,
chosen,
}))
}
}
pub(crate) fn field<'a>(
fields: &mut SplitWhitespace<'a>,
name: &str,
line: usize,
) -> Result<&'a str, DumpError> {
let Some(raw) = fields.next() else {
return Err(DumpError::new(line, cstr!("missing `{name}` field")));
};
raw.strip_prefix(name)
.and_then(|rest| rest.strip_prefix('='))
.ok_or_else(|| DumpError::new(line, cstr!("expected `{name}=...`, got `{raw}`")))
}
pub(crate) fn parse_u32(text: &str, name: &str, line: usize) -> Result<u32, DumpError> {
text.parse()
.map_err(|_| DumpError::new(line, cstr!("invalid `{name}` integer `{text}`")))
}
pub(crate) fn parse_placement(text: &str, line: usize) -> Result<Placement, DumpError> {
Placement::from_str(text)
.ok_or_else(|| DumpError::new(line, cstr!("unknown placement `{text}`")))
}
fn parse_set(text: &str, line: usize) -> Result<PlacementSet, DumpError> {
let mut set = PlacementSet::EMPTY;
if text == "-" {
return Ok(set);
}
let mut previous: Option<Placement> = None;
for name in text.split(',') {
let placement = parse_placement(name, line)?;
if previous.is_some_and(|previous| previous >= placement) {
return Err(DumpError::new(
line,
cstr!("placement alternatives `{text}` are not in canonical order"),
));
}
previous = Some(placement);
set = set.with(placement);
}
Ok(set)
}