Skip to content

Commit d800712

Browse files
committed
Implement find pattern
1 parent 5522b6a commit d800712

File tree

6 files changed

+206
-10
lines changed

6 files changed

+206
-10
lines changed

CHANGELOG.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
New features:
44

55
* C/C++ extensions are now compiled using the system toolchain and executed natively instead of using GraalVM LLVM (Sulong). This leads to faster startup, no warmup, better compatibility, smaller distribution and faster installation for C/C++ extensions (#3118, @eregon).
6-
* Full suport for the Ruby 3.2 and Ruby 3.3 syntax by adopting the [Prism](https://github.com/ruby/prism) parser, which is about twice as fast as the old parser (#3117, #3038, #3039, @andrykonchin, @eregon).
7-
* Pattern matching is now fully supported, with the exception of Find pattern (`in [*, a, *]`) (#3332, #2683, @eregon, @razetime).
6+
* Full support for the Ruby 3.2 and Ruby 3.3 syntax by adopting the [Prism](https://github.com/ruby/prism) parser, which is about twice as fast as the old parser (#3117, #3038, #3039, @andrykonchin, @eregon).
7+
* Pattern matching is now fully supported (#3332, #2683, @eregon, @razetime).
88

99
Bug fixes:
1010

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,5 @@
11
fails:Pattern matching alternative pattern does not support variable binding
22
fails:Pattern matching Array pattern calls #deconstruct once for multiple patterns, caching the result
3-
fails:Pattern matching find pattern captures both preceding and following elements to the pattern
43
fails:Pattern matching warning when one-line form warns about pattern matching is experimental feature
5-
fails:Pattern matching find pattern can be nested
6-
fails:Pattern matching find pattern can be nested with an array pattern
7-
fails:Pattern matching find pattern can be nested within a hash pattern
8-
fails:Pattern matching find pattern can nest hash and array patterns
94
fails:Pattern matching variable pattern does not support using variable name (except _) several times
105
fails:Pattern matching Hash pattern raise SyntaxError when keys duplicate in pattern
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
/*
2+
* Copyright (c) 2024 Oracle and/or its affiliates. All rights reserved. This
3+
* code is released under a tri EPL/GPL/LGPL license. You can use it,
4+
* redistribute it and/or modify it under the terms of the:
5+
*
6+
* Eclipse Public License version 2.0, or
7+
* GNU General Public License version 2, or
8+
* GNU Lesser General Public License version 2.1.
9+
*/
10+
package org.truffleruby.core.array;
11+
12+
import com.oracle.truffle.api.dsl.Bind;
13+
import com.oracle.truffle.api.dsl.Cached;
14+
import com.oracle.truffle.api.dsl.ImportStatic;
15+
import com.oracle.truffle.api.frame.VirtualFrame;
16+
import com.oracle.truffle.api.library.CachedLibrary;
17+
import org.truffleruby.core.array.ArrayIndexNodes.ReadSliceNormalizedNode;
18+
import org.truffleruby.core.array.library.ArrayStoreLibrary;
19+
import org.truffleruby.language.RubyContextSourceNode;
20+
import org.truffleruby.language.RubyNode;
21+
22+
import com.oracle.truffle.api.dsl.NodeChild;
23+
import com.oracle.truffle.api.dsl.Specialization;
24+
import org.truffleruby.language.locals.WriteLocalNode;
25+
26+
@ImportStatic(ArrayGuards.class)
27+
@NodeChild(value = "valueNode", type = RubyNode.class)
28+
public abstract class ArrayFindPatternNode extends RubyContextSourceNode {
29+
30+
@Children final WriteLocalNode[] writeSlots;
31+
@Children final RubyNode[] conditions;
32+
@Child WriteLocalNode writeLeftSlot;
33+
@Child RubyNode leftCondition;
34+
@Child WriteLocalNode writeRightSlot;
35+
@Child RubyNode rightCondition;
36+
37+
protected ArrayFindPatternNode(
38+
WriteLocalNode[] writeSlots,
39+
RubyNode[] conditions,
40+
WriteLocalNode writeLeftSlot,
41+
RubyNode leftCondition,
42+
WriteLocalNode writeRightSlot,
43+
RubyNode rightCondition) {
44+
this.writeSlots = writeSlots;
45+
this.conditions = conditions;
46+
this.writeLeftSlot = writeLeftSlot;
47+
this.leftCondition = leftCondition;
48+
this.writeRightSlot = writeRightSlot;
49+
this.rightCondition = rightCondition;
50+
}
51+
52+
abstract RubyNode getValueNode();
53+
54+
@Specialization
55+
boolean findPattern(VirtualFrame frame, RubyArray array,
56+
@Bind("array.getStore()") Object store,
57+
@CachedLibrary(limit = "storageStrategyLimit()") ArrayStoreLibrary stores,
58+
@Cached ReadSliceNormalizedNode readSliceNormalizedNode) {
59+
int size = array.size;
60+
int limit = size - writeSlots.length;
61+
62+
outer: for (int start = 0; start <= limit; start++) {
63+
for (int i = 0; i < writeSlots.length; i++) {
64+
Object element = stores.read(store, start + i);
65+
writeSlots[i].assign(frame, element);
66+
if (!(boolean) conditions[i].execute(frame)) {
67+
continue outer;
68+
}
69+
}
70+
71+
writeLeftSlot.assign(frame, readSliceNormalizedNode.executeReadSlice(array, 0, start));
72+
if (!(boolean) leftCondition.execute(frame)) {
73+
continue;
74+
}
75+
76+
int from = start + writeSlots.length;
77+
writeRightSlot.assign(frame, readSliceNormalizedNode.executeReadSlice(array, from, size - from));
78+
if (!(boolean) rightCondition.execute(frame)) {
79+
continue;
80+
}
81+
82+
return true; // match found
83+
}
84+
85+
return false;
86+
}
87+
88+
89+
@Override
90+
public RubyNode cloneUninitialized() {
91+
var writeSlotsCopies = new WriteLocalNode[writeSlots.length];
92+
for (int i = 0; i < writeSlots.length; i++) {
93+
writeSlotsCopies[i] = (WriteLocalNode) writeSlots[i].cloneUninitialized();
94+
}
95+
96+
return ArrayFindPatternNodeGen.create(
97+
writeSlotsCopies,
98+
cloneUninitialized(conditions),
99+
(WriteLocalNode) writeLeftSlot.cloneUninitialized(),
100+
leftCondition.cloneUninitialized(),
101+
(WriteLocalNode) writeRightSlot.cloneUninitialized(),
102+
rightCondition.cloneUninitialized(),
103+
getValueNode()).copyFlags(this);
104+
}
105+
}

src/main/java/org/truffleruby/parser/YARPBaseTranslator.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import org.truffleruby.language.control.RaiseException;
2828
import org.truffleruby.language.control.SequenceNode;
2929
import org.truffleruby.language.dispatch.RubyCallNodeParameters;
30+
import org.truffleruby.language.literal.BooleanLiteralNode;
3031
import org.truffleruby.language.literal.NilLiteralNode;
3132

3233
import com.oracle.truffle.api.TruffleSafepoint;
@@ -97,6 +98,16 @@ protected final RubyNode translateNodeOrNil(Nodes.Node node) {
9798
return rubyNode;
9899
}
99100

101+
protected final RubyNode translateNodeOrTrue(Nodes.Node node) {
102+
final RubyNode rubyNode;
103+
if (node == null) {
104+
rubyNode = new BooleanLiteralNode(true);
105+
} else {
106+
rubyNode = node.accept(this);
107+
}
108+
return rubyNode;
109+
}
110+
100111
protected final RubyContextSourceNode createCallNode(RubyNode receiver, String method, RubyNode... arguments) {
101112
return createCallNode(true, receiver, method, arguments);
102113
}

src/main/java/org/truffleruby/parser/YARPPatternMatchingTranslator.java

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
import org.prism.Nodes;
1313
import org.truffleruby.core.array.ArrayDeconstructNodeGen;
14+
import org.truffleruby.core.array.ArrayFindPatternNodeGen;
1415
import org.truffleruby.core.array.ArrayIndexNodes;
1516
import org.truffleruby.core.array.ArrayPatternLengthCheckNodeGen;
1617
import org.truffleruby.core.array.ArraySliceNodeGen;
@@ -171,6 +172,91 @@ public RubyNode visitArrayPatternNode(Nodes.ArrayPatternNode node) {
171172
}
172173
}
173174

175+
// See the Ruby logic in https://github.com/ruby/ruby/commit/ddded1157a90d21cb54b9f07de35ab9b4cc472e1
176+
@Override
177+
public RubyNode visitFindPatternNode(Nodes.FindPatternNode node) {
178+
RubyNode condition;
179+
if (node.constant != null) { // Constant[*, a, *]
180+
condition = matchValue(node.constant);
181+
} else {
182+
condition = null;
183+
}
184+
185+
var middle = node.requireds;
186+
var middleSize = middle.length;
187+
188+
var deconstructed = ArrayDeconstructNodeGen.create(currentValueToMatch);
189+
190+
final int deconstructedSlot = environment.declareLocalTemp("pattern_deconstruct_find");
191+
final ReadLocalNode readTemp = environment.readNode(deconstructedSlot, node);
192+
final RubyNode assignTemp = readTemp.makeWriteNode(deconstructed);
193+
194+
RubyNode outerPrev = currentValueToMatch;
195+
currentValueToMatch = readTemp;
196+
try {
197+
RubyNode check = YARPTranslator.sequence(
198+
assignTemp,
199+
ArrayPatternLengthCheckNodeGen.create(middleSize, true, readTemp));
200+
if (condition == null) {
201+
condition = check;
202+
} else {
203+
condition = AndNodeGen.create(condition, check);
204+
}
205+
206+
var writeSlots = new WriteLocalNode[middleSize];
207+
var conditions = new RubyNode[middleSize];
208+
for (int i = 0; i < middleSize; i++) {
209+
int slot = environment.declareLocalTemp("pattern_find_middle");
210+
var readSlot = environment.readNode(slot, node);
211+
writeSlots[i] = readSlot.makeWriteNode(null);
212+
213+
RubyNode prev = currentValueToMatch;
214+
currentValueToMatch = readSlot;
215+
try {
216+
conditions[i] = middle[i].accept(this);
217+
} finally {
218+
currentValueToMatch = prev;
219+
}
220+
}
221+
222+
Nodes.SplatNode leftSplat = (Nodes.SplatNode) node.left;
223+
int leftSlot = environment.declareLocalTemp("pattern_find_left");
224+
var readLeftSlot = environment.readNode(leftSlot, node);
225+
var writeLeftSlot = readLeftSlot.makeWriteNode(null);
226+
RubyNode leftCondition;
227+
228+
RubyNode prev = currentValueToMatch;
229+
currentValueToMatch = readLeftSlot;
230+
try {
231+
leftCondition = translateNodeOrTrue(leftSplat.expression);
232+
} finally {
233+
currentValueToMatch = prev;
234+
}
235+
236+
Nodes.SplatNode rightSplat = (Nodes.SplatNode) node.right;
237+
int rightSlot = environment.declareLocalTemp("pattern_find_right");
238+
var readRightSlot = environment.readNode(rightSlot, node);
239+
var writeRightSlot = readRightSlot.makeWriteNode(null);
240+
RubyNode rightCondition;
241+
242+
prev = currentValueToMatch;
243+
currentValueToMatch = readRightSlot;
244+
try {
245+
rightCondition = translateNodeOrTrue(rightSplat.expression);
246+
} finally {
247+
currentValueToMatch = prev;
248+
}
249+
250+
var findPatternNode = ArrayFindPatternNodeGen.create(writeSlots, conditions, writeLeftSlot, leftCondition,
251+
writeRightSlot, rightCondition, currentValueToMatch);
252+
condition = AndNodeGen.create(condition, findPatternNode);
253+
} finally {
254+
currentValueToMatch = outerPrev;
255+
}
256+
257+
return assignPositionAndFlags(node, condition);
258+
}
259+
174260
@Override
175261
public RubyNode visitHashPatternNode(Nodes.HashPatternNode node) {
176262
RubyNode condition;

test/mri/excludes/TestPatternMatching.rb

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,8 @@
22
exclude :test_single_pattern_error_alternative_pattern, "Expected Exception(NoMatchingPatternError) was raised, but the message doesn't match."
33
exclude :test_single_pattern_error_array_pattern, "Expected Exception(NoMatchingPatternError) was raised, but the message doesn't match."
44
exclude :test_single_pattern_error_as_pattern, "Expected Exception(NoMatchingPatternError) was raised, but the message doesn't match."
5+
exclude :test_single_pattern_error_find_pattern, "Expected Exception(NoMatchingPatternError) was raised, but the message doesn't match."
56
exclude :test_single_pattern_error_guard_clause, "Expected Exception(NoMatchingPatternError) was raised, but the message doesn't match."
67
exclude :test_single_pattern_error_hash_pattern, "Expected Exception(NoMatchingPatternError) was raised, but the message doesn't match."
78
exclude :test_single_pattern_error_value_pattern, "Expected Exception(NoMatchingPatternError) was raised, but the message doesn't match."
8-
exclude :test_deconstruct_cache, "SyntaxError: /home/eregon/code/truffleruby-ws/truffleruby/test/mri/tests/ruby/test_pattern_matching.rb:1451: YARPBlockNodeTranslator does not know how to translate FindPatternNode at /home/eregon/code/truffleruby-ws/truffleruby/test/mri/tests/ruby/test_pattern_matching.rb:1451"
9-
exclude :test_find_pattern, "SyntaxError: /home/eregon/code/truffleruby-ws/truffleruby/test/mri/tests/ruby/test_pattern_matching.rb:806: YARPDefNodeTranslator does not know how to translate FindPatternNode at /home/eregon/code/truffleruby-ws/truffleruby/test/mri/tests/ruby/test_pattern_matching.rb:806"
10-
exclude :test_single_pattern_error_find_pattern, "SyntaxError: /home/eregon/code/truffleruby-ws/truffleruby/test/mri/tests/ruby/test_pattern_matching.rb:1623: YARPBlockNodeTranslator does not know how to translate FindPatternNode at /home/eregon/code/truffleruby-ws/truffleruby/test/mri/tests/ruby/test_pattern_matching.rb:1623"
9+
exclude :test_deconstruct_cache, "TypeError: deconstruct must return Array"

0 commit comments

Comments
 (0)