diff options
Diffstat (limited to 'crates/ra_syntax/src')
-rw-r--r-- | crates/ra_syntax/src/algo.rs | 8 | ||||
-rw-r--r-- | crates/ra_syntax/src/ast/make.rs | 4 | ||||
-rw-r--r-- | crates/ra_syntax/src/lib.rs | 35 | ||||
-rw-r--r-- | crates/ra_syntax/src/parsing.rs | 32 | ||||
-rw-r--r-- | crates/ra_syntax/src/parsing/text_token_source.rs | 89 | ||||
-rw-r--r-- | crates/ra_syntax/src/tests.rs | 66 |
6 files changed, 187 insertions, 47 deletions
diff --git a/crates/ra_syntax/src/algo.rs b/crates/ra_syntax/src/algo.rs index 664894d1f..f7a885eb3 100644 --- a/crates/ra_syntax/src/algo.rs +++ b/crates/ra_syntax/src/algo.rs | |||
@@ -290,6 +290,11 @@ impl<'a> SyntaxRewriter<'a> { | |||
290 | N::cast(self.rewrite(node.syntax())).unwrap() | 290 | N::cast(self.rewrite(node.syntax())).unwrap() |
291 | } | 291 | } |
292 | 292 | ||
293 | /// Returns a node that encompasses all replacements to be done by this rewriter. | ||
294 | /// | ||
295 | /// Passing the returned node to `rewrite` will apply all replacements queued up in `self`. | ||
296 | /// | ||
297 | /// Returns `None` when there are no replacements. | ||
293 | pub fn rewrite_root(&self) -> Option<SyntaxNode> { | 298 | pub fn rewrite_root(&self) -> Option<SyntaxNode> { |
294 | assert!(self.f.is_none()); | 299 | assert!(self.f.is_none()); |
295 | self.replacements | 300 | self.replacements |
@@ -298,6 +303,9 @@ impl<'a> SyntaxRewriter<'a> { | |||
298 | SyntaxElement::Node(it) => it.clone(), | 303 | SyntaxElement::Node(it) => it.clone(), |
299 | SyntaxElement::Token(it) => it.parent(), | 304 | SyntaxElement::Token(it) => it.parent(), |
300 | }) | 305 | }) |
306 | // If we only have one replacement, we must return its parent node, since `rewrite` does | ||
307 | // not replace the node passed to it. | ||
308 | .map(|it| it.parent().unwrap_or(it)) | ||
301 | .fold1(|a, b| least_common_ancestor(&a, &b).unwrap()) | 309 | .fold1(|a, b| least_common_ancestor(&a, &b).unwrap()) |
302 | } | 310 | } |
303 | 311 | ||
diff --git a/crates/ra_syntax/src/ast/make.rs b/crates/ra_syntax/src/ast/make.rs index da0eb0926..192c610f1 100644 --- a/crates/ra_syntax/src/ast/make.rs +++ b/crates/ra_syntax/src/ast/make.rs | |||
@@ -75,6 +75,10 @@ pub fn record_field(name: ast::NameRef, expr: Option<ast::Expr>) -> ast::RecordF | |||
75 | } | 75 | } |
76 | } | 76 | } |
77 | 77 | ||
78 | pub fn record_field_def(name: ast::NameRef, ty: ast::TypeRef) -> ast::RecordFieldDef { | ||
79 | ast_from_text(&format!("struct S {{ {}: {}, }}", name, ty)) | ||
80 | } | ||
81 | |||
78 | pub fn block_expr( | 82 | pub fn block_expr( |
79 | stmts: impl IntoIterator<Item = ast::Stmt>, | 83 | stmts: impl IntoIterator<Item = ast::Stmt>, |
80 | tail_expr: Option<ast::Expr>, | 84 | tail_expr: Option<ast::Expr>, |
diff --git a/crates/ra_syntax/src/lib.rs b/crates/ra_syntax/src/lib.rs index a33a35cc1..9b7664576 100644 --- a/crates/ra_syntax/src/lib.rs +++ b/crates/ra_syntax/src/lib.rs | |||
@@ -168,6 +168,41 @@ impl SourceFile { | |||
168 | } | 168 | } |
169 | } | 169 | } |
170 | 170 | ||
171 | impl ast::Path { | ||
172 | /// Returns `text`, parsed as a path, but only if it has no errors. | ||
173 | pub fn parse(text: &str) -> Result<Self, ()> { | ||
174 | parsing::parse_text_fragment(text, ra_parser::FragmentKind::Path) | ||
175 | } | ||
176 | } | ||
177 | |||
178 | impl ast::Pat { | ||
179 | /// Returns `text`, parsed as a pattern, but only if it has no errors. | ||
180 | pub fn parse(text: &str) -> Result<Self, ()> { | ||
181 | parsing::parse_text_fragment(text, ra_parser::FragmentKind::Pattern) | ||
182 | } | ||
183 | } | ||
184 | |||
185 | impl ast::Expr { | ||
186 | /// Returns `text`, parsed as an expression, but only if it has no errors. | ||
187 | pub fn parse(text: &str) -> Result<Self, ()> { | ||
188 | parsing::parse_text_fragment(text, ra_parser::FragmentKind::Expr) | ||
189 | } | ||
190 | } | ||
191 | |||
192 | impl ast::ModuleItem { | ||
193 | /// Returns `text`, parsed as an item, but only if it has no errors. | ||
194 | pub fn parse(text: &str) -> Result<Self, ()> { | ||
195 | parsing::parse_text_fragment(text, ra_parser::FragmentKind::Item) | ||
196 | } | ||
197 | } | ||
198 | |||
199 | impl ast::TypeRef { | ||
200 | /// Returns `text`, parsed as an type reference, but only if it has no errors. | ||
201 | pub fn parse(text: &str) -> Result<Self, ()> { | ||
202 | parsing::parse_text_fragment(text, ra_parser::FragmentKind::Type) | ||
203 | } | ||
204 | } | ||
205 | |||
171 | /// Matches a `SyntaxNode` against an `ast` type. | 206 | /// Matches a `SyntaxNode` against an `ast` type. |
172 | /// | 207 | /// |
173 | /// # Example: | 208 | /// # Example: |
diff --git a/crates/ra_syntax/src/parsing.rs b/crates/ra_syntax/src/parsing.rs index e5eb80850..0ed3c20ef 100644 --- a/crates/ra_syntax/src/parsing.rs +++ b/crates/ra_syntax/src/parsing.rs | |||
@@ -6,13 +6,14 @@ mod text_token_source; | |||
6 | mod text_tree_sink; | 6 | mod text_tree_sink; |
7 | mod reparsing; | 7 | mod reparsing; |
8 | 8 | ||
9 | use crate::{syntax_node::GreenNode, SyntaxError}; | 9 | use crate::{syntax_node::GreenNode, AstNode, SyntaxError, SyntaxNode}; |
10 | use text_token_source::TextTokenSource; | 10 | use text_token_source::TextTokenSource; |
11 | use text_tree_sink::TextTreeSink; | 11 | use text_tree_sink::TextTreeSink; |
12 | 12 | ||
13 | pub use lexer::*; | 13 | pub use lexer::*; |
14 | 14 | ||
15 | pub(crate) use self::reparsing::incremental_reparse; | 15 | pub(crate) use self::reparsing::incremental_reparse; |
16 | use ra_parser::SyntaxKind; | ||
16 | 17 | ||
17 | pub(crate) fn parse_text(text: &str) -> (GreenNode, Vec<SyntaxError>) { | 18 | pub(crate) fn parse_text(text: &str) -> (GreenNode, Vec<SyntaxError>) { |
18 | let (tokens, lexer_errors) = tokenize(&text); | 19 | let (tokens, lexer_errors) = tokenize(&text); |
@@ -27,3 +28,32 @@ pub(crate) fn parse_text(text: &str) -> (GreenNode, Vec<SyntaxError>) { | |||
27 | 28 | ||
28 | (tree, parser_errors) | 29 | (tree, parser_errors) |
29 | } | 30 | } |
31 | |||
32 | /// Returns `text` parsed as a `T` provided there are no parse errors. | ||
33 | pub(crate) fn parse_text_fragment<T: AstNode>( | ||
34 | text: &str, | ||
35 | fragment_kind: ra_parser::FragmentKind, | ||
36 | ) -> Result<T, ()> { | ||
37 | let (tokens, lexer_errors) = tokenize(&text); | ||
38 | if !lexer_errors.is_empty() { | ||
39 | return Err(()); | ||
40 | } | ||
41 | |||
42 | let mut token_source = TextTokenSource::new(text, &tokens); | ||
43 | let mut tree_sink = TextTreeSink::new(text, &tokens); | ||
44 | |||
45 | // TextTreeSink assumes that there's at least some root node to which it can attach errors and | ||
46 | // tokens. We arbitrarily give it a SourceFile. | ||
47 | use ra_parser::TreeSink; | ||
48 | tree_sink.start_node(SyntaxKind::SOURCE_FILE); | ||
49 | ra_parser::parse_fragment(&mut token_source, &mut tree_sink, fragment_kind); | ||
50 | tree_sink.finish_node(); | ||
51 | |||
52 | let (tree, parser_errors) = tree_sink.finish(); | ||
53 | use ra_parser::TokenSource; | ||
54 | if !parser_errors.is_empty() || token_source.current().kind != SyntaxKind::EOF { | ||
55 | return Err(()); | ||
56 | } | ||
57 | |||
58 | SyntaxNode::new_root(tree).first_child().and_then(T::cast).ok_or(()) | ||
59 | } | ||
diff --git a/crates/ra_syntax/src/parsing/text_token_source.rs b/crates/ra_syntax/src/parsing/text_token_source.rs index 7ddc2c2c3..97aa3e795 100644 --- a/crates/ra_syntax/src/parsing/text_token_source.rs +++ b/crates/ra_syntax/src/parsing/text_token_source.rs | |||
@@ -1,40 +1,35 @@ | |||
1 | //! FIXME: write short doc here | 1 | //! See `TextTokenSource` docs. |
2 | 2 | ||
3 | use ra_parser::Token as PToken; | ||
4 | use ra_parser::TokenSource; | 3 | use ra_parser::TokenSource; |
5 | 4 | ||
6 | use crate::{parsing::lexer::Token, SyntaxKind::EOF, TextRange, TextSize}; | 5 | use crate::{parsing::lexer::Token, SyntaxKind::EOF, TextRange, TextSize}; |
7 | 6 | ||
7 | /// Implementation of `ra_parser::TokenSource` that takes tokens from source code text. | ||
8 | pub(crate) struct TextTokenSource<'t> { | 8 | pub(crate) struct TextTokenSource<'t> { |
9 | text: &'t str, | 9 | text: &'t str, |
10 | /// start position of each token(expect whitespace and comment) | 10 | /// token and its start position (non-whitespace/comment tokens) |
11 | /// ```non-rust | 11 | /// ```non-rust |
12 | /// struct Foo; | 12 | /// struct Foo; |
13 | /// ^------^--- | 13 | /// ^------^--^- |
14 | /// | | ^- | 14 | /// | | \________ |
15 | /// 0 7 10 | 15 | /// | \____ \ |
16 | /// | \ | | ||
17 | /// (struct, 0) (Foo, 7) (;, 10) | ||
16 | /// ``` | 18 | /// ``` |
17 | /// (token, start_offset): `[(struct, 0), (Foo, 7), (;, 10)]` | 19 | /// `[(struct, 0), (Foo, 7), (;, 10)]` |
18 | start_offsets: Vec<TextSize>, | 20 | token_offset_pairs: Vec<(Token, TextSize)>, |
19 | /// non-whitespace/comment tokens | ||
20 | /// ```non-rust | ||
21 | /// struct Foo {} | ||
22 | /// ^^^^^^ ^^^ ^^ | ||
23 | /// ``` | ||
24 | /// tokens: `[struct, Foo, {, }]` | ||
25 | tokens: Vec<Token>, | ||
26 | 21 | ||
27 | /// Current token and position | 22 | /// Current token and position |
28 | curr: (PToken, usize), | 23 | curr: (ra_parser::Token, usize), |
29 | } | 24 | } |
30 | 25 | ||
31 | impl<'t> TokenSource for TextTokenSource<'t> { | 26 | impl<'t> TokenSource for TextTokenSource<'t> { |
32 | fn current(&self) -> PToken { | 27 | fn current(&self) -> ra_parser::Token { |
33 | self.curr.0 | 28 | self.curr.0 |
34 | } | 29 | } |
35 | 30 | ||
36 | fn lookahead_nth(&self, n: usize) -> PToken { | 31 | fn lookahead_nth(&self, n: usize) -> ra_parser::Token { |
37 | mk_token(self.curr.1 + n, &self.start_offsets, &self.tokens) | 32 | mk_token(self.curr.1 + n, &self.token_offset_pairs) |
38 | } | 33 | } |
39 | 34 | ||
40 | fn bump(&mut self) { | 35 | fn bump(&mut self) { |
@@ -43,45 +38,47 @@ impl<'t> TokenSource for TextTokenSource<'t> { | |||
43 | } | 38 | } |
44 | 39 | ||
45 | let pos = self.curr.1 + 1; | 40 | let pos = self.curr.1 + 1; |
46 | self.curr = (mk_token(pos, &self.start_offsets, &self.tokens), pos); | 41 | self.curr = (mk_token(pos, &self.token_offset_pairs), pos); |
47 | } | 42 | } |
48 | 43 | ||
49 | fn is_keyword(&self, kw: &str) -> bool { | 44 | fn is_keyword(&self, kw: &str) -> bool { |
50 | let pos = self.curr.1; | 45 | self.token_offset_pairs |
51 | if pos >= self.tokens.len() { | 46 | .get(self.curr.1) |
52 | return false; | 47 | .map(|(token, offset)| &self.text[TextRange::at(*offset, token.len)] == kw) |
53 | } | 48 | .unwrap_or(false) |
54 | let range = TextRange::at(self.start_offsets[pos], self.tokens[pos].len); | ||
55 | self.text[range] == *kw | ||
56 | } | 49 | } |
57 | } | 50 | } |
58 | 51 | ||
59 | fn mk_token(pos: usize, start_offsets: &[TextSize], tokens: &[Token]) -> PToken { | 52 | fn mk_token(pos: usize, token_offset_pairs: &[(Token, TextSize)]) -> ra_parser::Token { |
60 | let kind = tokens.get(pos).map(|t| t.kind).unwrap_or(EOF); | 53 | let (kind, is_jointed_to_next) = match token_offset_pairs.get(pos) { |
61 | let is_jointed_to_next = if pos + 1 < start_offsets.len() { | 54 | Some((token, offset)) => ( |
62 | start_offsets[pos] + tokens[pos].len == start_offsets[pos + 1] | 55 | token.kind, |
63 | } else { | 56 | token_offset_pairs |
64 | false | 57 | .get(pos + 1) |
58 | .map(|(_, next_offset)| offset + token.len == *next_offset) | ||
59 | .unwrap_or(false), | ||
60 | ), | ||
61 | None => (EOF, false), | ||
65 | }; | 62 | }; |
66 | 63 | ra_parser::Token { kind, is_jointed_to_next } | |
67 | PToken { kind, is_jointed_to_next } | ||
68 | } | 64 | } |
69 | 65 | ||
70 | impl<'t> TextTokenSource<'t> { | 66 | impl<'t> TextTokenSource<'t> { |
71 | /// Generate input from tokens(expect comment and whitespace). | 67 | /// Generate input from tokens(expect comment and whitespace). |
72 | pub fn new(text: &'t str, raw_tokens: &'t [Token]) -> TextTokenSource<'t> { | 68 | pub fn new(text: &'t str, raw_tokens: &'t [Token]) -> TextTokenSource<'t> { |
73 | let mut tokens = Vec::new(); | 69 | let token_offset_pairs: Vec<_> = raw_tokens |
74 | let mut start_offsets = Vec::new(); | 70 | .iter() |
75 | let mut len = 0.into(); | 71 | .filter_map({ |
76 | for &token in raw_tokens.iter() { | 72 | let mut len = 0.into(); |
77 | if !token.kind.is_trivia() { | 73 | move |token| { |
78 | tokens.push(token); | 74 | let pair = if token.kind.is_trivia() { None } else { Some((*token, len)) }; |
79 | start_offsets.push(len); | 75 | len += token.len; |
80 | } | 76 | pair |
81 | len += token.len; | 77 | } |
82 | } | 78 | }) |
79 | .collect(); | ||
83 | 80 | ||
84 | let first = mk_token(0, &start_offsets, &tokens); | 81 | let first = mk_token(0, &token_offset_pairs); |
85 | TextTokenSource { text, start_offsets, tokens, curr: (first, 0) } | 82 | TextTokenSource { text, token_offset_pairs, curr: (first, 0) } |
86 | } | 83 | } |
87 | } | 84 | } |
diff --git a/crates/ra_syntax/src/tests.rs b/crates/ra_syntax/src/tests.rs index aee57db62..959967b79 100644 --- a/crates/ra_syntax/src/tests.rs +++ b/crates/ra_syntax/src/tests.rs | |||
@@ -55,6 +55,51 @@ fn parser_tests() { | |||
55 | } | 55 | } |
56 | 56 | ||
57 | #[test] | 57 | #[test] |
58 | fn expr_parser_tests() { | ||
59 | fragment_parser_dir_test( | ||
60 | &["parser/fragments/expr/ok"], | ||
61 | &["parser/fragments/expr/err"], | ||
62 | crate::ast::Expr::parse, | ||
63 | ); | ||
64 | } | ||
65 | |||
66 | #[test] | ||
67 | fn path_parser_tests() { | ||
68 | fragment_parser_dir_test( | ||
69 | &["parser/fragments/path/ok"], | ||
70 | &["parser/fragments/path/err"], | ||
71 | crate::ast::Path::parse, | ||
72 | ); | ||
73 | } | ||
74 | |||
75 | #[test] | ||
76 | fn pattern_parser_tests() { | ||
77 | fragment_parser_dir_test( | ||
78 | &["parser/fragments/pattern/ok"], | ||
79 | &["parser/fragments/pattern/err"], | ||
80 | crate::ast::Pat::parse, | ||
81 | ); | ||
82 | } | ||
83 | |||
84 | #[test] | ||
85 | fn item_parser_tests() { | ||
86 | fragment_parser_dir_test( | ||
87 | &["parser/fragments/item/ok"], | ||
88 | &["parser/fragments/item/err"], | ||
89 | crate::ast::ModuleItem::parse, | ||
90 | ); | ||
91 | } | ||
92 | |||
93 | #[test] | ||
94 | fn type_parser_tests() { | ||
95 | fragment_parser_dir_test( | ||
96 | &["parser/fragments/type/ok"], | ||
97 | &["parser/fragments/type/err"], | ||
98 | crate::ast::TypeRef::parse, | ||
99 | ); | ||
100 | } | ||
101 | |||
102 | #[test] | ||
58 | fn parser_fuzz_tests() { | 103 | fn parser_fuzz_tests() { |
59 | for (_, text) in collect_rust_files(&test_data_dir(), &["parser/fuzz-failures"]) { | 104 | for (_, text) in collect_rust_files(&test_data_dir(), &["parser/fuzz-failures"]) { |
60 | fuzz::check_parser(&text) | 105 | fuzz::check_parser(&text) |
@@ -134,3 +179,24 @@ fn dump_tokens_and_errors(tokens: &[Token], errors: &[SyntaxError], text: &str) | |||
134 | } | 179 | } |
135 | acc | 180 | acc |
136 | } | 181 | } |
182 | |||
183 | fn fragment_parser_dir_test<T, F>(ok_paths: &[&str], err_paths: &[&str], f: F) | ||
184 | where | ||
185 | T: crate::AstNode, | ||
186 | F: Fn(&str) -> Result<T, ()>, | ||
187 | { | ||
188 | dir_tests(&test_data_dir(), ok_paths, "rast", |text, path| { | ||
189 | if let Ok(node) = f(text) { | ||
190 | format!("{:#?}", crate::ast::AstNode::syntax(&node)) | ||
191 | } else { | ||
192 | panic!("Failed to parse '{:?}'", path); | ||
193 | } | ||
194 | }); | ||
195 | dir_tests(&test_data_dir(), err_paths, "rast", |text, path| { | ||
196 | if let Ok(_) = f(text) { | ||
197 | panic!("'{:?}' successfully parsed when it should have errored", path); | ||
198 | } else { | ||
199 | "ERROR\n".to_owned() | ||
200 | } | ||
201 | }); | ||
202 | } | ||