Files
ruin/lib/reactivity_parsing/src/parse.rs
Will Temple 4347232b98 Clippy clean
2026-05-16 16:59:09 -04:00

173 lines
4.9 KiB
Rust

use core::fmt;
use crate::{RopeCursor, TextRange};
pub trait ParserCheckpoint: Clone + Eq + fmt::Debug + 'static {}
impl<T> ParserCheckpoint for T where T: Clone + Eq + fmt::Debug + 'static {}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct SyntaxFragment<C, T, D> {
pub range: TextRange,
pub start_checkpoint: C,
pub end_checkpoint: C,
pub tree: T,
pub diagnostics: Vec<D>,
pub grammar_revision: u64,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ParseTree<T> {
pub root: T,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ParseInvalidation<C> {
pub resume_at: usize,
pub restart_checkpoint: C,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ParseResult<C, T, D> {
pub tree: ParseTree<T>,
pub fragments: Vec<SyntaxFragment<C, T, D>>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ParseError {
pub message: String,
}
pub type ParserResult<C, T, D> = Result<ParseResult<C, T, D>, ParseError>;
impl ParseError {
pub fn new(message: impl Into<String>) -> Self {
Self {
message: message.into(),
}
}
}
pub trait IncrementalParser {
type Checkpoint: ParserCheckpoint;
type Tree: Clone + fmt::Debug + 'static;
type Diagnostic: Clone + fmt::Debug + 'static;
fn grammar_revision(&self) -> u64;
fn initial_checkpoint(&self) -> Self::Checkpoint;
fn invalidate(
&self,
delta_range: TextRange,
previous_fragments: &[SyntaxFragment<Self::Checkpoint, Self::Tree, Self::Diagnostic>],
) -> ParseInvalidation<Self::Checkpoint>;
fn parse(
&self,
cursor: RopeCursor,
fragments: &[SyntaxFragment<Self::Checkpoint, Self::Tree, Self::Diagnostic>],
invalidation: ParseInvalidation<Self::Checkpoint>,
) -> ParserResult<Self::Checkpoint, Self::Tree, Self::Diagnostic>;
}
#[cfg(test)]
mod tests {
use std::cell::Cell;
use std::rc::Rc;
use ruin_reactivity::Reactor;
use super::{
IncrementalParser, ParseError, ParseInvalidation, ParseResult, ParseTree, SyntaxFragment,
};
use crate::{ReactiveRope, RopeCursor, TextRange};
#[derive(Clone, Debug)]
struct DummyParser {
parse_calls: Rc<Cell<usize>>,
}
impl IncrementalParser for DummyParser {
type Checkpoint = u8;
type Tree = String;
type Diagnostic = String;
fn grammar_revision(&self) -> u64 {
7
}
fn initial_checkpoint(&self) -> Self::Checkpoint {
0
}
fn invalidate(
&self,
delta_range: TextRange,
_previous_fragments: &[SyntaxFragment<
Self::Checkpoint,
Self::Tree,
Self::Diagnostic,
>],
) -> ParseInvalidation<Self::Checkpoint> {
ParseInvalidation {
resume_at: delta_range.start.saturating_sub(1),
restart_checkpoint: 1,
}
}
fn parse(
&self,
mut cursor: RopeCursor,
_fragments: &[SyntaxFragment<Self::Checkpoint, Self::Tree, Self::Diagnostic>],
invalidation: ParseInvalidation<Self::Checkpoint>,
) -> Result<ParseResult<Self::Checkpoint, Self::Tree, Self::Diagnostic>, ParseError>
{
self.parse_calls.set(self.parse_calls.get() + 1);
let text = cursor.read_bytes(usize::MAX);
Ok(ParseResult {
tree: ParseTree { root: text.clone() },
fragments: vec![SyntaxFragment {
range: TextRange::new(
invalidation.resume_at,
invalidation.resume_at + text.len(),
),
start_checkpoint: invalidation.restart_checkpoint,
end_checkpoint: 2,
tree: text,
diagnostics: Vec::new(),
grammar_revision: self.grammar_revision(),
}],
})
}
}
#[test]
fn parse_error_constructor_sets_message() {
let error = ParseError::new("bad");
assert_eq!(error.message, "bad");
}
#[test]
fn parser_trait_round_trip_produces_fragments() {
let reactor = Reactor::new();
let rope = ReactiveRope::from_text(&reactor, "{\"a\":1}");
let parser = DummyParser {
parse_calls: Rc::new(Cell::new(0)),
};
let invalidation = parser.invalidate(TextRange::new(3, 4), &[]);
assert_eq!(invalidation.resume_at, 2);
assert_eq!(invalidation.restart_checkpoint, 1);
let result = parser
.parse(rope.cursor(0), &[], invalidation)
.expect("dummy parse should succeed");
assert_eq!(result.tree.root, "{\"a\":1}");
assert_eq!(result.fragments.len(), 1);
assert_eq!(result.fragments[0].grammar_revision, 7);
assert_eq!(parser.parse_calls.get(), 1);
}
}