use crate::engine::source::Span;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub(crate) struct PpNodeId(pub(crate) u32);
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct PpNode {
pub(crate) kind: PpKind,
pub(crate) span: Span,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum PpKind {
Text,
Comment,
Ignored,
Template {
parts: Vec<Part>,
},
TplArg {
parts: Vec<Part>,
},
ExtTag {
name: Span,
attributes: Option<Span>,
body: Option<Span>,
closed: bool,
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct Part {
pub(crate) children: Vec<PpNodeId>,
pub(crate) eq: Option<usize>,
pub(crate) span: Span,
}
#[derive(Debug, Default)]
pub(crate) struct PpDom {
nodes: Vec<PpNode>,
root: Vec<PpNodeId>,
}
impl PpDom {
pub(crate) fn new() -> Self {
Self::default()
}
pub(crate) fn push(&mut self, node: PpNode) -> PpNodeId {
let id = PpNodeId(u32::try_from(self.nodes.len()).unwrap_or(u32::MAX));
self.nodes.push(node);
id
}
pub(crate) fn push_root(&mut self, id: PpNodeId) {
self.root.push(id);
}
pub(crate) fn node(&self, id: PpNodeId) -> &PpNode {
&self.nodes[id.0 as usize]
}
pub(crate) fn root(&self) -> &[PpNodeId] {
&self.root
}
#[allow(
dead_code,
reason = "consumed by the expansion-stage limits accounting"
)]
pub(crate) fn len(&self) -> usize {
self.nodes.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::engine::source::SourceId;
fn span(start: u32, end: u32) -> Span {
Span::new(SourceId(0), start, end)
}
#[test]
fn arena_stores_and_retrieves_nodes() {
let mut dom = PpDom::new();
let text = dom.push(PpNode {
kind: PpKind::Text,
span: span(0, 5),
});
let comment = dom.push(PpNode {
kind: PpKind::Comment,
span: span(5, 14),
});
dom.push_root(text);
dom.push_root(comment);
assert_eq!(dom.len(), 2);
assert_eq!(dom.root(), [text, comment]);
assert_eq!(dom.node(text).kind, PpKind::Text);
assert_eq!(dom.node(comment).span.len(), 9);
}
#[test]
fn template_parts_carry_children_and_eq_split() {
let mut dom = PpDom::new();
let name = dom.push(PpNode {
kind: PpKind::Text,
span: span(2, 6),
});
let arg_name = dom.push(PpNode {
kind: PpKind::Text,
span: span(7, 8),
});
let eq_text = dom.push(PpNode {
kind: PpKind::Text,
span: span(8, 9),
});
let arg_value = dom.push(PpNode {
kind: PpKind::Text,
span: span(9, 10),
});
let parts = vec![
Part {
children: vec![name],
eq: None,
span: span(2, 6),
},
Part {
children: vec![arg_name, eq_text, arg_value],
eq: Some(1),
span: span(7, 10),
},
];
let template = dom.push(PpNode {
kind: PpKind::Template {
parts: parts.clone(),
},
span: span(0, 12),
});
dom.push_root(template);
assert_eq!(dom.node(template).kind, PpKind::Template { parts });
assert_eq!(dom.node(template).span.len(), 12);
}
#[test]
fn ext_tag_kind_models_open_and_selfclosing_forms() {
let closed = PpKind::ExtTag {
name: span(1, 7),
attributes: Some(span(8, 20)),
body: Some(span(21, 30)),
closed: true,
};
let self_closing = PpKind::ExtTag {
name: span(1, 4),
attributes: None,
body: None,
closed: true,
};
assert_ne!(closed, self_closing);
assert_ne!(PpKind::Ignored, PpKind::Comment);
let debug = format!("{closed:?} {:?}", PpNodeId(3));
assert!(debug.contains("ExtTag"));
}
}