Introduce incremental parsing

This commit is contained in:
Martin Haug 2021-11-02 12:06:22 +01:00
parent 52761a3baa
commit 1e4cab393e
4 changed files with 170 additions and 3 deletions

View File

@ -25,6 +25,20 @@ pub fn parse(src: &str) -> Rc<GreenNode> {
}
}
/// Parse a block. Returns `Some` if there was only one block.
pub fn parse_block(source: &str) -> Option<Rc<GreenNode>> {
let mut p = Parser::new(source);
block(&mut p);
if p.eof() {
match p.finish().into_iter().next() {
Some(Green::Node(node)) => Some(node),
_ => unreachable!(),
}
} else {
None
}
}
/// Parse markup.
fn markup(p: &mut Parser) {
markup_while(p, true, &mut |_| true)

View File

@ -268,7 +268,7 @@ impl SourceFile {
/// This panics if the `replace` range is out of bounds.
pub fn edit(&mut self, replace: Range<usize>, with: &str) {
let start = replace.start;
self.src.replace_range(replace, with);
self.src.replace_range(replace.clone(), with);
// Remove invalidated line starts.
let line = self.byte_to_line(start).unwrap();
@ -283,10 +283,41 @@ impl SourceFile {
self.line_starts
.extend(newlines(&self.src[start ..]).map(|idx| start + idx));
// Reparse.
// Update the root node.
#[cfg(not(feature = "parse-cache"))]
{
self.root = parse(&self.src);
}
#[cfg(feature = "parse-cache")]
{
let insertion_span = replace.into_span(self.id);
let incremental_target =
Rc::make_mut(&mut self.root).incremental_parent(insertion_span);
match incremental_target {
Some((child_idx, parent, offset)) => {
let child = &parent.children()[child_idx];
let src = &self.src[offset .. offset + child.len()];
let parse_res = match child.kind() {
NodeKind::Markup => Some(parse(src)),
_ => parse_block(src),
}
.and_then(|x| x.data().erroneous().not().then(|| x));
if let Some(parse_res) = parse_res {
parent.replace_child(child_idx, parse_res);
} else {
self.root = parse(&self.src);
}
}
None => {
self.root = parse(&self.src);
}
}
}
}
/// Provide highlighting categories for the given range of the source file.
pub fn highlight<F>(&self, range: Range<usize>, mut f: F)
where
@ -473,4 +504,21 @@ mod tests {
// Test removing everything.
test(TEST, 0 .. 21, "", "");
}
#[test]
fn test_source_file_edit_2() {
#[track_caller]
fn test(prev: &str, range: Range<usize>, with: &str, after: &str) {
let mut source = SourceFile::detached(prev);
let result = SourceFile::detached(after);
dbg!(Green::from(source.root.clone()));
source.edit(range, with);
assert_eq!(source.src, result.src);
assert_eq!(source.line_starts, result.line_starts);
dbg!(Green::from(source.root));
}
// Test inserting at the begining.
test("abc #f()[def] ghi", 10 .. 11, "xyz", "abc #f()[dxyzf] ghi");
}
}

View File

@ -127,6 +127,92 @@ impl GreenNode {
pub fn children(&self) -> &[Green] {
&self.children
}
/// The node's children, mutably.
pub fn children_mut(&mut self) -> &mut [Green] {
&mut self.children
}
/// The node's metadata.
pub fn data(&self) -> &GreenData {
&self.data
}
/// The node's type.
pub fn kind(&self) -> &NodeKind {
self.data().kind()
}
/// The node's length.
pub fn len(&self) -> usize {
self.data().len()
}
/// Find the parent of the deepest incremental-safe node and the index of
/// the found child.
pub fn incremental_parent(
&mut self,
span: Span,
) -> Option<(usize, &mut GreenNode, usize)> {
self.incremental_parent_internal(span, 0)
}
fn incremental_parent_internal(
&mut self,
span: Span,
mut offset: usize,
) -> Option<(usize, &mut GreenNode, usize)> {
let x = unsafe { &mut *(self as *mut _) };
for (i, child) in self.children.iter_mut().enumerate() {
match child {
Green::Token(n) => {
if offset < span.start {
// the token is strictly before the span
offset += n.len();
} else {
// the token is within or after the span; tokens are
// never safe, so we return.
return None;
}
}
Green::Node(n) => {
let end = n.len() + offset;
if offset < span.start && end < span.start {
// the node is strictly before the span
offset += n.len();
} else if span.start >= offset
&& span.start < end
&& span.end <= end
&& span.end > offset
{
// the node is within the span.
if n.kind().is_incremental_safe() {
let res =
Rc::make_mut(n).incremental_parent_internal(span, offset);
if res.is_none() {
return Some((i, x, offset));
}
} else {
return Rc::make_mut(n)
.incremental_parent_internal(span, offset);
}
} else {
// the node is overlapping or after after the span; nodes are
// never safe, so we return.
return None;
}
}
}
}
return None;
}
/// Replace one of the node's children.
pub fn replace_child(&mut self, index: usize, child: impl Into<Green>) {
self.children[index] = child.into();
}
}
impl From<GreenNode> for Green {
@ -653,6 +739,14 @@ impl NodeKind {
matches!(self, NodeKind::Error(_, _) | NodeKind::Unknown(_))
}
/// Whether it is safe to do incremental parsing on this node.
pub fn is_incremental_safe(&self) -> bool {
match self {
Self::Block | Self::Markup => true,
_ => false,
}
}
/// A human-readable name for the kind.
pub fn as_str(&self) -> &'static str {
match self {

View File

@ -125,6 +125,17 @@ impl Span {
*self = self.join(other)
}
/// Create a new span with n characters inserted inside of this span.
pub fn inserted(mut self, other: Self, n: usize) -> Self {
if !self.contains(other.start) || !self.contains(other.end) {
panic!();
}
let len_change = (n as isize - other.len() as isize) as usize;
self.end += len_change;
self
}
/// Test whether a position is within the span.
pub fn contains(&self, pos: usize) -> bool {
self.start <= pos && self.end >= pos