Merge origin/main into macos
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
170
lib/reactivity_parsing/src/parse.rs
Normal file
170
lib/reactivity_parsing/src/parse.rs
Normal file
@@ -0,0 +1,170 @@
|
||||
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,
|
||||
}
|
||||
|
||||
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>,
|
||||
) -> Result<ParseResult<Self::Checkpoint, Self::Tree, Self::Diagnostic>, ParseError>;
|
||||
}
|
||||
|
||||
#[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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user