use crate::{LexerMode, Position};
use std::fmt;
#[derive(Debug, Clone, PartialEq)]
pub struct LexerCheckpoint {
pub position: usize,
pub mode: LexerMode,
pub delimiter_stack: Vec<char>,
pub in_prototype: bool,
pub prototype_depth: usize,
pub after_sub: bool,
pub after_arrow: bool,
pub hash_brace_depth: usize,
pub after_var_subscript: bool,
pub paren_depth: usize,
pub current_pos: Position,
pub context: CheckpointContext,
}
#[derive(Debug, Clone, PartialEq)]
pub enum CheckpointContext {
Normal,
Heredoc { terminator: String, is_interpolated: bool },
Format { start_position: usize },
Regex { delimiter: char, flags_position: Option<usize> },
QuoteLike { operator: String, delimiter: char, is_paired: bool },
}
impl LexerCheckpoint {
pub fn new() -> Self {
Self {
position: 0,
mode: LexerMode::ExpectTerm,
delimiter_stack: Vec::new(),
in_prototype: false,
prototype_depth: 0,
after_sub: false,
after_arrow: false,
hash_brace_depth: 0,
after_var_subscript: false,
paren_depth: 0,
current_pos: Position::start(),
context: CheckpointContext::Normal,
}
}
pub fn at_position(position: usize) -> Self {
Self { position, ..Self::new() }
}
pub fn is_at_start(&self) -> bool {
self.position == 0
}
pub fn diff(&self, other: &Self) -> CheckpointDiff {
CheckpointDiff {
position_delta: self.position as isize - other.position as isize,
mode_changed: self.mode != other.mode,
delimiter_stack_changed: self.delimiter_stack != other.delimiter_stack,
prototype_state_changed: self.in_prototype != other.in_prototype
|| self.prototype_depth != other.prototype_depth
|| self.after_sub != other.after_sub
|| self.after_arrow != other.after_arrow
|| self.hash_brace_depth != other.hash_brace_depth
|| self.after_var_subscript != other.after_var_subscript
|| self.paren_depth != other.paren_depth,
context_changed: self.context != other.context,
}
}
pub fn apply_edit(&mut self, start: usize, old_len: usize, new_len: usize) {
if self.position > start {
if self.position >= start + old_len {
self.position = self.position - old_len + new_len;
} else {
self.position = start;
self.mode = LexerMode::ExpectTerm;
self.delimiter_stack.clear();
self.in_prototype = false;
self.prototype_depth = 0;
self.after_sub = false;
self.after_arrow = false;
self.hash_brace_depth = 0;
self.after_var_subscript = false;
self.paren_depth = 0;
self.context = CheckpointContext::Normal;
}
}
}
pub fn is_valid_for(&self, input: &str) -> bool {
self.position <= input.len()
}
}
impl Default for LexerCheckpoint {
fn default() -> Self {
Self::new()
}
}
impl fmt::Display for LexerCheckpoint {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"Checkpoint@{} mode={:?} delims={} proto={} after_sub={}",
self.position,
self.mode,
self.delimiter_stack.len(),
self.in_prototype,
self.after_sub
)
}
}
#[derive(Debug)]
pub struct CheckpointDiff {
pub position_delta: isize,
pub mode_changed: bool,
pub delimiter_stack_changed: bool,
pub prototype_state_changed: bool,
pub context_changed: bool,
}
impl CheckpointDiff {
pub fn has_state_changes(&self) -> bool {
self.mode_changed
|| self.delimiter_stack_changed
|| self.prototype_state_changed
|| self.context_changed
}
}
pub trait Checkpointable {
fn checkpoint(&self) -> LexerCheckpoint;
fn restore(&mut self, checkpoint: &LexerCheckpoint);
fn can_restore(&self, checkpoint: &LexerCheckpoint) -> bool;
}
pub struct CheckpointCache {
checkpoints: Vec<(usize, LexerCheckpoint)>,
max_checkpoints: usize,
}
impl CheckpointCache {
pub fn new(max_checkpoints: usize) -> Self {
Self { checkpoints: Vec::new(), max_checkpoints }
}
pub fn add(&mut self, checkpoint: LexerCheckpoint) {
let position = checkpoint.position;
self.checkpoints.retain(|(pos, _)| *pos != position);
self.checkpoints.push((position, checkpoint));
self.checkpoints.sort_by_key(|(pos, _)| *pos);
if self.checkpoints.len() > self.max_checkpoints {
let total = self.checkpoints.len();
let step = total as f64 / self.max_checkpoints as f64;
let mut kept = Vec::new();
for i in 0..self.max_checkpoints {
let idx = (i as f64 * step) as usize;
if idx < total {
kept.push(self.checkpoints[idx].clone());
}
}
self.checkpoints = kept;
}
}
pub fn find_before(&self, position: usize) -> Option<&LexerCheckpoint> {
self.checkpoints.iter().rev().find(|(pos, _)| *pos <= position).map(|(_, cp)| cp)
}
pub fn clear(&mut self) {
self.checkpoints.clear();
}
pub fn apply_edit(&mut self, start: usize, old_len: usize, new_len: usize) {
for (pos, checkpoint) in &mut self.checkpoints {
checkpoint.apply_edit(start, old_len, new_len);
*pos = checkpoint.position;
}
self.checkpoints
.retain(|(_, cp)| !matches!(cp.context, CheckpointContext::Normal) || cp.position > 0);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_checkpoint_creation() {
let cp = LexerCheckpoint::new();
assert_eq!(cp.position, 0);
assert_eq!(cp.mode, LexerMode::ExpectTerm);
assert!(cp.delimiter_stack.is_empty());
}
#[test]
fn test_checkpoint_diff() {
let cp1 = LexerCheckpoint::at_position(10);
let mut cp2 = cp1.clone();
cp2.position = 20;
cp2.mode = LexerMode::ExpectOperator;
let diff = cp2.diff(&cp1);
assert_eq!(diff.position_delta, 10);
assert!(diff.mode_changed);
assert!(!diff.delimiter_stack_changed);
}
#[test]
fn test_checkpoint_edit() {
let mut cp = LexerCheckpoint::at_position(50);
cp.apply_edit(10, 5, 10);
assert_eq!(cp.position, 55);
let mut cp2 = LexerCheckpoint::at_position(50);
cp2.apply_edit(60, 10, 5);
assert_eq!(cp2.position, 50);
let mut cp3 = LexerCheckpoint::at_position(50);
cp3.apply_edit(45, 10, 5);
assert_eq!(cp3.position, 45); }
#[test]
fn test_checkpoint_cache() -> std::result::Result<(), Box<dyn std::error::Error>> {
let mut cache = CheckpointCache::new(3);
cache.add(LexerCheckpoint::at_position(10));
cache.add(LexerCheckpoint::at_position(20));
cache.add(LexerCheckpoint::at_position(30));
cache.add(LexerCheckpoint::at_position(40));
assert_eq!(cache.checkpoints.len(), 3);
let cp = cache.find_before(25).ok_or("Expected checkpoint before position 25")?;
assert_eq!(cp.position, 20);
Ok(())
}
}