From bc99e95d7d954701c36142881302bb70e791bec1 Mon Sep 17 00:00:00 2001 From: David Lattimore Date: Fri, 19 Jun 2020 07:43:19 +1000 Subject: Implement APIs for parsing expressions, types, paths, patterns and items --- crates/ra_syntax/src/lib.rs | 35 ++++++++++++++++++++++ crates/ra_syntax/src/parsing.rs | 32 +++++++++++++++++++- crates/ra_syntax/src/tests.rs | 66 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 132 insertions(+), 1 deletion(-) (limited to 'crates/ra_syntax/src') 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 { } } +impl ast::Path { + /// Returns `text`, parsed as a path, but only if it has no errors. + pub fn parse(text: &str) -> Result { + parsing::parse_text_fragment(text, ra_parser::FragmentKind::Path) + } +} + +impl ast::Pat { + /// Returns `text`, parsed as a pattern, but only if it has no errors. + pub fn parse(text: &str) -> Result { + parsing::parse_text_fragment(text, ra_parser::FragmentKind::Pattern) + } +} + +impl ast::Expr { + /// Returns `text`, parsed as an expression, but only if it has no errors. + pub fn parse(text: &str) -> Result { + parsing::parse_text_fragment(text, ra_parser::FragmentKind::Expr) + } +} + +impl ast::ModuleItem { + /// Returns `text`, parsed as an item, but only if it has no errors. + pub fn parse(text: &str) -> Result { + parsing::parse_text_fragment(text, ra_parser::FragmentKind::Item) + } +} + +impl ast::TypeRef { + /// Returns `text`, parsed as an type reference, but only if it has no errors. + pub fn parse(text: &str) -> Result { + parsing::parse_text_fragment(text, ra_parser::FragmentKind::Type) + } +} + /// Matches a `SyntaxNode` against an `ast` type. /// /// # 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; mod text_tree_sink; mod reparsing; -use crate::{syntax_node::GreenNode, SyntaxError}; +use crate::{syntax_node::GreenNode, AstNode, SyntaxError, SyntaxNode}; use text_token_source::TextTokenSource; use text_tree_sink::TextTreeSink; pub use lexer::*; pub(crate) use self::reparsing::incremental_reparse; +use ra_parser::SyntaxKind; pub(crate) fn parse_text(text: &str) -> (GreenNode, Vec) { let (tokens, lexer_errors) = tokenize(&text); @@ -27,3 +28,32 @@ pub(crate) fn parse_text(text: &str) -> (GreenNode, Vec) { (tree, parser_errors) } + +/// Returns `text` parsed as a `T` provided there are no parse errors. +pub(crate) fn parse_text_fragment( + text: &str, + fragment_kind: ra_parser::FragmentKind, +) -> Result { + let (tokens, lexer_errors) = tokenize(&text); + if !lexer_errors.is_empty() { + return Err(()); + } + + let mut token_source = TextTokenSource::new(text, &tokens); + let mut tree_sink = TextTreeSink::new(text, &tokens); + + // TextTreeSink assumes that there's at least some root node to which it can attach errors and + // tokens. We arbitrarily give it a SourceFile. + use ra_parser::TreeSink; + tree_sink.start_node(SyntaxKind::SOURCE_FILE); + ra_parser::parse_fragment(&mut token_source, &mut tree_sink, fragment_kind); + tree_sink.finish_node(); + + let (tree, parser_errors) = tree_sink.finish(); + use ra_parser::TokenSource; + if !parser_errors.is_empty() || token_source.current().kind != SyntaxKind::EOF { + return Err(()); + } + + SyntaxNode::new_root(tree).first_child().and_then(T::cast).ok_or(()) +} 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 @@ -54,6 +54,51 @@ fn parser_tests() { }); } +#[test] +fn expr_parser_tests() { + fragment_parser_dir_test( + &["parser/fragments/expr/ok"], + &["parser/fragments/expr/err"], + crate::ast::Expr::parse, + ); +} + +#[test] +fn path_parser_tests() { + fragment_parser_dir_test( + &["parser/fragments/path/ok"], + &["parser/fragments/path/err"], + crate::ast::Path::parse, + ); +} + +#[test] +fn pattern_parser_tests() { + fragment_parser_dir_test( + &["parser/fragments/pattern/ok"], + &["parser/fragments/pattern/err"], + crate::ast::Pat::parse, + ); +} + +#[test] +fn item_parser_tests() { + fragment_parser_dir_test( + &["parser/fragments/item/ok"], + &["parser/fragments/item/err"], + crate::ast::ModuleItem::parse, + ); +} + +#[test] +fn type_parser_tests() { + fragment_parser_dir_test( + &["parser/fragments/type/ok"], + &["parser/fragments/type/err"], + crate::ast::TypeRef::parse, + ); +} + #[test] fn parser_fuzz_tests() { for (_, text) in collect_rust_files(&test_data_dir(), &["parser/fuzz-failures"]) { @@ -134,3 +179,24 @@ fn dump_tokens_and_errors(tokens: &[Token], errors: &[SyntaxError], text: &str) } acc } + +fn fragment_parser_dir_test(ok_paths: &[&str], err_paths: &[&str], f: F) +where + T: crate::AstNode, + F: Fn(&str) -> Result, +{ + dir_tests(&test_data_dir(), ok_paths, "rast", |text, path| { + if let Ok(node) = f(text) { + format!("{:#?}", crate::ast::AstNode::syntax(&node)) + } else { + panic!("Failed to parse '{:?}'", path); + } + }); + dir_tests(&test_data_dir(), err_paths, "rast", |text, path| { + if let Ok(_) = f(text) { + panic!("'{:?}' successfully parsed when it should have errored", path); + } else { + "ERROR\n".to_owned() + } + }); +} -- cgit v1.2.3