-
Notifications
You must be signed in to change notification settings - Fork 0
Refactor parser AST into modules #89
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
7352989
Extract AST submodules
leynos fbd3bde
Refactor parenthesized parsing helpers
leynos e2a4998
Refactor AST helpers
leynos 316dde1
Refactor Index and Relation helpers
leynos eff0843
Improve AST helpers and tests
leynos 6ceec1f
Add transformer parameters test
leynos 685ceaf
Address review comments
leynos 90b1d51
Refactor delimiter content extraction
leynos File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,91 @@ | ||
| //! | ||
| //! AST wrapper for function declarations and definitions. | ||
| //! | ||
| //! Provides access to function names, parameters, return types and whether the | ||
| //! function is declared as `extern`. | ||
|
|
||
| use super::AstNode; | ||
| use crate::{DdlogLanguage, SyntaxKind}; | ||
|
|
||
| /// Typed wrapper for a function declaration or definition. | ||
| #[derive(Debug, Clone)] | ||
| pub struct Function { | ||
| pub(crate) syntax: rowan::SyntaxNode<DdlogLanguage>, | ||
| } | ||
|
|
||
| impl Function { | ||
| /// Name of the function if present. | ||
| #[must_use] | ||
| pub fn name(&self) -> Option<String> { | ||
| self.syntax | ||
| .children_with_tokens() | ||
| .skip_while(|e| !matches!(e.kind(), SyntaxKind::K_FUNCTION)) | ||
| .skip(1) | ||
| .find_map(|e| match e { | ||
| rowan::NodeOrToken::Token(t) if t.kind() == SyntaxKind::T_IDENT => { | ||
| Some(t.text().to_string()) | ||
| } | ||
| _ => None, | ||
| }) | ||
| } | ||
|
|
||
| /// Returns `true` if declared with the `extern` keyword. | ||
| #[must_use] | ||
| pub fn is_extern(&self) -> bool { | ||
| self.syntax | ||
| .children_with_tokens() | ||
| .any(|e| e.kind() == SyntaxKind::K_EXTERN) | ||
| } | ||
|
|
||
| /// Parameters as pairs of name and type. | ||
| #[must_use] | ||
| pub fn parameters(&self) -> Vec<(String, String)> { | ||
| let (pairs, errors) = | ||
| super::parse_utils::parse_name_type_pairs(self.syntax.children_with_tokens()); | ||
| if !errors.is_empty() { | ||
| log::debug!("Parsing errors in function parameters: {errors:?}"); | ||
| } | ||
| pairs | ||
| } | ||
|
|
||
| /// Return type text if specified. | ||
| #[must_use] | ||
| pub fn return_type(&self) -> Option<String> { | ||
| let mut iter = self.syntax.children_with_tokens().peekable(); | ||
| let mut depth = 0usize; | ||
| for e in &mut iter { | ||
| match e.kind() { | ||
| SyntaxKind::T_LPAREN => depth += 1, | ||
| SyntaxKind::T_RPAREN => { | ||
| depth -= 1; | ||
| if depth == 0 { | ||
| break; | ||
| } | ||
| } | ||
| _ => {} | ||
| } | ||
| } | ||
|
|
||
| super::parse_utils::parse_type_after_colon(&mut iter) | ||
| } | ||
| } | ||
|
|
||
| impl_ast_node!(Function); | ||
|
|
||
| #[cfg(test)] | ||
| mod tests { | ||
|
|
||
| use crate::parse; | ||
| #[expect(clippy::expect_used, reason = "Using expect for clearer test failures")] | ||
| #[test] | ||
| fn function_name() { | ||
| let parsed = parse("function foo() {}"); | ||
| let func = parsed | ||
| .root() | ||
| .functions() | ||
| .first() | ||
| .cloned() | ||
| .expect("function missing"); | ||
| assert_eq!(func.name(), Some("foo".into())); | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,101 @@ | ||
| //! | ||
| //! AST wrapper for `import` statements in `DDlog`. | ||
| //! | ||
| //! This module provides the `Import` struct for reading module paths such as | ||
| //! `foo::bar` and optional aliases introduced with the `as` keyword. It allows | ||
| //! consumers to navigate import statements without reimplementing token logic. | ||
|
|
||
| use super::AstNode; | ||
| use crate::{DdlogLanguage, SyntaxKind}; | ||
|
|
||
| /// Typed wrapper for an `import` statement. | ||
| #[derive(Debug, Clone)] | ||
| pub struct Import { | ||
| pub(crate) syntax: rowan::SyntaxNode<DdlogLanguage>, | ||
| } | ||
|
|
||
| impl Import { | ||
| /// The module path text as written in the source. | ||
| #[must_use] | ||
| pub fn path(&self) -> String { | ||
| self.syntax | ||
| .children_with_tokens() | ||
| .skip_while(|e| !matches!(e.kind(), SyntaxKind::K_IMPORT)) | ||
| .skip(1) | ||
| .take_while(|e| !matches!(e.kind(), SyntaxKind::K_AS)) | ||
| .filter_map(|e| match e { | ||
| rowan::NodeOrToken::Token(t) if t.kind() == SyntaxKind::T_IDENT => { | ||
| Some(t.text().to_string()) | ||
| } | ||
| rowan::NodeOrToken::Token(t) if t.kind() == SyntaxKind::T_COLON_COLON => { | ||
| Some("::".to_string()) | ||
| } | ||
| _ => None, | ||
| }) | ||
| .collect::<String>() | ||
| } | ||
|
|
||
| /// The alias assigned with `as`, if any. | ||
| #[must_use] | ||
| pub fn alias(&self) -> Option<String> { | ||
| self.syntax | ||
| .children_with_tokens() | ||
| .skip_while(|e| !matches!(e.kind(), SyntaxKind::K_AS)) | ||
| .skip(1) | ||
| .find_map(|e| match e { | ||
| rowan::NodeOrToken::Token(t) if t.kind() == SyntaxKind::T_IDENT => { | ||
| Some(t.text().to_string()) | ||
| } | ||
| _ => None, | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| impl_ast_node!(Import); | ||
|
|
||
| #[cfg(test)] | ||
| mod tests { | ||
|
|
||
| use crate::parse; | ||
| #[test] | ||
| fn parses_simple_import() { | ||
| let parsed = parse("import foo"); | ||
| #[expect(clippy::expect_used, reason = "Using expect for clearer test failures")] | ||
| let first = parsed | ||
| .root() | ||
| .imports() | ||
| .first() | ||
| .cloned() | ||
| .expect("import missing"); | ||
| assert_eq!(first.path(), "foo"); | ||
| assert!(first.alias().is_none()); | ||
| } | ||
|
|
||
| #[test] | ||
| fn parses_import_with_alias() { | ||
| let parsed = parse("import foo::bar as baz"); | ||
| #[expect(clippy::expect_used, reason = "Using expect for clearer test failures")] | ||
| let first = parsed | ||
| .root() | ||
| .imports() | ||
| .first() | ||
| .cloned() | ||
| .expect("import missing"); | ||
| assert_eq!(first.path(), "foo::bar"); | ||
| assert_eq!(first.alias(), Some("baz".to_string())); | ||
| } | ||
|
|
||
| #[test] | ||
| fn parses_complex_path() { | ||
| let parsed = parse("import std::collections::HashMap"); | ||
| #[expect(clippy::expect_used, reason = "Using expect for clearer test failures")] | ||
| let first = parsed | ||
| .root() | ||
| .imports() | ||
| .first() | ||
| .cloned() | ||
| .expect("import missing"); | ||
| assert_eq!(first.path(), "std::collections::HashMap"); | ||
| assert!(first.alias().is_none()); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| //! | ||
| //! AST wrapper for index declarations. | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| //! | ||
| //! This module provides a typed wrapper around `DDlog` index syntax nodes, | ||
| //! enabling structured access to the optional name, target relation and column | ||
| //! expressions used to build the index. | ||
|
|
||
| use super::AstNode; | ||
| use crate::{DdlogLanguage, SyntaxKind}; | ||
|
|
||
| /// Typed wrapper for an index declaration. | ||
| #[derive(Debug, Clone)] | ||
| pub struct Index { | ||
| pub(crate) syntax: rowan::SyntaxNode<DdlogLanguage>, | ||
| } | ||
|
|
||
| impl Index { | ||
| /// Name of the index if present. | ||
| #[must_use] | ||
| pub fn name(&self) -> Option<String> { | ||
| self.find_identifier_after_keyword(SyntaxKind::K_INDEX) | ||
| } | ||
|
|
||
| /// Target relation name. | ||
| #[must_use] | ||
| pub fn relation(&self) -> Option<String> { | ||
| self.find_identifier_after_keyword(SyntaxKind::K_ON) | ||
| } | ||
|
|
||
| fn find_identifier_after_keyword(&self, keyword: SyntaxKind) -> Option<String> { | ||
| let mut iter = self.syntax.children_with_tokens(); | ||
| if !super::skip_to_match(&mut iter, |k| k == keyword) { | ||
| return None; | ||
| } | ||
| super::take_first_ident(iter) | ||
| } | ||
|
|
||
| /// Column expressions included in the index. | ||
| #[must_use] | ||
| pub fn columns(&self) -> Vec<String> { | ||
|
leynos marked this conversation as resolved.
|
||
| crate::syntax_utils::parse_parenthesized_list(self.syntax.children_with_tokens()) | ||
| } | ||
| } | ||
|
|
||
| impl_ast_node!(Index); | ||
|
|
||
| #[cfg(test)] | ||
| mod tests { | ||
|
|
||
| use crate::parse; | ||
|
|
||
| #[expect(clippy::expect_used, reason = "Using expect for clearer test failures")] | ||
| #[test] | ||
| fn index_columns() { | ||
| let parsed = parse("index I on R(lower(name))"); | ||
| let idx = parsed | ||
| .root() | ||
| .indexes() | ||
| .first() | ||
| .cloned() | ||
| .expect("index missing"); | ||
| assert_eq!(idx.relation(), Some("R".into())); | ||
| assert_eq!(idx.columns(), vec!["lower(name)".to_string()]); | ||
| } | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Expand the module documentation.
The module documentation is too brief and violates the coding guidelines requiring comprehensive module-level comments that explain the module's purpose and utility.
Apply this diff to provide a complete module documentation:
📝 Committable suggestion
🤖 Prompt for AI Agents