-
Notifications
You must be signed in to change notification settings - Fork 360
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
Make Groovy Parser correctly handle nested parenthesis #4718
Make Groovy Parser correctly handle nested parenthesis #4718
Conversation
8579420
to
378fe5b
Compare
rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyParserVisitor.java
Show resolved
Hide resolved
bc3b03b
to
fe98f02
Compare
5c22da6
to
fe378e2
Compare
rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyParserParentheseDiscoverer.java
Show resolved
Hide resolved
rewrite-groovy/src/test/java/org/openrewrite/groovy/GroovyParserParentheseDiscovererTest.java
Show resolved
Hide resolved
rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/MethodInvocationTest.java
Show resolved
Hide resolved
rewrite-java/src/main/java/org/openrewrite/java/tree/Space.java
Outdated
Show resolved
Hide resolved
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.
LGTM but since it's an extensive change let's let Tim, Sam or Knut take a look too!
rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyPrinter.java
Outdated
Show resolved
Hide resolved
rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/RangeTest.java
Outdated
Show resolved
Hide resolved
rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/CastTest.java
Outdated
Show resolved
Hide resolved
rewrite-groovy/src/test/java/org/openrewrite/groovy/GroovyParserParentheseDiscovererTest.java
Outdated
Show resolved
Hide resolved
rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyParserVisitor.java
Outdated
Show resolved
Hide resolved
rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyParserVisitor.java
Show resolved
Hide resolved
public static @Nullable Integer getInsideParenthesesLevel(MethodCallExpression node, String source, int cursor) { | ||
String sourceFromCursor = source.substring(cursor); |
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.
I'm somewhat surprised to see source
and cursor
as separate arguments here when they are merely used to do this substring. Would it make sense to change the method signature here to
public static @Nullable Integer getInsideParenthesesLevel(MethodCallExpression node, String source, int cursor) { | |
String sourceFromCursor = source.substring(cursor); | |
public static @Nullable Integer getInsideParenthesesLevel(MethodCallExpression node, String sourceFromCursor) { |
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.
I had that before, but reverted it back to current state. I think it more clear if the GroovyParserParentheseDiscoverer is a completely separate entity. So no former logic is needed to determine the wrapping. Just pass everything you have (thus the node, the source code of the entire file and the current place you are parsing right now) and let the function discover it all.
Having the cursor argument also prevents from misinterpretation to function, because you need to provide the starting point (that is the cursor) of parsing.
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.
I wonder if this can be simplified. For Python I had the same basic problem to solve, but I didn't have any "parentheses level" from the AST or similar. Additionally, in Python the parentheses for tuples and generator expressions are sometimes optional, which doesn't make things simpler.
Anyway, rather than counting the "parentheses levels" up front, I added something like a maybeParenthesized(node, delegate)
method which does something like this:
- Save cursor and use
whitespace()
to parse whitespace (and comments) at cursor into aSpace
- Check if the next character is a
(
- If not, reset cursor to saved cursor and return result from
delegate
and we are done - Otherwise push a lambda on a parentheses stack, which is a field on the parser visitor
- Call
maybeParenthesized()
recursively - Again, save cursor, call
whitespace()
and check if we are at a)
- If yes, pop lambda from parentheses stack and apply it to result, which will wrap it in a
J.Parentheses
- If not, reset cursor and return result
For a language like Java or Python, where we can expect the source files to be reasonably large, I would definitely do something like this, as the substringing and regexp matching won't be free in terms of performance. But for Groovy that would probably not be a big concern, as scripts tend to be small and few.
I will let you think a bit about this and don't want to force you into a different solution.
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.
For reference, here is the Python code in question. It isn't pretty, so in case you decide to give this approach a try, I would only look at the Python code if you get stuck, as you will probably find a cleaner solution for your parser: https://github.com/openrewrite/rewrite-python/blob/b42101961bbc9daf21d54fe803dd9eaec59c3184/rewrite/rewrite/python/_parser_visitor.py#L1950-L2025
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.
I second Knut's comments and like the approach he describes. I worry that these regexes will be slow and error prone.
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.
The one tricky situation I can think of are Java-style type casts, as the first token there is also a (
. So if the AST node is a Java-style type cast (assuming they can be distinguished from Groovy-style type casts in the AST), the logic could for example pop that last element from the stack and reset the cursor before calling the corresponding visit method (or do it in the method itself). There are also other alternative solutions.
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.
I agree, your implementation seems like a better idea @knutwannheden. I would rather first merge this and then do a second round with improvements (or better said, go for the new/better implementation). Currently we don't support nested parenthesis, which just breaks scripts that use them. Having this sub-ideal implementation makes them at least runnable, though with a performance loss.
Then I can start working on the better implementation
--
Edit: Talked with @knutwannheden about this, we concluded it's better to not go forward yet:
If we aren't in a hurry to get this done, it will just make the Git history messier to go through, when someone tries to find out why some change was made in the past.
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.
It isn't quite as easy as I made it out to be and I now remember the struggles I had with Python here. The difficulty arises because we don't know at what AST level the parentheses are. Here that parentheses level may come in handy.
rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyParserParentheseDiscoverer.java
Outdated
Show resolved
Hide resolved
…erParentheseDiscoverer.java Co-authored-by: Tim te Beek <[email protected]>
import java.util.regex.Matcher; | ||
import java.util.regex.Pattern; | ||
|
||
class GroovyParserParentheseDiscoverer { |
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.
I haven't reviewed in detail but at first glance it doesn't seem to be aware of comments so any parentheses in comments might throw it off
This is just my 2c, but the several regexes makes me feel like the solution here isn't quite right. For trivial source files found in the test cases, it works. But I fear that for actual source files that this would bring G-based parsing to a crawl. |
…-nested-parenthesis # Conflicts: # rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyParserVisitor.java # rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/MethodInvocationTest.java # rewrite-java/src/main/java/org/openrewrite/java/tree/Space.java
…-nested-parenthesis
What's changed?
Nested parentheses around method invocations are supported as well for the groovy parser.
What's your motivation?
Checklist