use core::fmt;
use core::marker::PhantomData;
use vize_l0::{String, cstr};
use super::{FactGroup, FactTable};
use crate::dump::{Dump, Error as DumpError, Mode as DumpMode};
use crate::pass::AnalysisId;
pub trait AlphaExport: FactGroup {
const ALPHA_SCHEMA: u16;
type Alpha: Dump + PartialEq + Send + Sync + 'static;
fn export(table: &FactTable<Self>) -> Self::Alpha;
fn import(alpha: Self::Alpha) -> FactTable<Self>;
const ALPHA_DESC: AlphaDesc = AlphaDesc {
id: Self::ID,
name: Self::NAME,
schema: Self::ALPHA_SCHEMA,
};
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct AlphaDesc {
pub id: AnalysisId,
pub name: &'static str,
pub schema: u16,
}
pub const ALPHA_GROUPS: &[AlphaDesc] = &[<super::ExpressionFacts as AlphaExport>::ALPHA_DESC];
pub struct AlphaDocument<G: AlphaExport> {
pub alpha: G::Alpha,
group: PhantomData<G>,
}
impl<G: AlphaExport> PartialEq for AlphaDocument<G> {
fn eq(&self, other: &Self) -> bool {
self.alpha == other.alpha
}
}
impl<G: AlphaExport> fmt::Debug for AlphaDocument<G>
where
G::Alpha: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("AlphaDocument")
.field("group", &G::NAME)
.field("schema_version", &G::ALPHA_SCHEMA)
.field("alpha", &self.alpha)
.finish()
}
}
impl<G: AlphaExport> AlphaDocument<G> {
#[must_use]
pub fn export(table: &FactTable<G>) -> Self {
Self {
alpha: G::export(table),
group: PhantomData,
}
}
#[must_use]
pub fn import(self) -> FactTable<G> {
G::import(self.alpha)
}
}
const HEADER_LINES: usize = 4;
impl<G: AlphaExport> Dump for AlphaDocument<G> {
fn print<W: fmt::Write>(&self, w: &mut W, mode: DumpMode) -> fmt::Result {
writeln!(w, "[fact-alpha]")?;
writeln!(w, "group={}", G::NAME)?;
writeln!(w, "schema_version={}", G::ALPHA_SCHEMA)?;
writeln!(w)?;
self.alpha.print(w, mode)
}
fn parse(input: &str) -> Result<Self, DumpError> {
let mut lines = input.splitn(HEADER_LINES + 1, '\n');
let mut expect = |line: usize, expected: &str| match lines.next() {
Some(found) if found == expected => Ok(()),
Some(found) => Err(DumpError::new(
line,
cstr!("expected `{expected}`, found `{found}`"),
)),
None => Err(DumpError::new(0, String::from("truncated α header"))),
};
expect(1, "[fact-alpha]")?;
expect(2, cstr!("group={}", G::NAME).as_str())?;
expect(3, cstr!("schema_version={}", G::ALPHA_SCHEMA).as_str())?;
expect(4, "")?;
let body = lines.next().unwrap_or_default();
let alpha = G::Alpha::parse(body).map_err(|error| {
let line = if error.line == 0 {
0
} else {
error.line + HEADER_LINES
};
DumpError::new(line, error.message)
})?;
Ok(Self {
alpha,
group: PhantomData,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AlphaDocError {
Undocumented { group: &'static str },
Mismatch {
group: &'static str,
documented: String,
},
}
pub fn check_alpha_schema_doc(doc: &str, groups: &[AlphaDesc]) -> Result<(), AlphaDocError> {
for group in groups {
let quoted = cstr!("`{}`", group.name);
let row = doc.lines().find_map(|line| {
let mut cells = line.split('|').map(str::trim).skip(1);
(cells.next() == Some(quoted.as_str())).then(|| (cells.next(), cells.next()))
});
let Some((id, schema)) = row else {
return Err(AlphaDocError::Undocumented { group: group.name });
};
let expected = (cstr!("{}", group.id.index()), cstr!("{}", group.schema));
if (id, schema) != (Some(expected.0.as_str()), Some(expected.1.as_str())) {
return Err(AlphaDocError::Mismatch {
group: group.name,
documented: cstr!("id={} schema={}", id.unwrap_or(""), schema.unwrap_or("")),
});
}
}
Ok(())
}