173 lines
4.9 KiB
Rust
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);
|
|
}
|
|
}
|