use perl_ast::ast::Node;
use std::ops::Range;
use crate::{PragmaSnapshot, PragmaState, range_builder};
#[derive(Debug, Clone, PartialEq)]
pub struct PragmaStateQuery {
offset: usize,
snapshot: PragmaSnapshot,
}
impl PragmaStateQuery {
#[must_use]
pub fn offset(&self) -> usize {
self.offset
}
#[must_use]
pub fn snapshot(&self) -> &PragmaSnapshot {
&self.snapshot
}
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct CompileTimePragmaEnvironment {
map: PragmaMap,
}
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub struct PragmaEntry {
pub range: Range<usize>,
pub snapshot: PragmaSnapshot,
}
#[derive(Debug, Clone, Default, PartialEq)]
#[non_exhaustive]
pub struct PragmaMap {
entries: Box<[PragmaEntry]>,
}
impl CompileTimePragmaEnvironment {
#[must_use]
pub fn build(ast: &Node) -> Self {
let mut ranges = Vec::new();
let mut current_state = PragmaState::default();
range_builder::build_ranges(ast, &mut current_state, &mut ranges);
ranges.sort_by_key(|(range, _)| range.start);
let entries = ranges
.into_iter()
.map(|(range, state)| PragmaEntry { range, snapshot: PragmaSnapshot::from(state) })
.collect::<Vec<_>>()
.into_boxed_slice();
Self { map: PragmaMap { entries } }
}
#[must_use]
pub fn query_at(&self, offset: usize) -> PragmaStateQuery {
PragmaStateQuery { offset, snapshot: self.snapshot_at(offset) }
}
#[must_use]
pub fn snapshot_at(&self, offset: usize) -> PragmaSnapshot {
self.map.snapshot_at(offset)
}
#[must_use]
pub fn map(&self) -> &PragmaMap {
&self.map
}
#[must_use]
pub fn as_map(&self) -> Vec<(Range<usize>, PragmaSnapshot)> {
self.map.to_tuples()
}
}
impl PragmaMap {
#[must_use]
pub fn snapshot_at(&self, offset: usize) -> PragmaSnapshot {
let idx = self.entries.partition_point(|entry| entry.range.start <= offset);
let snapshot = if idx > 0 {
self.entries[idx - 1].snapshot.clone()
} else {
PragmaSnapshot::default()
};
normalize_snapshot(snapshot)
}
#[must_use]
pub fn state_at(&self, offset: usize) -> PragmaState {
self.snapshot_at(offset).into()
}
#[must_use]
pub fn final_state(&self) -> PragmaState {
let state = self
.entries
.last()
.map_or_else(PragmaState::default, |entry| entry.snapshot.clone().into());
normalize_state(state)
}
#[must_use]
pub fn cursor(&self) -> PragmaQueryCursor {
PragmaQueryCursor::new()
}
#[must_use]
pub fn entries(&self) -> &[PragmaEntry] {
&self.entries
}
#[must_use]
pub fn to_tuples(&self) -> Vec<(Range<usize>, PragmaSnapshot)> {
self.entries.iter().map(|e| (e.range.clone(), e.snapshot.clone())).collect()
}
}
fn normalize_snapshot(mut snapshot: PragmaSnapshot) -> PragmaSnapshot {
if snapshot.state.signatures_strict {
snapshot.state.strict_vars = true;
snapshot.state.strict_subs = true;
snapshot.state.strict_refs = true;
}
snapshot
}
pub(crate) fn normalize_state(mut state: PragmaState) -> PragmaState {
if state.signatures_strict {
state.strict_vars = true;
state.strict_subs = true;
state.strict_refs = true;
}
state
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub struct PragmaQueryCursor {
index: usize,
}
impl PragmaQueryCursor {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn snapshot_at(&mut self, pragma_map: &PragmaMap, offset: usize) -> PragmaSnapshot {
let snapshot = self
.entry_for_offset(pragma_map.entries(), offset)
.map_or_else(PragmaSnapshot::default, |entry| entry.snapshot.clone());
normalize_snapshot(snapshot)
}
pub fn state_at(&mut self, pragma_map: &PragmaMap, offset: usize) -> PragmaState {
self.snapshot_at(pragma_map, offset).into()
}
pub fn state_for_offset(
&mut self,
pragma_map: &[(Range<usize>, PragmaState)],
offset: usize,
) -> PragmaState {
if pragma_map.is_empty() {
return PragmaState::default();
}
if self.index >= pragma_map.len() {
self.index = pragma_map.len() - 1;
}
if pragma_map[self.index].0.start > offset {
self.index = pragma_map.partition_point(|(range, _)| range.start <= offset);
if self.index > 0 {
self.index -= 1;
}
} else {
while self.index + 1 < pragma_map.len() && pragma_map[self.index + 1].0.start <= offset
{
self.index += 1;
}
}
let state = if pragma_map[self.index].0.start <= offset {
pragma_map[self.index].1.clone()
} else {
PragmaState::default()
};
normalize_state(state)
}
fn entry_for_offset<'a>(
&mut self,
entries: &'a [PragmaEntry],
offset: usize,
) -> Option<&'a PragmaEntry> {
if entries.is_empty() {
return None;
}
if self.index >= entries.len() {
self.index = entries.len() - 1;
}
if entries[self.index].range.start > offset {
self.index = entries.partition_point(|entry| entry.range.start <= offset);
if self.index > 0 {
self.index -= 1;
}
} else {
while self.index + 1 < entries.len() && entries[self.index + 1].range.start <= offset {
self.index += 1;
}
}
if entries[self.index].range.start <= offset { Some(&entries[self.index]) } else { None }
}
}