From c3e5987c433cdd0ea95a6b1057b442f4f0fe1ffc Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Sat, 25 Aug 2018 14:45:17 +0300 Subject: incremental reparse --- crates/libsyntax2/src/grammar/mod.rs | 6 ++--- crates/libsyntax2/src/lib.rs | 41 ++++++++++++++++++++++++++++---- crates/libsyntax2/src/parser_impl/mod.rs | 9 ++++--- crates/libsyntax2/src/yellow/syntax.rs | 23 +++++++++++++++++- crates/libsyntax2/tests/test/main.rs | 38 +++++++++++++++++++++++++++-- 5 files changed, 104 insertions(+), 13 deletions(-) (limited to 'crates/libsyntax2') diff --git a/crates/libsyntax2/src/grammar/mod.rs b/crates/libsyntax2/src/grammar/mod.rs index 46ba8a89a..496d28349 100644 --- a/crates/libsyntax2/src/grammar/mod.rs +++ b/crates/libsyntax2/src/grammar/mod.rs @@ -40,11 +40,11 @@ pub(crate) use self::{ items::named_field_def_list, }; -pub(crate) fn file(p: &mut Parser) { - let file = p.start(); +pub(crate) fn root(p: &mut Parser) { + let m = p.start(); p.eat(SHEBANG); items::mod_contents(p, false); - file.complete(p, ROOT); + m.complete(p, ROOT); } diff --git a/crates/libsyntax2/src/lib.rs b/crates/libsyntax2/src/lib.rs index bb060cbae..d43d26c4c 100644 --- a/crates/libsyntax2/src/lib.rs +++ b/crates/libsyntax2/src/lib.rs @@ -70,13 +70,15 @@ impl File { } pub fn parse(text: &str) -> File { let tokens = tokenize(&text); - let (green, errors) = parser_impl::parse::(text, &tokens); + let (green, errors) = parser_impl::parse_with::( + text, &tokens, grammar::root, + ); File::new(green, errors) } pub fn reparse(&self, edit: &AtomEdit) -> File { self.incremental_reparse(edit).unwrap_or_else(|| self.full_reparse(edit)) } - fn incremental_reparse(&self, edit: &AtomEdit) -> Option { + pub fn incremental_reparse(&self, edit: &AtomEdit) -> Option { let (node, reparser) = find_reparsable_node(self.syntax(), edit.delete)?; let text = replace_range( node.text(), @@ -87,7 +89,12 @@ impl File { if !is_balanced(&tokens) { return None; } - None + let (green, new_errors) = parser_impl::parse_with::( + &text, &tokens, reparser, + ); + let green_root = node.replace_with(green); + let errors = merge_errors(self.errors(), new_errors, edit, node.range().start()); + Some(File::new(green_root, errors)) } fn full_reparse(&self, edit: &AtomEdit) -> File { let text = replace_range(self.syntax().text(), edit.delete, &edit.insert); @@ -173,7 +180,7 @@ fn find_reparsable_node(node: SyntaxNodeRef, range: TextRange) -> Option<(Syntax } } -fn replace_range(mut text: String, range: TextRange, replace_with: &str) -> String { +pub /*(meh)*/ fn replace_range(mut text: String, range: TextRange, replace_with: &str) -> String { let start = u32::from(range.start()) as usize; let end = u32::from(range.end()) as usize; text.replace_range(start..end, replace_with); @@ -199,3 +206,29 @@ fn is_balanced(tokens: &[Token]) -> bool { } balance == 0 } + +fn merge_errors( + old_errors: Vec, + new_errors: Vec, + edit: &AtomEdit, + node_offset: TextUnit, +) -> Vec { + let mut res = Vec::new(); + for e in old_errors { + if e.offset < edit.delete.start() { + res.push(e) + } else if e.offset > edit.delete.end() { + res.push(SyntaxError { + msg: e.msg, + offset: e.offset + TextUnit::of_str(&edit.insert) - edit.delete.len(), + }) + } + } + for e in new_errors { + res.push(SyntaxError { + msg: e.msg, + offset: e.offset + node_offset, + }) + } + res +} diff --git a/crates/libsyntax2/src/parser_impl/mod.rs b/crates/libsyntax2/src/parser_impl/mod.rs index 14cceced5..f60ef80f0 100644 --- a/crates/libsyntax2/src/parser_impl/mod.rs +++ b/crates/libsyntax2/src/parser_impl/mod.rs @@ -2,7 +2,6 @@ mod event; mod input; use { - grammar, lexer::Token, parser_api::Parser, parser_impl::{ @@ -27,12 +26,16 @@ pub(crate) trait Sink<'a> { } /// Parse a sequence of tokens into the representative node tree -pub(crate) fn parse<'a, S: Sink<'a>>(text: &'a str, tokens: &[Token]) -> S::Tree { +pub(crate) fn parse_with<'a, S: Sink<'a>>( + text: &'a str, + tokens: &[Token], + parser: fn(&mut Parser), +) -> S::Tree { let events = { let input = input::ParserInput::new(text, tokens); let parser_impl = ParserImpl::new(&input); let mut parser_api = Parser(parser_impl); - grammar::file(&mut parser_api); + parser(&mut parser_api); parser_api.0.into_events() }; let mut sink = S::new(text); diff --git a/crates/libsyntax2/src/yellow/syntax.rs b/crates/libsyntax2/src/yellow/syntax.rs index 8f1b1d79a..0045598d4 100644 --- a/crates/libsyntax2/src/yellow/syntax.rs +++ b/crates/libsyntax2/src/yellow/syntax.rs @@ -3,7 +3,7 @@ use std::{fmt, sync::Arc}; use smol_str::SmolStr; use { - yellow::{RedNode, TreeRoot, SyntaxRoot, RedPtr, RefRoot, OwnedRoot}, + yellow::{GreenNode, RedNode, TreeRoot, SyntaxRoot, RedPtr, RefRoot, OwnedRoot}, SyntaxKind::{self, *}, TextRange, TextUnit, }; @@ -141,6 +141,27 @@ impl SyntaxNode { self.red().green().leaf_text() } + pub(crate) fn replace_with(&self, green: GreenNode) -> GreenNode { + assert_eq!(self.kind(), green.kind()); + match self.parent() { + None => green, + Some(parent) => { + let children: Vec<_> = parent.children().map(|child| { + if child == *self { + green.clone() + } else { + child.red().green().clone() + } + }).collect(); + let new_parent = GreenNode::new_branch( + parent.kind(), + children.into_boxed_slice(), + ); + parent.replace_with(new_parent) + }, + } + } + fn red(&self) -> &RedNode { unsafe { self.red.get(&self.root) } } diff --git a/crates/libsyntax2/tests/test/main.rs b/crates/libsyntax2/tests/test/main.rs index cb8a52c98..e7ae4d601 100644 --- a/crates/libsyntax2/tests/test/main.rs +++ b/crates/libsyntax2/tests/test/main.rs @@ -9,7 +9,11 @@ use std::{ fmt::Write, }; -use libsyntax2::File; +use test_utils::extract_range; +use libsyntax2::{ + File, AtomEdit, + utils::dump_tree, +}; #[test] fn lexer_tests() { @@ -23,10 +27,40 @@ fn lexer_tests() { fn parser_tests() { dir_tests(&["parser/inline", "parser/ok", "parser/err"], |text| { let file = File::parse(text); - libsyntax2::utils::dump_tree(file.syntax()) + dump_tree(file.syntax()) }) } +#[test] +fn reparse_test() { + fn do_check(before: &str, replace_with: &str) { + let (range, before) = extract_range(before); + let after = libsyntax2::replace_range(before.clone(), range, replace_with); + + let fully_reparsed = File::parse(&after); + let incrementally_reparsed = { + let f = File::parse(&before); + let edit = AtomEdit { delete: range, insert: replace_with.to_string() }; + f.incremental_reparse(&edit).unwrap() + }; + assert_eq_text!( + &dump_tree(fully_reparsed.syntax()), + &dump_tree(incrementally_reparsed.syntax()), + ) + } + + do_check(r" +fn foo() { + let x = foo + <|>bar<|> +} +", "baz"); + do_check(r" +struct Foo { + f: foo<|><|> +} +", ",\n g: (),"); +} + /// Read file and normalize newlines. /// -- cgit v1.2.3