mod dump;
mod facet;
mod global;
mod usage;
pub use facet::schema;
pub use facet::{Facet, SummaryError};
pub use global::{
GlobalEntry, GlobalError, GlobalFacet, GlobalFacts, GlobalResolution, GlobalSummary,
};
pub use usage::Usage;
use alloc::vec::Vec;
use core::fmt;
use vize_l0::hash::StableHasher128;
use vize_l0::{String, cstr};
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct Fingerprint([u8; 16]);
impl Fingerprint {
#[must_use]
pub const fn bytes(self) -> [u8; 16] {
self.0
}
}
impl fmt::Debug for Fingerprint {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Fingerprint(")?;
for byte in self.0 {
write!(f, "{byte:02x}")?;
}
write!(f, ")")
}
}
const DOMAIN: &[u8] = b"vize.sfc-summary.v2\0";
fn fingerprint(facet: Facet, name: &str, contract: &str) -> Fingerprint {
digest(DOMAIN, facet.group(), facet.schema(), name, contract)
}
pub(super) fn digest(
domain: &[u8],
group: &str,
schema: u16,
name: &str,
contract: &str,
) -> Fingerprint {
let mut hasher = StableHasher128::new();
hasher.update(domain);
feed_str(&mut hasher, group);
hasher.update(&schema.to_le_bytes());
feed_str(&mut hasher, name);
feed_str(&mut hasher, contract);
Fingerprint(hasher.digest())
}
fn feed_str(hasher: &mut StableHasher128, value: &str) {
hasher.update(&(value.len() as u64).to_le_bytes());
hasher.update(value.as_bytes());
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DeclarationId {
facet: Facet,
name: String,
}
impl DeclarationId {
#[must_use]
pub fn new(facet: Facet, name: impl Into<String>) -> Self {
Self {
facet,
name: name.into(),
}
}
#[must_use]
pub const fn facet(&self) -> Facet {
self.facet
}
#[must_use]
pub fn name(&self) -> &str {
&self.name
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Signature {
pub name: String,
pub params: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AlphaEntry {
pub name: String,
pub contract: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AlphaPages {
pub signature: Signature,
pub props: Vec<AlphaEntry>,
pub emits: Vec<AlphaEntry>,
pub slots: Vec<AlphaEntry>,
pub reactivity: Vec<AlphaEntry>,
pub components: Vec<AlphaEntry>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct Declaration {
facet: Facet,
name: String,
contract: String,
}
impl Declaration {
fn id(&self) -> DeclarationId {
DeclarationId::new(self.facet, self.name.clone())
}
fn fingerprint(&self) -> Fingerprint {
fingerprint(self.facet, &self.name, &self.contract)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SfcSummary {
declarations: Vec<Declaration>,
}
impl SfcSummary {
pub fn from_alpha(pages: AlphaPages) -> Result<Self, SummaryError> {
let mut declarations = Vec::new();
insert(
&mut declarations,
Facet::Signature,
pages.signature.name,
pages.signature.params,
)?;
insert_all(&mut declarations, Facet::Prop, pages.props)?;
insert_all(&mut declarations, Facet::Emit, pages.emits)?;
insert_all(&mut declarations, Facet::Slot, pages.slots)?;
insert_all(&mut declarations, Facet::Reactivity, pages.reactivity)?;
insert_all(&mut declarations, Facet::Component, pages.components)?;
Ok(finish(declarations))
}
#[must_use]
pub fn len(&self) -> usize {
self.declarations.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.declarations.is_empty()
}
pub fn iter(&self) -> impl ExactSizeIterator<Item = (Facet, &str, &str)> + '_ {
self.declarations
.iter()
.map(|decl| (decl.facet, decl.name.as_str(), decl.contract.as_str()))
}
#[must_use]
pub fn fingerprint(&self, facet: Facet, name: &str) -> Option<Fingerprint> {
let index = self
.declarations
.binary_search_by(|decl| (decl.facet, decl.name.as_str()).cmp(&(facet, name)))
.ok()?;
self.declarations.get(index).map(|decl| decl.fingerprint())
}
#[must_use]
pub fn changed(&self, next: &Self) -> Vec<DeclarationId> {
let mut changed = Vec::new();
let mut left = self.declarations.iter();
let mut right = next.declarations.iter();
let mut lhs = left.next();
let mut rhs = right.next();
loop {
match (lhs, rhs) {
(None, None) => break,
(Some(only), None) => {
changed.push(only.id());
lhs = left.next();
}
(None, Some(only)) => {
changed.push(only.id());
rhs = right.next();
}
(Some(prev), Some(next_decl)) => {
match (prev.facet, prev.name.as_str())
.cmp(&(next_decl.facet, next_decl.name.as_str()))
{
core::cmp::Ordering::Less => {
changed.push(prev.id());
lhs = left.next();
}
core::cmp::Ordering::Greater => {
changed.push(next_decl.id());
rhs = right.next();
}
core::cmp::Ordering::Equal => {
if prev.fingerprint() != next_decl.fingerprint() {
changed.push(prev.id());
}
lhs = left.next();
rhs = right.next();
}
}
}
}
}
changed
}
}
fn insert_all(
out: &mut Vec<Declaration>,
facet: Facet,
entries: Vec<AlphaEntry>,
) -> Result<(), SummaryError> {
for entry in entries {
insert(out, facet, entry.name, entry.contract)?;
}
Ok(())
}
fn insert(
out: &mut Vec<Declaration>,
facet: Facet,
name: String,
contract: String,
) -> Result<(), SummaryError> {
if name.is_empty() || name.chars().any(|ch| ch.is_whitespace() || ch == '=') {
return Err(SummaryError::BadName { facet, name });
}
if contract.chars().any(|ch| ch == '\n' || ch == '\r') {
return Err(SummaryError::BadContract { facet, name });
}
if out
.iter()
.any(|decl| decl.facet == facet && decl.name == name)
{
return Err(SummaryError::Duplicate { facet, name });
}
out.push(Declaration {
facet,
name,
contract,
});
Ok(())
}
fn finish(mut declarations: Vec<Declaration>) -> SfcSummary {
declarations.sort_unstable_by(|lhs, rhs| {
(lhs.facet, lhs.name.as_str()).cmp(&(rhs.facet, rhs.name.as_str()))
});
debug_assert_eq!(
declarations
.iter()
.filter(|decl| decl.facet == Facet::Signature)
.count(),
1
);
SfcSummary { declarations }
}
fn folio_error(err: SummaryError, line: usize) -> vize_l0::dump::Error {
let message = match err {
SummaryError::BadName { name, .. } => cstr!("invalid declaration name `{name}`"),
SummaryError::BadContract { name, .. } => cstr!("invalid contract of `{name}`"),
SummaryError::Duplicate { name, .. } => cstr!("duplicate declaration `{name}`"),
SummaryError::NotInterface { group } => cstr!("not an interface group `{group}`"),
SummaryError::Schema {
group,
expected,
found,
} => cstr!("schema of `{group}` is {found}, expected {expected}"),
SummaryError::Unknown { name, .. } => cstr!("unknown declaration `{name}`"),
SummaryError::EmptyConsumer => cstr!("empty consumer"),
SummaryError::DuplicateUse { name, .. } => cstr!("duplicate use `{name}`"),
};
vize_l0::dump::Error::new(line, message)
}