mod codec;
pub(crate) mod exec;
mod parse;
use std::sync::{
atomic::{AtomicBool, Ordering},
Arc,
};
use strop_core::id::ByteOffset;
use strop_core::Buffer;
pub use exec::STEP_BUDGET;
use parse::Program;
#[derive(
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize,
)]
pub struct SearchMatch {
pub start: ByteOffset,
pub end: ByteOffset,
}
impl SearchMatch {
pub fn len(&self) -> usize {
self.end - self.start
}
pub fn is_empty(&self) -> bool {
self.start == self.end
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum QueryError {
Unsupported { construct: &'static str, at: usize },
UnbalancedGroup { at: usize },
UnclosedClass { at: usize },
UnclosedOptional { at: usize },
BadRepeat { at: usize },
BadClass { at: usize },
BadCharCode { at: usize },
TrailingBackslash { at: usize },
TooComplex,
Cancelled,
}
impl std::fmt::Display for QueryError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let (head, at): (&str, Option<usize>) = match self {
QueryError::Unsupported { construct, at } => {
return write!(f, "unsupported query syntax: {construct} (at byte {at})")
}
QueryError::UnbalancedGroup { at } => ("unbalanced \\( \\)", Some(*at)),
QueryError::UnclosedClass { at } => ("unclosed [...]", Some(*at)),
QueryError::UnclosedOptional { at } => ("unclosed \\%[", Some(*at)),
QueryError::BadRepeat { at } => ("malformed repetition", Some(*at)),
QueryError::BadClass { at } => ("malformed [...]", Some(*at)),
QueryError::BadCharCode { at } => ("malformed character code", Some(*at)),
QueryError::TrailingBackslash { at } => ("trailing backslash", Some(*at)),
QueryError::Cancelled => return f.write_str("query cancelled"),
QueryError::TooComplex => {
return write!(f, "query too complex (over {STEP_BUDGET} steps)")
}
};
match at {
Some(at) => write!(f, "{head} (at byte {at})"),
None => write!(f, "{head}"),
}
}
}
impl std::error::Error for QueryError {}
#[derive(Clone)]
pub struct CompiledQuery {
source: Arc<str>,
whole_word: bool,
prog: Arc<Program>,
cancel: Option<Arc<AtomicBool>>,
}
impl CompiledQuery {
pub fn compile(pattern: &str, whole_word: bool) -> Result<Self, QueryError> {
let prog = parse::compile(pattern, whole_word)?;
Ok(Self {
source: Arc::from(pattern),
whole_word,
prog: Arc::new(prog),
cancel: None,
})
}
pub fn cancellable(&self, cancel: Arc<AtomicBool>) -> Self {
let mut query = self.clone();
query.cancel = Some(cancel);
query
}
pub(crate) fn cancelled(&self) -> bool {
self.cancel
.as_ref()
.is_some_and(|cancel| cancel.load(Ordering::Acquire))
}
pub fn source(&self) -> &str {
&self.source
}
pub fn whole_word(&self) -> bool {
self.whole_word
}
pub(super) fn program(&self) -> &Program {
&self.prog
}
}
impl std::fmt::Debug for CompiledQuery {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("CompiledQuery")
.field("source", &self.source)
.field("whole_word", &self.whole_word)
.finish()
}
}
impl PartialEq for CompiledQuery {
fn eq(&self, other: &Self) -> bool {
self.source == other.source && self.whole_word == other.whole_word
}
}
impl Eq for CompiledQuery {}
pub fn search_forward(
buf: &Buffer,
from: usize,
query: &CompiledQuery,
) -> Result<Option<SearchMatch>, QueryError> {
super::resolve::search::forward(buf, from, query)
}
pub fn search_backward(
buf: &Buffer,
from: usize,
query: &CompiledQuery,
) -> Result<Option<SearchMatch>, QueryError> {
super::resolve::search::backward(buf, from, query)
}
pub fn search_all(buf: &Buffer, query: &CompiledQuery) -> Result<Vec<SearchMatch>, QueryError> {
super::resolve::search::all(buf, query)
}
pub fn search_visit(
buf: &Buffer,
query: &CompiledQuery,
visitor: impl FnMut(SearchMatch) -> std::ops::ControlFlow<()>,
) -> Result<(), QueryError> {
super::resolve::search::visit_all(buf, query, visitor)
}