use crate::frontend::ast::{AstNode, Regex, RuleDecl};
use crate::frontend::lexer::Token;
use crate::frontend::sema::TokenName;
use crate::{Cst, NodeRef, Rule, SemanticData};
use logos::Span;
use std::collections::BTreeSet;
use super::lookup::*;
pub fn hover(cst: &Cst<'_>, sema: &SemanticData<'_>, pos: usize) -> Option<(String, Span)> {
let node = lookup_rule_node(cst, NodeRef::ROOT, pos)?;
let span = cst.span(node);
fn filter_part_eof<'a>(tokens: &BTreeSet<TokenName<'a>>) -> String {
format!(
"{:?}",
tokens
.iter()
.filter(|tok| tok.0.as_ref() == "EOF" || !tok.0.as_ref().starts_with("EOF"))
.collect::<BTreeSet<_>>()
)
}
if let Some(regex) = Regex::cast(cst, node) {
let first = &sema
.first_sets
.get(®ex.syntax())
.map_or("{}".to_string(), filter_part_eof);
let follow = &sema
.follow_sets
.get(®ex.syntax())
.map_or("{}".to_string(), filter_part_eof);
let predict = &sema
.predict_sets
.get(®ex.syntax())
.map_or("{}".to_string(), filter_part_eof);
match regex {
Regex::Star(_) | Regex::Plus(_) | Regex::Optional(_) => {
let recovery = &sema
.recovery_sets
.get(®ex.syntax())
.map_or("{}".to_string(), filter_part_eof);
Some((
format!(
"**First:** {first}\n\n**Follow:** {follow}\n\n**Predict:** {predict}\n\n**Recovery:** {recovery}"
),
span,
))
}
Regex::Name(_) | Regex::Symbol(_) => {
let comment_attached_node = sema.decl_bindings.get(&node).and_then(|decl| {
find_node(cst, NodeRef::ROOT, cst.span(*decl).start, |r| {
r == Rule::TokenList || r == Rule::RuleDecl
})
});
let mut comment_nodes = vec![];
if let Some(comment_attached_node) = comment_attached_node {
for i in 1.. {
if let Some(node) = cst.match_token(
NodeRef(comment_attached_node.0.saturating_sub(i)),
Token::DocComment,
) {
comment_nodes.push(node);
} else {
break;
}
}
}
let mut comment = String::new();
for (val, _) in comment_nodes.iter().rev() {
comment.push_str(val.strip_prefix("///").unwrap().trim_start());
}
if !comment.is_empty() {
comment.push_str("\n---\n")
}
Some((
format!(
"{comment}**First:** {first}\n\n**Follow:** {follow}\n\n**Predict:** {predict}"
),
span,
))
}
_ => Some((
format!("**First:** {first}\n\n**Follow:** {follow}\n\n**Predict:** {predict}"),
span,
)),
}
} else if let Some(rule) = RuleDecl::cast(cst, node) {
let regex = rule.regex(cst)?;
let first = &sema
.first_sets
.get(®ex.syntax())
.map_or("{}".to_string(), filter_part_eof);
let follow = &sema
.follow_sets
.get(®ex.syntax())
.map_or("{}".to_string(), filter_part_eof);
let predict = &sema
.predict_sets
.get(®ex.syntax())
.map_or("{}".to_string(), filter_part_eof);
Some((
format!("**First:** {first}\n\n**Follow:** {follow}\n\n**Predict:** {predict}"),
span,
))
} else {
None
}
}