use std::{fmt::Debug, num::NonZeroU32};
use super::Location;
use crate::scanner::{Comment as ScannerComment, CommentStyle, Token, TokenType};
pub(super) trait Tracker<'s> {
type NodeId: Copy + Ord + Debug;
type Metadata: Metadata<'s, NodeId = Self::NodeId>;
fn on_node_start(&mut self, loc: Location) -> Self::NodeId;
fn accept_comment(&mut self, t: &Token<'s>) -> bool {
matches!(t.ttype, TokenType::Comment(_))
}
fn on_node_end(&mut self);
fn on_node_end_restore(&mut self, node: Self::NodeId);
fn metadata(&self) -> &impl Metadata<'s, NodeId = Self::NodeId>;
fn finish(self) -> Self::Metadata;
}
pub trait Metadata<'s> {
type NodeId: Copy + Ord;
fn location(&self, node: Self::NodeId) -> Location;
fn comments(&self, node: Self::NodeId) -> (&[Comment<'s>], &[Comment<'s>]);
}
#[derive(Debug, PartialEq, Eq)]
pub struct Comment<'s> {
pub text: &'s str,
pub style: CommentStyle,
pub loc: Location,
}
#[derive(PartialEq, Eq, Copy, Clone, PartialOrd, Ord)]
pub struct Id(Location);
impl Debug for Id {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Id({}_{})", self.0.line, self.0.col)
}
}
#[derive(Debug)]
pub struct DefaultMetadata<'s>(CommentsAccum<'s>);
impl<'s> Metadata<'s> for DefaultMetadata<'s> {
type NodeId = Id;
fn location(&self, node: Self::NodeId) -> Location {
self.0.location(node)
}
fn comments(&self, node: Self::NodeId) -> (&[Comment<'s>], &[Comment<'s>]) {
self.0.comments(node)
}
}
#[derive(Default)]
pub(super) struct DefaultTracker<'s> {
last_node: Option<Id>,
last_comments: Vec<Comment<'s>>,
final_comments: CommentsAccum<'s>,
}
impl<'s> Tracker<'s> for DefaultTracker<'s> {
type NodeId = Id;
type Metadata = DefaultMetadata<'s>;
fn accept_comment(&mut self, t: &Token<'s>) -> bool {
match t.ttype {
TokenType::Comment(ScannerComment(text, style)) => {
self.last_comments.push(Comment {
text,
style,
loc: t.loc,
});
true
}
_ => false,
}
}
fn on_node_start(&mut self, loc: Location) -> Self::NodeId {
let new_node = self.final_comments.new_node(loc);
if !self.last_comments.is_empty() {
let comments = self.last_comments.drain(..);
if let Some(last_node) = self.last_node {
self.final_comments
.track_comments_between(comments, last_node, new_node);
} else {
self.final_comments
.track_comments_before(comments, new_node);
}
}
self.last_node = Some(new_node);
new_node
}
fn on_node_end(&mut self) {
if let Some(last_node) = self.last_node.take()
&& !self.last_comments.is_empty()
{
let comments = self.last_comments.drain(..);
self.final_comments
.track_comments_after(comments, last_node);
}
}
fn on_node_end_restore(&mut self, node: Self::NodeId) {
self.on_node_end();
self.last_node = Some(node);
}
fn metadata(&self) -> &impl Metadata<'s, NodeId = Self::NodeId> {
self
}
fn finish(mut self) -> Self::Metadata {
self.on_node_end();
self.final_comments.finish()
}
}
#[derive(Default, Debug)]
struct CommentsAccum<'s> {
comments: Vec<Comment<'s>>,
nodes: Vec<NodeWithComments>,
}
#[derive(Debug)]
struct NodeWithComments {
node: Id,
leading: WeakRef,
trailing: WeakRef,
}
#[derive(Debug, Copy, Clone)]
struct WeakRef {
pos: u32,
len: u32,
}
impl WeakRef {
fn sub_slice<T>(self, slice: &[T]) -> &[T] {
&slice[self.pos as usize..self.pos as usize + self.len as usize]
}
}
impl<'s> CommentsAccum<'s> {
fn new_node(&mut self, loc: Location) -> Id {
Id(loc)
}
fn track_comments_after(&mut self, comments: impl Iterator<Item = Comment<'s>>, node: Id) {
if let Some((num_comments, comments_pos)) = self.push_comments_(comments) {
self.track_comments_as_trailing_(node, num_comments.get(), comments_pos);
}
}
fn track_comments_before(&mut self, comments: impl Iterator<Item = Comment<'s>>, node: Id) {
if let Some((num_comments, comments_pos)) = self.push_comments_(comments) {
self.track_comments_as_leading_(node, num_comments.get(), comments_pos);
}
}
fn track_comments_between(
&mut self,
comments: impl Iterator<Item = Comment<'s>>,
node_before: Id,
node_after: Id,
) {
let Some((num_comments, comments_pos)) = self.push_comments_(comments) else {
return;
};
self.track_comments_as_trailing_(node_before, num_comments.get(), comments_pos);
self.track_comments_as_leading_(node_after, num_comments.get(), comments_pos);
}
fn finish(self) -> DefaultMetadata<'s> {
DefaultMetadata(self)
}
fn track_comments_as_trailing_(&mut self, node: Id, num_comments: u32, comments_pos: u32) {
debug_assert!(num_comments > 0);
match self.nodes.binary_search_by(|x| x.node.cmp(&node)) {
Ok(i) => {
self.nodes[i].trailing = WeakRef {
pos: comments_pos,
len: num_comments,
};
}
Err(i) => {
self.nodes.insert(
i,
NodeWithComments {
node,
leading: WeakRef { pos: 0, len: 0 },
trailing: WeakRef {
pos: comments_pos,
len: num_comments,
},
},
);
}
}
}
fn track_comments_as_leading_(&mut self, node: Id, num_comments: u32, comments_pos: u32) {
debug_assert!(num_comments > 0);
match self.nodes.binary_search_by(|x| x.node.cmp(&node)) {
Ok(i) => {
self.nodes[i].leading = WeakRef {
pos: comments_pos,
len: num_comments,
}
}
Err(i) => {
self.nodes.insert(
i,
NodeWithComments {
node,
leading: WeakRef {
pos: comments_pos,
len: num_comments,
},
trailing: WeakRef { pos: 0, len: 0 },
},
);
}
}
}
fn push_comments_(
&mut self,
comments: impl Iterator<Item = Comment<'s>>,
) -> Option<(NonZeroU32, u32)> {
let (mut num_comments, index) = (0, self.comments.len() as u32);
for comment in comments {
self.comments.push(comment);
num_comments += 1;
}
if self.comments.len() >= u32::MAX as usize {
panic!("too many comments");
}
if num_comments == 0 {
None
} else {
Some((
NonZeroU32::new(num_comments).expect("invalid num_comments"),
index,
))
}
}
}
impl<'s> Metadata<'s> for CommentsAccum<'s> {
type NodeId = Id;
fn location(&self, node: Self::NodeId) -> Location {
node.0
}
fn comments(&self, node: Self::NodeId) -> (&[Comment<'s>], &[Comment<'s>]) {
match self.nodes.binary_search_by(|x| x.node.cmp(&node)) {
Ok(i) => {
let (n, cs) = (&self.nodes[i], self.comments.as_slice());
(n.leading.sub_slice(cs), n.trailing.sub_slice(cs))
}
Err(_) => (&[], &[]),
}
}
}
impl<'s> Metadata<'s> for DefaultTracker<'s> {
type NodeId = Id;
fn location(&self, node: Self::NodeId) -> Location {
node.0
}
fn comments(&self, node: Self::NodeId) -> (&[Comment<'s>], &[Comment<'s>]) {
let comments = self.final_comments.comments(node);
if Some(node) == self.last_node && !self.last_comments.is_empty() {
(comments.0, &self.last_comments)
} else {
comments
}
}
}
#[derive(Default)]
pub(super) struct LocationsTracker;
impl<'s> Tracker<'s> for LocationsTracker {
type NodeId = Location;
type Metadata = LocationMetadata;
fn on_node_start(&mut self, loc: Location) -> Self::NodeId {
loc
}
fn on_node_end(&mut self) {}
fn on_node_end_restore(&mut self, _node: Self::NodeId) {}
fn metadata(&self) -> &impl Metadata<'s, NodeId = Self::NodeId> {
&LocationMetadata
}
fn finish(self) -> Self::Metadata {
LocationMetadata
}
}
pub(super) struct LocationMetadata;
impl<'s> Metadata<'s> for LocationMetadata {
type NodeId = Location;
fn location(&self, node: Self::NodeId) -> Location {
node
}
fn comments(&self, _node: Self::NodeId) -> (&[Comment<'s>], &[Comment<'s>]) {
(&[][..], &[][..])
}
}
#[derive(Default)]
pub(super) struct VoidTracker;
impl<'s> Tracker<'s> for VoidTracker {
type NodeId = ();
type Metadata = VoidMetadata;
fn on_node_start(&mut self, _loc: Location) -> Self::NodeId {}
fn on_node_end(&mut self) {}
fn on_node_end_restore(&mut self, _node: Self::NodeId) {}
fn metadata(&self) -> &impl Metadata<'s, NodeId = Self::NodeId> {
&VoidMetadata
}
fn finish(self) -> Self::Metadata {
VoidMetadata
}
}
pub(super) struct VoidMetadata;
impl<'s> Metadata<'s> for VoidMetadata {
type NodeId = ();
fn location(&self, _node: Self::NodeId) -> Location {
Location { line: 0, col: 0 }
}
fn comments(&self, _node: Self::NodeId) -> (&[Comment<'s>], &[Comment<'s>]) {
(&[][..], &[][..])
}
}
#[cfg(test)]
mod tests {
use pretty_assertions::assert_eq;
use super::*;
impl<'s> DefaultMetadata<'s> {
fn comments_all(&self) -> &[Comment<'s>] {
&self.0.comments
}
}
#[test]
fn test_default_tracker() {
fn tok_comment<'s>(text: &'s str, loc: Location) -> Token<'s> {
Token {
ttype: TokenType::Comment(ScannerComment(text, CommentStyle::Block)),
loc,
}
}
fn comment<'s>(text: &'s str, loc: Location) -> Comment<'s> {
Comment {
text,
style: CommentStyle::Block,
loc,
}
}
let mut t = DefaultTracker::default();
assert!(t.accept_comment(&tok_comment(" one ", (1, 1).into())));
assert!(t.accept_comment(&tok_comment(" two ", (1, 5).into())));
let node_123 = t.on_node_start((1, 15).into()); assert!(!t.accept_comment(&Token {
ttype: TokenType::Plus,
loc: (1, 20).into()
}));
let node_plus = t.on_node_start((1, 20).into()); assert!(t.accept_comment(&tok_comment(" three ", (1, 40).into())));
let node_456 = t.on_node_start((1, 40).into()); assert!(t.accept_comment(&tok_comment(" four ", (1, 50).into())));
let meta = t.finish();
assert_eq!(
&[
comment(" one ", (1, 1).into()),
comment(" two ", (1, 5).into()),
comment(" three ", (1, 40).into()),
comment(" four ", (1, 50).into()),
][..],
meta.comments_all()
);
assert_eq!(
(
&[
comment(" one ", (1, 1).into()),
comment(" two ", (1, 5).into())
][..],
&[][..]
),
meta.comments(node_123)
);
assert_eq!(
(&[][..], &[comment(" three ", (1, 40).into())][..]),
meta.comments(node_plus)
);
assert_eq!(
(
&[comment(" three ", (1, 40).into())][..],
&[comment(" four ", (1, 50).into())][..]
),
meta.comments(node_456)
);
}
}