diff options
author | Aleksey Kladov <[email protected]> | 2019-02-20 12:47:32 +0000 |
---|---|---|
committer | Aleksey Kladov <[email protected]> | 2019-02-20 12:47:32 +0000 |
commit | 5222b8aba3b1c2c68706aacf6869423a8e4fe6d5 (patch) | |
tree | c8a6e999b8ac5f1f29bde86a2e0b3a53466bb369 /crates/ra_syntax/src/parsing/reparsing.rs | |
parent | 9d0cda4bc84350961f3884e75a1c20e62c449ede (diff) |
move all parsing related bits to a separate module
Diffstat (limited to 'crates/ra_syntax/src/parsing/reparsing.rs')
-rw-r--r-- | crates/ra_syntax/src/parsing/reparsing.rs | 370 |
1 files changed, 370 insertions, 0 deletions
diff --git a/crates/ra_syntax/src/parsing/reparsing.rs b/crates/ra_syntax/src/parsing/reparsing.rs new file mode 100644 index 000000000..994e7e212 --- /dev/null +++ b/crates/ra_syntax/src/parsing/reparsing.rs | |||
@@ -0,0 +1,370 @@ | |||
1 | use crate::{ | ||
2 | SyntaxKind::*, TextRange, TextUnit, | ||
3 | algo, | ||
4 | syntax_node::{GreenNode, SyntaxError, SyntaxNode}, | ||
5 | parsing::{ | ||
6 | grammar, | ||
7 | parser_impl, | ||
8 | builder::GreenBuilder, | ||
9 | parser_api::Parser, | ||
10 | lexer::{tokenize, Token}, | ||
11 | } | ||
12 | }; | ||
13 | |||
14 | use ra_text_edit::AtomTextEdit; | ||
15 | |||
16 | pub(crate) fn incremental_reparse( | ||
17 | node: &SyntaxNode, | ||
18 | edit: &AtomTextEdit, | ||
19 | errors: Vec<SyntaxError>, | ||
20 | ) -> Option<(GreenNode, Vec<SyntaxError>)> { | ||
21 | let (node, green, new_errors) = | ||
22 | reparse_leaf(node, &edit).or_else(|| reparse_block(node, &edit))?; | ||
23 | let green_root = node.replace_with(green); | ||
24 | let errors = merge_errors(errors, new_errors, node, edit); | ||
25 | Some((green_root, errors)) | ||
26 | } | ||
27 | |||
28 | fn reparse_leaf<'node>( | ||
29 | node: &'node SyntaxNode, | ||
30 | edit: &AtomTextEdit, | ||
31 | ) -> Option<(&'node SyntaxNode, GreenNode, Vec<SyntaxError>)> { | ||
32 | let node = algo::find_covering_node(node, edit.delete); | ||
33 | match node.kind() { | ||
34 | WHITESPACE | COMMENT | IDENT | STRING | RAW_STRING => { | ||
35 | let text = get_text_after_edit(node, &edit); | ||
36 | let tokens = tokenize(&text); | ||
37 | let token = match tokens[..] { | ||
38 | [token] if token.kind == node.kind() => token, | ||
39 | _ => return None, | ||
40 | }; | ||
41 | |||
42 | if token.kind == IDENT && is_contextual_kw(&text) { | ||
43 | return None; | ||
44 | } | ||
45 | |||
46 | let green = GreenNode::new_leaf(node.kind(), text.into()); | ||
47 | let new_errors = vec![]; | ||
48 | Some((node, green, new_errors)) | ||
49 | } | ||
50 | _ => None, | ||
51 | } | ||
52 | } | ||
53 | |||
54 | fn reparse_block<'node>( | ||
55 | node: &'node SyntaxNode, | ||
56 | edit: &AtomTextEdit, | ||
57 | ) -> Option<(&'node SyntaxNode, GreenNode, Vec<SyntaxError>)> { | ||
58 | let (node, reparser) = find_reparsable_node(node, edit.delete)?; | ||
59 | let text = get_text_after_edit(node, &edit); | ||
60 | let tokens = tokenize(&text); | ||
61 | if !is_balanced(&tokens) { | ||
62 | return None; | ||
63 | } | ||
64 | let (green, new_errors) = | ||
65 | parser_impl::parse_with(GreenBuilder::new(), &text, &tokens, reparser); | ||
66 | Some((node, green, new_errors)) | ||
67 | } | ||
68 | |||
69 | fn get_text_after_edit(node: &SyntaxNode, edit: &AtomTextEdit) -> String { | ||
70 | let edit = AtomTextEdit::replace(edit.delete - node.range().start(), edit.insert.clone()); | ||
71 | edit.apply(node.text().to_string()) | ||
72 | } | ||
73 | |||
74 | fn is_contextual_kw(text: &str) -> bool { | ||
75 | match text { | ||
76 | "auto" | "default" | "union" => true, | ||
77 | _ => false, | ||
78 | } | ||
79 | } | ||
80 | |||
81 | type ParseFn = fn(&mut Parser); | ||
82 | fn find_reparsable_node(node: &SyntaxNode, range: TextRange) -> Option<(&SyntaxNode, ParseFn)> { | ||
83 | let node = algo::find_covering_node(node, range); | ||
84 | return node.ancestors().filter_map(|node| reparser(node).map(|r| (node, r))).next(); | ||
85 | |||
86 | fn reparser(node: &SyntaxNode) -> Option<ParseFn> { | ||
87 | let res = match node.kind() { | ||
88 | BLOCK => grammar::block, | ||
89 | NAMED_FIELD_DEF_LIST => grammar::named_field_def_list, | ||
90 | NAMED_FIELD_LIST => grammar::named_field_list, | ||
91 | ENUM_VARIANT_LIST => grammar::enum_variant_list, | ||
92 | MATCH_ARM_LIST => grammar::match_arm_list, | ||
93 | USE_TREE_LIST => grammar::use_tree_list, | ||
94 | EXTERN_ITEM_LIST => grammar::extern_item_list, | ||
95 | TOKEN_TREE if node.first_child().unwrap().kind() == L_CURLY => grammar::token_tree, | ||
96 | ITEM_LIST => { | ||
97 | let parent = node.parent().unwrap(); | ||
98 | match parent.kind() { | ||
99 | IMPL_BLOCK => grammar::impl_item_list, | ||
100 | TRAIT_DEF => grammar::trait_item_list, | ||
101 | MODULE => grammar::mod_item_list, | ||
102 | _ => return None, | ||
103 | } | ||
104 | } | ||
105 | _ => return None, | ||
106 | }; | ||
107 | Some(res) | ||
108 | } | ||
109 | } | ||
110 | |||
111 | fn is_balanced(tokens: &[Token]) -> bool { | ||
112 | if tokens.is_empty() | ||
113 | || tokens.first().unwrap().kind != L_CURLY | ||
114 | || tokens.last().unwrap().kind != R_CURLY | ||
115 | { | ||
116 | return false; | ||
117 | } | ||
118 | let mut balance = 0usize; | ||
119 | for t in tokens.iter() { | ||
120 | match t.kind { | ||
121 | L_CURLY => balance += 1, | ||
122 | R_CURLY => { | ||
123 | balance = match balance.checked_sub(1) { | ||
124 | Some(b) => b, | ||
125 | None => return false, | ||
126 | } | ||
127 | } | ||
128 | _ => (), | ||
129 | } | ||
130 | } | ||
131 | balance == 0 | ||
132 | } | ||
133 | |||
134 | fn merge_errors( | ||
135 | old_errors: Vec<SyntaxError>, | ||
136 | new_errors: Vec<SyntaxError>, | ||
137 | old_node: &SyntaxNode, | ||
138 | edit: &AtomTextEdit, | ||
139 | ) -> Vec<SyntaxError> { | ||
140 | let mut res = Vec::new(); | ||
141 | for e in old_errors { | ||
142 | if e.offset() <= old_node.range().start() { | ||
143 | res.push(e) | ||
144 | } else if e.offset() >= old_node.range().end() { | ||
145 | res.push(e.add_offset(TextUnit::of_str(&edit.insert) - edit.delete.len())); | ||
146 | } | ||
147 | } | ||
148 | for e in new_errors { | ||
149 | res.push(e.add_offset(old_node.range().start())); | ||
150 | } | ||
151 | res | ||
152 | } | ||
153 | |||
154 | #[cfg(test)] | ||
155 | mod tests { | ||
156 | use test_utils::{extract_range, assert_eq_text}; | ||
157 | |||
158 | use crate::{SourceFile, AstNode, utils::dump_tree}; | ||
159 | use super::*; | ||
160 | |||
161 | fn do_check<F>(before: &str, replace_with: &str, reparser: F) | ||
162 | where | ||
163 | for<'a> F: Fn( | ||
164 | &'a SyntaxNode, | ||
165 | &AtomTextEdit, | ||
166 | ) -> Option<(&'a SyntaxNode, GreenNode, Vec<SyntaxError>)>, | ||
167 | { | ||
168 | let (range, before) = extract_range(before); | ||
169 | let edit = AtomTextEdit::replace(range, replace_with.to_owned()); | ||
170 | let after = edit.apply(before.clone()); | ||
171 | |||
172 | let fully_reparsed = SourceFile::parse(&after); | ||
173 | let incrementally_reparsed = { | ||
174 | let f = SourceFile::parse(&before); | ||
175 | let edit = AtomTextEdit { delete: range, insert: replace_with.to_string() }; | ||
176 | let (node, green, new_errors) = | ||
177 | reparser(f.syntax(), &edit).expect("cannot incrementally reparse"); | ||
178 | let green_root = node.replace_with(green); | ||
179 | let errors = super::merge_errors(f.errors(), new_errors, node, &edit); | ||
180 | SourceFile::new(green_root, errors) | ||
181 | }; | ||
182 | |||
183 | assert_eq_text!( | ||
184 | &dump_tree(fully_reparsed.syntax()), | ||
185 | &dump_tree(incrementally_reparsed.syntax()), | ||
186 | ) | ||
187 | } | ||
188 | |||
189 | #[test] | ||
190 | fn reparse_block_tests() { | ||
191 | let do_check = |before, replace_to| do_check(before, replace_to, reparse_block); | ||
192 | |||
193 | do_check( | ||
194 | r" | ||
195 | fn foo() { | ||
196 | let x = foo + <|>bar<|> | ||
197 | } | ||
198 | ", | ||
199 | "baz", | ||
200 | ); | ||
201 | do_check( | ||
202 | r" | ||
203 | fn foo() { | ||
204 | let x = foo<|> + bar<|> | ||
205 | } | ||
206 | ", | ||
207 | "baz", | ||
208 | ); | ||
209 | do_check( | ||
210 | r" | ||
211 | struct Foo { | ||
212 | f: foo<|><|> | ||
213 | } | ||
214 | ", | ||
215 | ",\n g: (),", | ||
216 | ); | ||
217 | do_check( | ||
218 | r" | ||
219 | fn foo { | ||
220 | let; | ||
221 | 1 + 1; | ||
222 | <|>92<|>; | ||
223 | } | ||
224 | ", | ||
225 | "62", | ||
226 | ); | ||
227 | do_check( | ||
228 | r" | ||
229 | mod foo { | ||
230 | fn <|><|> | ||
231 | } | ||
232 | ", | ||
233 | "bar", | ||
234 | ); | ||
235 | do_check( | ||
236 | r" | ||
237 | trait Foo { | ||
238 | type <|>Foo<|>; | ||
239 | } | ||
240 | ", | ||
241 | "Output", | ||
242 | ); | ||
243 | do_check( | ||
244 | r" | ||
245 | impl IntoIterator<Item=i32> for Foo { | ||
246 | f<|><|> | ||
247 | } | ||
248 | ", | ||
249 | "n next(", | ||
250 | ); | ||
251 | do_check( | ||
252 | r" | ||
253 | use a::b::{foo,<|>,bar<|>}; | ||
254 | ", | ||
255 | "baz", | ||
256 | ); | ||
257 | do_check( | ||
258 | r" | ||
259 | pub enum A { | ||
260 | Foo<|><|> | ||
261 | } | ||
262 | ", | ||
263 | "\nBar;\n", | ||
264 | ); | ||
265 | do_check( | ||
266 | r" | ||
267 | foo!{a, b<|><|> d} | ||
268 | ", | ||
269 | ", c[3]", | ||
270 | ); | ||
271 | do_check( | ||
272 | r" | ||
273 | fn foo() { | ||
274 | vec![<|><|>] | ||
275 | } | ||
276 | ", | ||
277 | "123", | ||
278 | ); | ||
279 | do_check( | ||
280 | r" | ||
281 | extern { | ||
282 | fn<|>;<|> | ||
283 | } | ||
284 | ", | ||
285 | " exit(code: c_int)", | ||
286 | ); | ||
287 | } | ||
288 | |||
289 | #[test] | ||
290 | fn reparse_leaf_tests() { | ||
291 | let do_check = |before, replace_to| do_check(before, replace_to, reparse_leaf); | ||
292 | |||
293 | do_check( | ||
294 | r"<|><|> | ||
295 | fn foo() -> i32 { 1 } | ||
296 | ", | ||
297 | "\n\n\n \n", | ||
298 | ); | ||
299 | do_check( | ||
300 | r" | ||
301 | fn foo() -> <|><|> {} | ||
302 | ", | ||
303 | " \n", | ||
304 | ); | ||
305 | do_check( | ||
306 | r" | ||
307 | fn <|>foo<|>() -> i32 { 1 } | ||
308 | ", | ||
309 | "bar", | ||
310 | ); | ||
311 | do_check( | ||
312 | r" | ||
313 | fn foo<|><|>foo() { } | ||
314 | ", | ||
315 | "bar", | ||
316 | ); | ||
317 | do_check( | ||
318 | r" | ||
319 | fn foo /* <|><|> */ () {} | ||
320 | ", | ||
321 | "some comment", | ||
322 | ); | ||
323 | do_check( | ||
324 | r" | ||
325 | fn baz <|><|> () {} | ||
326 | ", | ||
327 | " \t\t\n\n", | ||
328 | ); | ||
329 | do_check( | ||
330 | r" | ||
331 | fn baz <|><|> () {} | ||
332 | ", | ||
333 | " \t\t\n\n", | ||
334 | ); | ||
335 | do_check( | ||
336 | r" | ||
337 | /// foo <|><|>omment | ||
338 | mod { } | ||
339 | ", | ||
340 | "c", | ||
341 | ); | ||
342 | do_check( | ||
343 | r#" | ||
344 | fn -> &str { "Hello<|><|>" } | ||
345 | "#, | ||
346 | ", world", | ||
347 | ); | ||
348 | do_check( | ||
349 | r#" | ||
350 | fn -> &str { // "Hello<|><|>" | ||
351 | "#, | ||
352 | ", world", | ||
353 | ); | ||
354 | do_check( | ||
355 | r##" | ||
356 | fn -> &str { r#"Hello<|><|>"# | ||
357 | "##, | ||
358 | ", world", | ||
359 | ); | ||
360 | do_check( | ||
361 | r" | ||
362 | #[derive(<|>Copy<|>)] | ||
363 | enum Foo { | ||
364 | |||
365 | } | ||
366 | ", | ||
367 | "Clone", | ||
368 | ); | ||
369 | } | ||
370 | } | ||