use core::fmt; use crate::{RopeCursor, TextRange}; pub trait ParserCheckpoint: Clone + Eq + fmt::Debug + 'static {} impl ParserCheckpoint for T where T: Clone + Eq + fmt::Debug + 'static {} #[derive(Clone, Debug, Eq, PartialEq)] pub struct SyntaxFragment { pub range: TextRange, pub start_checkpoint: C, pub end_checkpoint: C, pub tree: T, pub diagnostics: Vec, pub grammar_revision: u64, } #[derive(Clone, Debug, Eq, PartialEq)] pub struct ParseTree { pub root: T, } #[derive(Clone, Debug, Eq, PartialEq)] pub struct ParseInvalidation { pub resume_at: usize, pub restart_checkpoint: C, } #[derive(Clone, Debug, Eq, PartialEq)] pub struct ParseResult { pub tree: ParseTree, pub fragments: Vec>, } #[derive(Clone, Debug, Eq, PartialEq)] pub struct ParseError { pub message: String, } pub type ParserResult = Result, ParseError>; impl ParseError { pub fn new(message: impl Into) -> 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], ) -> ParseInvalidation; fn parse( &self, cursor: RopeCursor, fragments: &[SyntaxFragment], invalidation: ParseInvalidation, ) -> ParserResult; } #[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>, } 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 { ParseInvalidation { resume_at: delta_range.start.saturating_sub(1), restart_checkpoint: 1, } } fn parse( &self, mut cursor: RopeCursor, _fragments: &[SyntaxFragment], invalidation: ParseInvalidation, ) -> Result, 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); } }