use std::cmp::Ordering;
use std::collections::{HashMap, HashSet};
use super::forking::{KeywordLeads, Lead};
use super::program::{Name, Rule};
use super::results::{TokenOrder, Tree, TreeType};
use super::token_conflicts::token_conflict;
#[derive(Debug, Default)]
pub(super) struct KeywordLexing {
only: HashSet<(usize, usize)>,
matched: HashMap<(usize, usize), HashSet<Option<usize>>>,
immediates: HashSet<(usize, usize)>,
matched_immediate: HashMap<(usize, usize), HashSet<Option<usize>>>,
separators: HashSet<usize>,
matched_separators: HashMap<usize, HashSet<Option<usize>>>,
joined: HashMap<usize, usize>,
matched_joins: HashMap<usize, (usize, HashSet<Option<usize>>)>,
calls: Vec<Call>,
}
#[derive(Debug)]
struct Call {
position: usize,
builds: bool,
rule: Name,
name: Name,
parents: HashSet<Option<usize>>,
}
impl KeywordLexing {
pub(super) fn call(
&mut self,
position: usize,
builds: bool,
rule: Name,
name: Name,
parent: Option<usize>,
) -> usize {
self.calls.push(Call {
position,
builds,
rule,
name,
parents: HashSet::from([parent]),
});
self.calls.len() - 1
}
pub(super) fn called(&mut self, call: usize, parent: Option<usize>) {
self.calls[call].parents.insert(parent);
}
pub(super) fn matched(&mut self, span: (usize, usize), call: Option<usize>, immediate: bool) {
let matched = if immediate {
&mut self.matched_immediate
} else {
&mut self.matched
};
matched.entry(span).or_default().insert(call);
}
pub(super) fn matched_separator(&mut self, position: usize, call: Option<usize>) {
self.matched_separators
.entry(position)
.or_default()
.insert(call);
}
pub(super) fn match_join(&mut self, position: usize, reach: usize, call: Option<usize>) {
let join = self
.matched_joins
.entry(position)
.or_insert_with(|| (reach, HashSet::new()));
join.0 = join.0.max(reach);
join.1.insert(call);
}
pub(super) fn has_joins(&self) -> bool {
!self.joined.is_empty()
}
pub(super) fn joined(&self, position: usize) -> Option<usize> {
self.joined.get(&position).copied()
}
pub(super) fn lexes_separator(&self, position: usize) -> bool {
self.separators.contains(&position)
}
pub(super) fn immediate_only(&self, span: (usize, usize)) -> bool {
self.immediates.contains(&span)
}
pub(super) fn outranks(&self, leaf: &Tree, tokens: TokenOrder<'_>) -> bool {
self.only.contains(&(leaf.start, leaf.end)) && outranks_at(leaf, tokens)
}
pub(super) fn conflicts(
&mut self,
root: &Tree,
tokens: TokenOrder<'_>,
rules: &[Rule],
) -> bool {
let mut found = false;
let mut reach = None;
let mut pending = vec![root];
while let Some(node) = pending.pop() {
if node.ty == TreeType::Node {
pending.extend(node.children.iter().rev().map(|child| &**child));
continue;
}
if node.trivia && node.kind.is_none() {
if let Some((join, calls)) = self.matched_joins.get(&node.start)
&& !self.joined.contains_key(&node.start)
{
let leads = tokens.grammar.keyword_leads(rules);
let reach =
reach.get_or_insert_with(|| TreeReach::of(root, leads, tokens.bytes));
let mut seen = HashSet::new();
if calls.iter().any(|call| {
self.built_around(*call, node, reach)
&& self.in_parse_state(*call, node, reach, &mut seen, leads)
}) {
self.joined.insert(node.start, *join);
found = true;
}
}
let Some(calls) = self.matched_separators.get(&node.start) else {
continue;
};
if self.separators.contains(&node.start) {
continue;
}
let leads = tokens.grammar.keyword_leads(rules);
let reach = reach.get_or_insert_with(|| TreeReach::of(root, leads, tokens.bytes));
let mut seen = HashSet::new();
if !calls.iter().any(|call| {
self.built_around(*call, node, reach)
&& self.in_parse_state(*call, node, reach, &mut seen, leads)
}) {
continue;
}
self.separators.insert(node.start);
found = true;
continue;
}
let span = (node.start, node.end);
let (matched, only) = if node.plain {
(&self.matched_immediate, &self.immediates)
} else if node.lexed.is_some() {
(&self.matched, &self.only)
} else {
continue;
};
let Some(calls) = matched.get(&span) else {
continue;
};
if only.contains(&span) {
continue;
}
if let Some(kind) = node.lexed.as_ref().filter(|_| !node.plain) {
let leaf = Tree::new(TreeType::Token, Some(kind.clone()), node.start, node.end);
if !outranks_at(&leaf, tokens) {
continue;
}
}
let leads = tokens.grammar.keyword_leads(rules);
let reach = reach.get_or_insert_with(|| TreeReach::of(root, leads, tokens.bytes));
let mut seen = HashSet::new();
if !calls
.iter()
.any(|call| self.in_parse_state(*call, node, reach, &mut seen, leads))
{
continue;
}
if node.plain {
self.immediates.insert(span);
} else {
self.only.insert(span);
}
found = true;
}
self.matched.clear();
self.matched_immediate.clear();
self.matched_separators.clear();
self.matched_joins.clear();
self.calls.clear();
found
}
fn built_around(&self, call: Option<usize>, leaf: &Tree, reach: &TreeReach) -> bool {
let mut pending = vec![call];
let mut seen = HashSet::new();
while let Some(current) = pending.pop() {
let Some(current) = current else {
return true;
};
if !seen.insert(current) {
continue;
}
let call = &self.calls[current];
if call.builds {
if reach
.rules
.get(&(call.position, call.rule.clone()))
.is_some_and(|end| *end > leaf.start)
{
return true;
}
continue;
}
pending.extend(call.parents.iter().copied());
}
false
}
fn in_parse_state(
&self,
call: Option<usize>,
leaf: &Tree,
reach: &TreeReach,
seen: &mut HashSet<usize>,
leads: &KeywordLeads,
) -> bool {
let mut pending = vec![call];
while let Some(current) = pending.pop() {
let Some(current) = current else {
return true;
};
if !seen.insert(current) {
continue;
}
let call = &self.calls[current];
if call.builds && call.position < leaf.start {
let low = reach.starts.partition_point(|start| *start < call.position);
let first = reach.starts.get(low).copied();
if let Some(first) = first
&& first < leaf.start
&& reach.ends.get(&first).is_none_or(|end| *end < leaf.end)
{
continue;
}
if let Some(previous) = low.checked_sub(1).map(|at| reach.starts[at])
&& reach
.last
.get(&previous)
.is_some_and(|end| *end > call.position)
{
continue;
}
if let Some(&own) = reach.rules.get(&(call.position, call.rule.clone()))
&& own < leaf.start
&& (reach
.starts
.get(reach.starts.partition_point(|start| *start < own))
.is_some_and(|start| *start < leaf.start)
|| reach.widthless.contains(&own))
{
continue;
}
if let Some(keyword) = first
.filter(|first| *first < leaf.start)
.and_then(|first| reach.keywords.get(&first))
&& leads
.first
.get(&call.name)
.is_some_and(|set| !set.contains(&Lead::Literal(keyword.clone())))
{
continue;
}
}
pending.extend(call.parents.iter().copied());
}
false
}
}
struct TreeReach {
starts: Vec<usize>,
ends: HashMap<usize, usize>,
last: HashMap<usize, usize>,
rules: HashMap<(usize, Name), usize>,
keywords: HashMap<usize, Vec<u8>>,
widthless: HashSet<usize>,
}
impl TreeReach {
fn of(root: &Tree, leads: &KeywordLeads, bytes: &[u8]) -> Self {
let mut starts = Vec::new();
let mut ends = HashMap::new();
let mut last = HashMap::new();
let mut rules = HashMap::new();
let mut keywords = HashMap::new();
let mut widthless = HashSet::new();
let mut open: Option<usize> = None;
let mut pending = vec![root];
while let Some(node) = pending.pop() {
if node.trivia {
continue;
}
if node.ty != TreeType::Node && node.start == node.end && node.scanned {
widthless.insert(node.start);
continue;
}
if node.ty == TreeType::Node {
open = Some(open.map_or(node.end, |end| end.max(node.end)));
if let Some(rule) = &node.rule {
let farthest = rules.entry((node.start, rule.clone())).or_insert(node.end);
*farthest = (*farthest).max(node.end);
}
pending.extend(node.children.iter().rev().map(|child| &**child));
continue;
}
starts.push(node.start);
let leaf_end = last.entry(node.start).or_insert(node.end);
*leaf_end = (*leaf_end).max(node.end);
if node.ty == TreeType::Token
&& node.kind.is_none()
&& node.end - node.start <= leads.longest
&& let Some(text) = bytes.get(node.start..node.end)
&& leads.keywords.contains(text)
{
keywords.insert(node.start, text.to_vec());
}
if let Some(end) = open.take() {
let farthest = ends.entry(node.start).or_insert(end);
*farthest = (*farthest).max(end);
}
}
Self {
starts,
ends,
last,
rules,
keywords,
widthless,
}
}
}
fn outranks_at(leaf: &Tree, tokens: TokenOrder<'_>) -> bool {
let keyword = Tree::new(TreeType::Token, None, leaf.start, leaf.end);
token_conflict(&keyword, leaf, tokens) == Ordering::Greater
}