use tree_sitter::Language;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NodeKind {
pub name: String,
pub named: bool,
pub visible: bool,
pub supertype: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GrammarShape {
pub abi: usize,
pub parse_states: usize,
pub supertypes: Vec<u16>,
pub kinds: Vec<NodeKind>,
pub fields: Vec<String>,
}
impl GrammarShape {
#[must_use]
pub fn of(language: &Language) -> Self {
let kinds = (0..language.node_kind_count())
.map(|id| {
let id = u16::try_from(id).unwrap_or(u16::MAX);
NodeKind {
name: language.node_kind_for_id(id).unwrap_or("").to_owned(),
named: language.node_kind_is_named(id),
visible: language.node_kind_is_visible(id),
supertype: language.node_kind_is_supertype(id),
}
})
.collect();
let fields = (0..=language.field_count())
.map(|id| {
let id = u16::try_from(id).unwrap_or(u16::MAX);
language.field_name_for_id(id).unwrap_or("").to_owned()
})
.collect();
Self {
abi: language.abi_version(),
parse_states: language.parse_state_count(),
supertypes: language.supertypes().to_vec(),
kinds,
fields,
}
}
#[must_use]
pub fn digest(&self) -> [u8; 32] {
let mut hasher = blake3::Hasher::new();
hasher.update(b"lanekeep-grammar-v1");
length_prefixed(&mut hasher, &counted(self.abi).to_le_bytes());
length_prefixed(&mut hasher, &counted(self.parse_states).to_le_bytes());
length_prefixed(&mut hasher, &counted(self.supertypes.len()).to_le_bytes());
for supertype in &self.supertypes {
length_prefixed(&mut hasher, &supertype.to_le_bytes());
}
length_prefixed(&mut hasher, &counted(self.kinds.len()).to_le_bytes());
for kind in &self.kinds {
length_prefixed(&mut hasher, kind.name.as_bytes());
length_prefixed(
&mut hasher,
&[
u8::from(kind.named),
u8::from(kind.visible),
u8::from(kind.supertype),
],
);
}
length_prefixed(&mut hasher, &counted(self.fields.len()).to_le_bytes());
for field in &self.fields {
length_prefixed(&mut hasher, field.as_bytes());
}
*hasher.finalize().as_bytes()
}
}
#[must_use]
pub fn grammar_digest(language: &Language) -> [u8; 32] {
GrammarShape::of(language).digest()
}
fn counted(value: usize) -> u64 {
u64::try_from(value).unwrap_or(u64::MAX)
}
fn length_prefixed(hasher: &mut blake3::Hasher, bytes: &[u8]) {
hasher.update(&counted(bytes.len()).to_le_bytes());
hasher.update(bytes);
}
#[cfg(test)]
mod tests {
use super::*;
fn shape() -> GrammarShape {
GrammarShape {
abi: 15,
parse_states: 1442,
supertypes: vec![1, 2],
kinds: vec![NodeKind {
name: "identifier".to_owned(),
named: true,
visible: true,
supertype: false,
}],
fields: vec![String::new(), "name".to_owned()],
}
}
#[test]
fn every_field_reaches_the_digest() {
let base = shape().digest();
let mut abi = shape();
abi.abi = 14;
assert_ne!(base, abi.digest(), "abi");
let mut states = shape();
states.parse_states = 1443;
assert_ne!(base, states.digest(), "parse_states");
let mut supertypes = shape();
supertypes.supertypes = vec![1, 3];
assert_ne!(base, supertypes.digest(), "supertypes");
let mut renamed = shape();
renamed.kinds[0].name = "type_identifier".to_owned();
assert_ne!(base, renamed.digest(), "kind name");
let mut unnamed = shape();
unnamed.kinds[0].named = false;
assert_ne!(base, unnamed.digest(), "kind named flag");
let mut hidden = shape();
hidden.kinds[0].visible = false;
assert_ne!(base, hidden.digest(), "kind visible flag");
let mut supertype = shape();
supertype.kinds[0].supertype = true;
assert_ne!(base, supertype.digest(), "kind supertype flag");
let mut fields = shape();
fields.fields[1] = "value".to_owned();
assert_ne!(base, fields.digest(), "field name");
}
#[test]
fn parts_cannot_run_together() {
let mut one = shape();
one.kinds[0].name = "ab".to_owned();
one.fields = vec![String::new(), "c".to_owned()];
let mut two = shape();
two.kinds[0].name = "a".to_owned();
two.fields = vec![String::new(), "bc".to_owned()];
assert_ne!(one.digest(), two.digest());
}
#[test]
fn the_same_shape_gives_the_same_digest() {
assert_eq!(shape().digest(), shape().digest());
}
}