-
-
Notifications
You must be signed in to change notification settings - Fork 3k
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
Add Pattern helper to make pattern-matching on ASTs more concise #2362
Conversation
e05fab9
to
8af55f0
Compare
The Travis CI failure is nothing to do with the PR -- when testing under MRI 2.0.0, it failed to download and install the |
It's a very interesting idea. I've played around with it a little bit, and I found that it produces code that's even more clear than the usual To not lose anything in readability, we may want to allow named throw-away markers like So... great work! It comes in at a late stage, but it's conceivable that we could gradually move away from other extraction techniques and start using the |
@jonas054 Thanks for your encouragement. I will make it support wildcards like The biggest thing missing right now is a way to do an "and" -- to say "both of these patterns must match at the same place in the tree". |
OK, we now have "named wildcards", like |
A couple other adjustments which make
|
The new capture functionality (prepending
Also, it would be nice if |
Aha, I didn't think of The reason why I made it yield captures instead of returning them is that you can't use multiple assignment in a conditional. Any idea on how to make a conditional based on whether a pattern matches or not, but also get the captures into variables? Maybe you can either use |
OK, @jonas054, you can use I haven't changed the thing about yielding the captures; I'd like to hear your suggestion on how to do it better first. |
3e65f9a
to
5171ea7
Compare
Another added feature: unification is now performed on named wildcards. So a pattern like |
This is a diff on the previous commit, but I think you get the point. This worked for me: def yield_captures(captures)
captures = (1..captures).map { |n| "capture#{n}" }.join(',')
- "yield(#{captures}) if block_given?"
+ ['if block_given?',
+ " yield(#{captures})",
+ %(elsif !"#{captures}".empty?),
+ " return #{captures}",
+ 'end'].join("\n ")
end If a block is given, it works the same as now. If not, it returns the captures if the pattern contained anything to capture, otherwise it returns What do you think? Can you incorporate this functionality and add testing for it? |
@jonas054 OK, that seems like a good idea. |
0a55678
to
0193939
Compare
OK, you got it! |
48b2406
to
7d388a1
Compare
To me it makes more sense to get a single node back when only one is matched, e.g., key = Pattern.new('(pair $_ _)').match(pair) instead of key, = Pattern.new('(pair $_ _)').match(pair) |
Another update. Now |
Coverage decreased by 0.001%! Horrors! |
Here's a failing example that I believe shows a bug: context 'within a sequence ending with ellipsis' do
let(:pattern) { '(hash _ ...)' }
let(:ruby) { '{}' }
it_behaves_like :nonmatching
end It shouldn't match since the AST is |
I finally got to take a closer look at this PR and things are looking pretty good if you ask me. I'm not sure about the performance, but the core functionality is pretty sound. It's not necessary to get everything perfect the first time around, you know. :-) I'm guessing many good ideas will arise from actual usage of the patterns. |
Another small thingy - maybe this should be named |
Sure, we can do that. How about I'm busy now but can do this within a couple hours. |
Yeah, |
# '(send ...)' # matches (send ...) | ||
# '{send class}' # matches (send ...) or (class ...) | ||
# '({send class})' # matches (send) or (class) | ||
# '(send const)' # matches (send (const)) |
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.
Should be # matches (send (const ...))
.
2e9af6d
to
de8bcb7
Compare
Renamed to |
You should also rename the files to |
|
And |
Yeah - |
db50c20
to
994917b
Compare
Updated according to latest feedback. |
We haven't discussed |
Sorry, I thought it would be obvious from the code. Give it a pattern string, it defines a method which searches a node's descendants and yields all the descendants which match the pattern. |
OK. So it replaces |
I didn't know about |
Thanks to Jonas Arvidsson for many helpful suggestions on how to improve this code.
Added method documentation to |
Add Pattern helper to make pattern-matching on ASTs more concise
I'll merge this so we can get back to #2359 and evolve this more organically down the road. Thanks for the hard work, @alexdowad! |
Good idea @bbatsov! |
P.S. I plan to do a release around the end of the week (e.g. Friday). |
While working on #2359, I used a very complex conditional with lots of calls like
node.children[1].children[0].type == ...
and so on. @bbatsov commented on this:This PR is an attempt to make that kind of code very concise. It introduces a pattern-matching language for AST nodes. Pass a pattern string to
RuboCop::Pattern.new
, it compiles it into a method which runs just as fast as if it was written in straight Ruby.Here are some examples of what the pattern format looks like:
Alphabetic identifiers match node types,
()
destructures a node,{}
provides alternative patterns, any of which can match._
is a wildcard, symbol and integer literals match themselves,...
matches any remaining children up to the end of a node.Other features:
!
negates the next part of the pattern,$
captures whatever it matches (returning it from#match
if the overall match succeeds), and you can match against the last child of a node by preceding it with a...
.If you extend
Astrolabe::Node
with methods which have a_type?
suffix, the prefix will become usable inPattern
. For example:Thoughts?