use crate::parsing::source::Source;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub struct ExprId(u32);
impl ExprId {
#[must_use]
pub fn index(self) -> usize {
self.0 as usize
}
#[must_use]
pub fn from_index(index: usize) -> Self {
Self(u32::try_from(index).expect("BUG: expression table exceeds u32::MAX nodes"))
}
}
pub trait ExprKind: Clone + PartialEq {
fn for_each_child(&self, f: impl FnMut(ExprId));
fn children_mut(&mut self) -> Vec<&mut ExprId>;
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ExprNode<K> {
pub kind: K,
pub source_location: Option<Source>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ExprTable<K> {
nodes: Vec<ExprNode<K>>,
}
impl<K> Default for ExprTable<K> {
fn default() -> Self {
Self { nodes: Vec::new() }
}
}
impl<K> ExprTable<K> {
#[must_use]
pub fn with_capacity(capacity: usize) -> Self {
Self {
nodes: Vec::with_capacity(capacity),
}
}
pub fn take_compact(&mut self) -> Self {
Self {
nodes: self.nodes.drain(..).collect(),
}
}
}
impl<K: ExprKind> ExprTable<K> {
pub fn push(&mut self, kind: K, source_location: Option<Source>) -> ExprId {
let id = ExprId::from_index(self.nodes.len());
kind.for_each_child(|child| {
assert!(
child < id,
"BUG: child ExprId {} pushed after its parent {}",
child.0,
id.0
);
});
self.nodes.push(ExprNode {
kind,
source_location,
});
id
}
#[must_use]
pub fn len(&self) -> usize {
self.nodes.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.nodes.is_empty()
}
pub fn truncate(&mut self, len: usize) {
assert!(
len <= self.nodes.len(),
"BUG: truncate to {len} beyond table length {}",
self.nodes.len()
);
self.nodes.truncate(len);
}
#[must_use]
pub fn node(&self, id: ExprId) -> &ExprNode<K> {
self.nodes.get(id.index()).unwrap_or_else(|| {
panic!(
"BUG: ExprId {} out of range (len {})",
id.0,
self.nodes.len()
)
})
}
#[must_use]
pub fn kind(&self, id: ExprId) -> &K {
&self.node(id).kind
}
#[must_use]
pub fn source(&self, id: ExprId) -> Option<&Source> {
self.node(id).source_location.as_ref()
}
pub fn ids(&self) -> impl Iterator<Item = ExprId> {
(0..self.nodes.len()).map(ExprId::from_index)
}
pub fn nodes(&self) -> impl Iterator<Item = &ExprNode<K>> {
self.nodes.iter()
}
pub fn kinds_mut(&mut self) -> impl Iterator<Item = &mut K> {
self.nodes.iter_mut().map(|node| &mut node.kind)
}
pub fn kind_mut(&mut self, id: ExprId) -> &mut K {
let len = self.nodes.len();
&mut self
.nodes
.get_mut(id.index())
.unwrap_or_else(|| panic!("BUG: ExprId {} out of range (len {len})", id.0))
.kind
}
#[must_use]
pub fn structurally_equal(&self, left: ExprId, other: &Self, right: ExprId) -> bool {
let mut lefts = vec![left];
let mut rights = vec![right];
while let (Some(a), Some(b)) = (lefts.pop(), rights.pop()) {
let kind_a = self.kind(a);
let kind_b = other.kind(b);
if shape(kind_a) != shape(kind_b) {
return false;
}
kind_a.for_each_child(|child| lefts.push(child));
kind_b.for_each_child(|child| rights.push(child));
if lefts.len() != rights.len() {
return false;
}
}
true
}
}
fn shape<K: ExprKind>(kind: &K) -> K {
let mut shaped = kind.clone();
for child in shaped.children_mut() {
*child = ExprId(0);
}
shaped
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExprTree<K> {
pub table: ExprTable<K>,
pub root: ExprId,
}
impl<K: ExprKind> ExprTree<K> {
#[must_use]
pub fn new(table: ExprTable<K>, root: ExprId) -> Self {
assert_eq!(
root.index() + 1,
table.len(),
"BUG: expression tree root must be the last node of its table"
);
Self { table, root }
}
#[must_use]
pub fn leaf(kind: K, source_location: Option<Source>) -> Self {
let mut table = ExprTable::with_capacity(1);
let root = table.push(kind, source_location);
Self::new(table, root)
}
#[must_use]
pub fn root_kind(&self) -> &K {
self.table.kind(self.root)
}
#[must_use]
pub fn root_source(&self) -> Option<&Source> {
self.table.source(self.root)
}
#[must_use]
pub fn at(&self, id: ExprId) -> ExprRef<'_, K> {
ExprRef {
table: &self.table,
id,
}
}
#[must_use]
pub fn root(&self) -> ExprRef<'_, K> {
self.at(self.root)
}
#[cfg(test)]
pub(crate) fn assert_is_tree(&self) {
let mut parents = vec![0usize; self.table.len()];
for id in self.table.ids() {
self.table
.kind(id)
.for_each_child(|child| parents[child.index()] += 1);
}
for (index, count) in parents.iter().enumerate() {
let expected = usize::from(index != self.root.index());
assert_eq!(
*count, expected,
"node {index} has {count} parents, expected {expected}"
);
}
}
}
impl<K: ExprKind> PartialEq for ExprTree<K> {
fn eq(&self, other: &Self) -> bool {
self.table
.structurally_equal(self.root, &other.table, other.root)
}
}
impl<K: ExprKind> Eq for ExprTree<K> {}
#[derive(Debug)]
pub struct ExprRef<'a, K> {
pub table: &'a ExprTable<K>,
pub id: ExprId,
}
impl<K> Clone for ExprRef<'_, K> {
fn clone(&self) -> Self {
*self
}
}
impl<K> Copy for ExprRef<'_, K> {}
impl<'a, K: ExprKind> ExprRef<'a, K> {
#[must_use]
pub fn kind(self) -> &'a K {
self.table.kind(self.id)
}
#[must_use]
pub fn source(self) -> Option<&'a Source> {
self.table.source(self.id)
}
#[must_use]
pub fn child(self, id: ExprId) -> Self {
Self {
table: self.table,
id,
}
}
pub fn push_children(self, pending: &mut Vec<Self>) {
let start = pending.len();
self.kind()
.for_each_child(|child| pending.push(self.child(child)));
pending[start..].reverse();
}
}
#[cfg(test)]
mod tests {
use crate::parsing::source::SourceType;
use crate::ResourceLimits;
use std::path::{Path, PathBuf};
const CORPUS_DIRS: [&str; 4] = [
"documentation/examples",
"benches/specs",
"tests/fixtures",
"../cli/tests/integrations",
];
fn lemma_files(root: &Path) -> Vec<PathBuf> {
let mut files = Vec::new();
let mut pending = vec![root.to_path_buf()];
while let Some(dir) = pending.pop() {
for entry in std::fs::read_dir(&dir).expect("corpus directory must be readable") {
let path = entry.expect("corpus entry must be readable").path();
if path.is_dir() {
pending.push(path);
} else if path.extension().is_some_and(|ext| ext == "lemma") {
files.push(path);
}
}
}
files.sort();
files
}
#[test]
fn parsed_expressions_are_trees() {
let manifest = Path::new(env!("CARGO_MANIFEST_DIR"));
let mut checked = 0usize;
for dir in CORPUS_DIRS {
for path in lemma_files(&manifest.join(dir)) {
let source = std::fs::read_to_string(&path).expect("corpus file must be readable");
let specs = crate::parsing::parse(
&source,
SourceType::Volatile,
&ResourceLimits::default(),
)
.unwrap_or_else(|error| panic!("{} must parse: {error}", path.display()))
.into_flattened_specs();
for spec in &specs {
for rule in &spec.rules {
rule.expression.assert_is_tree();
for clause in &rule.unless_clauses {
clause.condition.assert_is_tree();
clause.result.assert_is_tree();
}
checked += 1;
}
}
}
}
assert!(checked > 100, "corpus too small: {checked} rules");
}
}