use alloc::vec::Vec;
use core::fmt;
use core::str::SplitWhitespace;
use vize_l0::dump::value::DumpValue;
use vize_l0::dump::{Dump, Error as DumpError};
use vize_l0::{Span, String, cstr};
use crate::op::{EdgeKind, EffectId, OpId, OpKind, Program, RegionId};
#[doc(alias = "ImpetoFolio")]
#[derive(Debug, Clone, Default, PartialEq, Eq, Dump)]
#[dump(name = "l3-dump-v2")]
pub struct Page {
pub phase: String,
pub regions: Vec<Region>,
pub ops: Vec<Op>,
pub edges: Vec<Edge>,
pub effects: Vec<Effect>,
}
impl Page {
#[must_use]
pub fn of(program: &Program<'_>) -> Self {
Self {
phase: String::from(program.phase.as_str()),
regions: program.regions.iter().map(Region::from).collect(),
ops: program.ops.iter().map(Op::from).collect(),
edges: program.edges.iter().map(Edge::from).collect(),
effects: program.effects.iter().map(Effect::from).collect(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Region {
pub id: u32,
pub parent: Option<u32>,
pub owner: Option<u32>,
pub span: Span,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Op {
pub id: u32,
pub kind: OpKind,
pub region: u32,
pub effect: Option<u32>,
pub span: Span,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Edge {
pub from: u32,
pub to: u32,
pub kind: EdgeKind,
pub effect: Option<u32>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Effect {
pub id: u32,
pub owner: u32,
pub region: u32,
pub span: Span,
}
impl From<&crate::op::Region> for Region {
fn from(region: &crate::op::Region) -> Self {
Self {
id: region.id.index(),
parent: region.parent.map(RegionId::index),
owner: region.owner.map(OpId::index),
span: region.span,
}
}
}
impl From<&crate::op::Op> for Op {
fn from(op: &crate::op::Op) -> Self {
Self {
id: op.id.index(),
kind: op.kind,
region: op.region.index(),
effect: op.effect.map(EffectId::index),
span: op.span,
}
}
}
impl From<&crate::op::StateEdge> for Edge {
fn from(edge: &crate::op::StateEdge) -> Self {
Self {
from: edge.from.index(),
to: edge.to.index(),
kind: edge.kind,
effect: edge.effect.map(EffectId::index),
}
}
}
impl From<&crate::op::EffectScope> for Effect {
fn from(effect: &crate::op::EffectScope) -> Self {
Self {
id: effect.id.index(),
owner: effect.owner.index(),
region: effect.region.index(),
span: effect.span,
}
}
}
impl DumpValue for Region {
fn print_value<W: fmt::Write>(&self, w: &mut W) -> fmt::Result {
write!(
w,
"id={} parent={} owner={} span={}:{}",
self.id,
format_option(self.parent),
format_option(self.owner),
self.span.start,
self.span.end
)
}
fn parse_value(text: &str, line: usize) -> Result<Self, DumpError> {
let mut fields = text.split_whitespace();
let id = parse_u32(next(&mut fields, "id", line)?, "id", line)?;
let parent = parse_option(next(&mut fields, "parent", line)?, "parent", line)?;
let owner = parse_option(next(&mut fields, "owner", line)?, "owner", line)?;
let span = parse_span(next(&mut fields, "span", line)?, line)?;
expect_end(fields, line)?;
Ok(Self {
id,
parent,
owner,
span,
})
}
}
impl DumpValue for Op {
fn print_value<W: fmt::Write>(&self, w: &mut W) -> fmt::Result {
write!(
w,
"id={} kind={} region={} effect={} span={}:{}",
self.id,
self.kind.mnemonic(),
self.region,
format_option(self.effect),
self.span.start,
self.span.end
)
}
fn parse_value(text: &str, line: usize) -> Result<Self, DumpError> {
let mut fields = text.split_whitespace();
let id = parse_u32(next(&mut fields, "id", line)?, "id", line)?;
let kind = parse_op_kind(next(&mut fields, "kind", line)?, line)?;
let region = parse_u32(next(&mut fields, "region", line)?, "region", line)?;
let effect = parse_option(next(&mut fields, "effect", line)?, "effect", line)?;
let span = parse_span(next(&mut fields, "span", line)?, line)?;
expect_end(fields, line)?;
Ok(Self {
id,
kind,
region,
effect,
span,
})
}
}
impl DumpValue for Edge {
fn print_value<W: fmt::Write>(&self, w: &mut W) -> fmt::Result {
write!(
w,
"from={} to={} kind={} effect={}",
self.from,
self.to,
self.kind.as_str(),
format_option(self.effect)
)
}
fn parse_value(text: &str, line: usize) -> Result<Self, DumpError> {
let mut fields = text.split_whitespace();
let from = parse_u32(next(&mut fields, "from", line)?, "from", line)?;
let to = parse_u32(next(&mut fields, "to", line)?, "to", line)?;
let kind = parse_edge_kind(next(&mut fields, "kind", line)?, line)?;
let effect = parse_option(next(&mut fields, "effect", line)?, "effect", line)?;
expect_end(fields, line)?;
Ok(Self {
from,
to,
kind,
effect,
})
}
}
impl DumpValue for Effect {
fn print_value<W: fmt::Write>(&self, w: &mut W) -> fmt::Result {
write!(
w,
"id={} owner={} region={} span={}:{}",
self.id, self.owner, self.region, self.span.start, self.span.end
)
}
fn parse_value(text: &str, line: usize) -> Result<Self, DumpError> {
let mut fields = text.split_whitespace();
let id = parse_u32(next(&mut fields, "id", line)?, "id", line)?;
let owner = parse_u32(next(&mut fields, "owner", line)?, "owner", line)?;
let region = parse_u32(next(&mut fields, "region", line)?, "region", line)?;
let span = parse_span(next(&mut fields, "span", line)?, line)?;
expect_end(fields, line)?;
Ok(Self {
id,
owner,
region,
span,
})
}
}
fn format_option(value: Option<u32>) -> String {
match value {
Some(value) => cstr!("{value}"),
None => String::from("-"),
}
}
fn next<'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}`")))
}
fn expect_end(mut fields: SplitWhitespace<'_>, line: usize) -> Result<(), DumpError> {
match fields.next() {
Some(extra) => Err(DumpError::new(line, cstr!("unexpected field `{extra}`"))),
None => Ok(()),
}
}
fn parse_u32(text: &str, name: &str, line: usize) -> Result<u32, DumpError> {
text.parse()
.map_err(|_| DumpError::new(line, cstr!("invalid `{name}` integer `{text}`")))
}
fn parse_option(text: &str, name: &str, line: usize) -> Result<Option<u32>, DumpError> {
if text == "-" {
return Ok(None);
}
parse_u32(text, name, line).map(Some)
}
fn parse_span(text: &str, line: usize) -> Result<Span, DumpError> {
let Some((start, end)) = text.split_once(':') else {
return Err(DumpError::new(line, cstr!("invalid span `{text}`")));
};
Ok(Span::new(
parse_u32(start, "span.start", line)?,
parse_u32(end, "span.end", line)?,
))
}
fn parse_op_kind(text: &str, line: usize) -> Result<OpKind, DumpError> {
OpKind::from_mnemonic(text)
.ok_or_else(|| DumpError::new(line, cstr!("unknown L3 op kind `{text}`")))
}
fn parse_edge_kind(text: &str, line: usize) -> Result<EdgeKind, DumpError> {
EdgeKind::from_str(text)
.ok_or_else(|| DumpError::new(line, cstr!("unknown L3 edge kind `{text}`")))
}
#[cfg(test)]
mod tests {
use crate::dump::Page;
use crate::op::Phase;
use vize_l0::dump::{Dump, Error as DumpError, Mode as DumpMode};
use vize_l0::{String, cstr};
#[test]
fn phase_spellings_are_parseable() {
assert_eq!(Phase::from_str("built"), Some(Phase::Built));
assert_eq!(Phase::from_str("partitioned"), Some(Phase::Partitioned));
assert_eq!(Phase::from_str("scheduled"), Some(Phase::Scheduled));
assert_eq!(Phase::from_str("unknown"), None);
}
#[test]
fn derived_page_rejects_an_unknown_record_kind() {
let input = "\
[l3-dump-v2]
phase=built
[l3-dump-v2.ops]
id=0 kind=l3.missing region=0 effect=- span=0:1
";
assert_eq!(
Page::parse(input).unwrap_err(),
DumpError::new(5, cstr!("unknown L3 op kind `l3.missing`"))
);
}
#[test]
fn display_mode_has_the_derived_full_text_law() {
let folio = Page {
phase: String::from("built"),
..Page::default()
};
assert_eq!(
folio.print_to_string(DumpMode::Display),
folio.print_to_string(DumpMode::Full)
);
}
}