vize_l3 0.558.0

L3 - the Davinci reactivity IR (codename Impeto): flat scheduled ops with explicit state and effect edges
Documentation
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, cstr};

use super::{
    BindingFact, BindingId, BindingOrigin, EffectSet, EscapeKind, LatticeFacts, ReactivityClass,
    Verdict,
};

/// Owned folio page for the L3 reactivity lattice.
#[derive(Debug, Clone, Default, PartialEq, Eq, Dump)]
#[dump(name = "s3-reactivity-folio")]
pub struct Page {
    /// Binding facts in binding-id order.
    pub bindings: Vec<Binding>,
}

impl Page {
    /// Mirror live arena facts into the owned folio page.
    #[must_use]
    pub fn of(facts: &LatticeFacts<'_>) -> Self {
        Self {
            bindings: facts.bindings.iter().map(Binding::from).collect(),
        }
    }
}

/// One lattice fact line.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Binding {
    pub id: u32,
    pub class: ReactivityClass,
    pub verdict: Verdict,
    pub origin: BindingOrigin,
    pub effects: EffectSet,
    pub escape: EscapeKind,
    pub span: Span,
}

impl From<&BindingFact> for Binding {
    fn from(fact: &BindingFact) -> Self {
        Self {
            id: fact.id.index(),
            class: fact.class,
            verdict: fact.verdict,
            origin: fact.origin,
            effects: fact.effects,
            escape: fact.escape,
            span: fact.span,
        }
    }
}

impl DumpValue for Binding {
    fn print_value<W: fmt::Write>(&self, w: &mut W) -> fmt::Result {
        write!(
            w,
            "id={} class={} verdict={} origin={} effects=",
            self.id,
            self.class.as_str(),
            self.verdict.as_str(),
            self.origin.as_str()
        )?;
        self.effects.print(w)?;
        write!(
            w,
            " escape={} span={}:{}",
            self.escape.as_str(),
            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 class = parse_class(next(&mut fields, "class", line)?, line)?;
        let verdict = parse_verdict(next(&mut fields, "verdict", line)?, line)?;
        let origin = parse_origin(next(&mut fields, "origin", line)?, line)?;
        let effects = parse_effects(next(&mut fields, "effects", line)?, line)?;
        let escape = parse_escape(next(&mut fields, "escape", line)?, line)?;
        let span = parse_span(next(&mut fields, "span", line)?, line)?;
        expect_end(fields, line)?;
        Ok(Self {
            id,
            class,
            verdict,
            origin,
            effects,
            escape,
            span,
        })
    }
}

impl From<Binding> for BindingFact {
    fn from(binding: Binding) -> Self {
        Self {
            id: BindingId::new(binding.id),
            class: binding.class,
            verdict: binding.verdict,
            origin: binding.origin,
            effects: binding.effects,
            escape: binding.escape,
            span: binding.span,
        }
    }
}

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 parse_u32(raw: &str, name: &str, line: usize) -> Result<u32, DumpError> {
    raw.parse()
        .map_err(|_| DumpError::new(line, cstr!("invalid `{name}` value `{raw}`")))
}

fn parse_class(raw: &str, line: usize) -> Result<ReactivityClass, DumpError> {
    ReactivityClass::from_str(raw)
        .ok_or_else(|| DumpError::new(line, cstr!("unknown reactivity class `{raw}`")))
}

fn parse_verdict(raw: &str, line: usize) -> Result<Verdict, DumpError> {
    Verdict::from_str(raw).ok_or_else(|| DumpError::new(line, cstr!("unknown verdict `{raw}`")))
}

fn parse_origin(raw: &str, line: usize) -> Result<BindingOrigin, DumpError> {
    BindingOrigin::from_str(raw)
        .ok_or_else(|| DumpError::new(line, cstr!("unknown binding origin `{raw}`")))
}

fn parse_effects(raw: &str, line: usize) -> Result<EffectSet, DumpError> {
    EffectSet::parse(raw).ok_or_else(|| DumpError::new(line, cstr!("unknown effect set `{raw}`")))
}

fn parse_escape(raw: &str, line: usize) -> Result<EscapeKind, DumpError> {
    EscapeKind::from_str(raw)
        .ok_or_else(|| DumpError::new(line, cstr!("unknown escape kind `{raw}`")))
}

fn parse_span(raw: &str, line: usize) -> Result<Span, DumpError> {
    let Some((start, end)) = raw.split_once(':') else {
        return Err(DumpError::new(line, cstr!("invalid span `{raw}`")));
    };
    Ok(Span::new(
        parse_u32(start, "span start", line)?,
        parse_u32(end, "span end", line)?,
    ))
}

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(()),
    }
}