Skip to content

Commit 14fe753

Browse files
committed
[GR-29405] Implement interop iterable messages for ruby enumerables
PullRequest: truffleruby/2426
2 parents 6f2ee09 + 89cef9d commit 14fe753

File tree

6 files changed

+205
-9
lines changed

6 files changed

+205
-9
lines changed

mx.truffleruby/suite.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
{
88
"name": "regex",
99
"subdir": True,
10-
"version": "bf2c742da013f65f715dad08f58cb6a8eec1eee4",
10+
"version": "eb1d9e5a58e016d3284e2df06ca65446b37b61c0",
1111
"urls": [
1212
{"url": "https://github.com/oracle/graal.git", "kind": "git"},
1313
{"url": "https://curio.ssw.jku.at/nexus/content/repositories/snapshots", "kind": "binary"},
@@ -16,7 +16,7 @@
1616
{
1717
"name": "sulong",
1818
"subdir": True,
19-
"version": "bf2c742da013f65f715dad08f58cb6a8eec1eee4",
19+
"version": "eb1d9e5a58e016d3284e2df06ca65446b37b61c0",
2020
"urls": [
2121
{"url": "https://github.com/oracle/graal.git", "kind": "git"},
2222
{"url": "https://curio.ssw.jku.at/nexus/content/repositories/snapshots", "kind": "binary"},

spec/truffle/interop/iterator_spec.rb

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,48 @@
1010
require_relative 'fixtures/classes'
1111

1212
describe "Truffle::Interop iterator messages" do
13-
it "allow to iterate an array" do
14-
obj = [1, 2]
13+
def should_have_iterator(obj, expected_values)
1514
Truffle::Interop.should.has_iterator?(obj)
15+
1616
iterator = Truffle::Interop.iterator(obj)
1717
Truffle::Interop.should.iterator?(iterator)
18-
19-
Truffle::Interop.has_iterator_next_element?(iterator).should == true
20-
Truffle::Interop.iterator_next_element(iterator).should == 1
21-
Truffle::Interop.has_iterator_next_element?(iterator).should == true
22-
Truffle::Interop.iterator_next_element(iterator).should == 2
18+
expected_values.each do |value|
19+
Truffle::Interop.has_iterator_next_element?(iterator).should == true
20+
Truffle::Interop.iterator_next_element(iterator).should == value
21+
end
2322
Truffle::Interop.has_iterator_next_element?(iterator).should == false
2423
-> { Truffle::Interop.iterator_next_element(iterator) }.should raise_error(StopIteration)
24+
25+
iterator = Truffle::Interop.iterator(obj)
26+
Truffle::Interop.should.iterator?(iterator)
27+
expected_values.each do |value|
28+
Truffle::Interop.iterator_next_element(iterator).should == value
29+
end
30+
-> { Truffle::Interop.iterator_next_element(iterator) }.should raise_error(StopIteration)
31+
end
32+
33+
it "allow to iterate an array" do
34+
should_have_iterator([1, 2], [1, 2])
35+
end
36+
37+
it "allows iterating a range" do
38+
should_have_iterator(1..3, [1, 2, 3])
39+
end
40+
41+
it "allows iterating a hash" do
42+
should_have_iterator({ 1 => "one", 2 => "two"}, [[1, "one"], [2, "two"]])
43+
end
44+
45+
it "allows iterating user defined classes" do
46+
cls = Class.new do
47+
include Enumerable
48+
def each
49+
yield 1
50+
yield 2
51+
end
52+
end
53+
54+
obj = cls.new
55+
should_have_iterator(obj, [1, 2])
2556
end
2657
end

src/main/java/org/truffleruby/core/CoreLibrary.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@ public class CoreLibrary {
175175
public final RubyClass typeErrorClass;
176176
public final RubyClass zeroDivisionErrorClass;
177177
public final RubyModule enumerableModule;
178+
public final RubyClass enumeratorClass;
178179
public final RubyModule errnoModule;
179180
public final RubyModule kernelModule;
180181
public final RubyModule truffleFFIModule;
@@ -194,6 +195,7 @@ public class CoreLibrary {
194195
public final RubyClass arityExceptionClass;
195196
public final RubyModule truffleFeatureLoaderModule;
196197
public final RubyModule truffleKernelOperationsModule;
198+
public final RubyModule truffleInteropOperationsModule;
197199
public final RubyModule truffleStringOperationsModule;
198200
public final RubyModule truffleRegexpOperationsModule;
199201
public final RubyModule truffleRandomOperationsModule;
@@ -411,6 +413,7 @@ public CoreLibrary(RubyContext context, RubyLanguage language) {
411413
defineClass("Data"); // Needed by Socket::Ifaddr and defined in core MRI
412414
dirClass = defineClass("Dir");
413415
encodingClass = defineClass("Encoding");
416+
enumeratorClass = defineClass("Enumerator");
414417
falseClass = defineClass("FalseClass");
415418
fiberClass = defineClass("Fiber");
416419
defineModule("FileTest");
@@ -516,6 +519,7 @@ public CoreLibrary(RubyContext context, RubyLanguage language) {
516519
defineModule(truffleModule, "System");
517520
truffleFeatureLoaderModule = defineModule(truffleModule, "FeatureLoader");
518521
truffleKernelOperationsModule = defineModule(truffleModule, "KernelOperations");
522+
truffleInteropOperationsModule = defineModule(truffleModule, "InteropOperations");
519523
defineModule(truffleModule, "Binding");
520524
defineModule(truffleModule, "POSIX");
521525
defineModule(truffleModule, "Readline");
@@ -997,6 +1001,7 @@ public boolean isTruffleBootMainMethod(SharedMethodInfo info) {
9971001
"/core/truffle/debug.rb",
9981002
"/core/truffle/encoding_operations.rb",
9991003
"/core/truffle/hash_operations.rb",
1004+
"/core/truffle/interop_operations.rb",
10001005
"/core/truffle/numeric_operations.rb",
10011006
"/core/truffle/proc_operations.rb",
10021007
"/core/truffle/range_operations.rb",

src/main/java/org/truffleruby/core/array/RubyArray.java

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

1212
import java.util.Set;
1313

14+
import com.oracle.truffle.api.CompilerDirectives;
15+
import com.oracle.truffle.api.interop.StopIterationException;
16+
import com.oracle.truffle.api.interop.TruffleObject;
17+
import com.oracle.truffle.api.interop.UnsupportedMessageException;
1418
import org.truffleruby.core.klass.RubyClass;
1519
import org.truffleruby.language.RubyDynamicObject;
1620
import org.truffleruby.language.RubyGuards;
@@ -128,6 +132,66 @@ public boolean isArrayElementInsertable(long index,
128132
}
129133
// endregion
130134

135+
// region Iterable Messages
136+
@ExportMessage
137+
public boolean hasIterator() {
138+
return true;
139+
}
140+
141+
/** Override {@link RubyDynamicObject#getIterator} to avoid the extra Fiber for RubyArray */
142+
@ExportMessage
143+
public ArrayIterator getIterator() {
144+
return new ArrayIterator(this);
145+
}
146+
147+
@ExportLibrary(InteropLibrary.class)
148+
static final class ArrayIterator implements TruffleObject {
149+
150+
final RubyArray array;
151+
private long currentItemIndex;
152+
153+
ArrayIterator(RubyArray array) {
154+
this.array = array;
155+
}
156+
157+
@ExportMessage
158+
boolean isIterator() {
159+
return true;
160+
}
161+
162+
@ExportMessage
163+
boolean hasIteratorNextElement(
164+
@CachedLibrary("this.array") InteropLibrary arrays) {
165+
try {
166+
return currentItemIndex < arrays.getArraySize(array);
167+
} catch (UnsupportedMessageException e) {
168+
throw CompilerDirectives.shouldNotReachHere(e);
169+
}
170+
}
171+
172+
@ExportMessage
173+
Object getIteratorNextElement(
174+
@CachedLibrary("this.array") InteropLibrary arrays,
175+
@Cached BranchProfile concurrentModification) throws StopIterationException {
176+
try {
177+
final long size = arrays.getArraySize(array);
178+
if (currentItemIndex >= size) {
179+
throw StopIterationException.create();
180+
}
181+
182+
final Object element = arrays.readArrayElement(array, currentItemIndex);
183+
currentItemIndex++;
184+
return element;
185+
} catch (InvalidArrayIndexException e) {
186+
concurrentModification.enter();
187+
throw StopIterationException.create();
188+
} catch (UnsupportedMessageException e) {
189+
throw CompilerDirectives.shouldNotReachHere(e);
190+
}
191+
}
192+
}
193+
// endregion
194+
131195
private boolean inBounds(long index) {
132196
return index >= 0 && index < size;
133197
}

src/main/java/org/truffleruby/language/RubyDynamicObject.java

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
*/
1010
package org.truffleruby.language;
1111

12+
import com.oracle.truffle.api.interop.StopIterationException;
1213
import org.truffleruby.Layouts;
1314
import org.truffleruby.RubyContext;
1415
import org.truffleruby.RubyLanguage;
@@ -30,6 +31,7 @@
3031
import org.truffleruby.language.library.RubyLibrary;
3132
import org.truffleruby.language.library.RubyStringLibrary;
3233
import org.truffleruby.language.methods.GetMethodObjectNode;
34+
import org.truffleruby.language.objects.IsANode;
3335
import org.truffleruby.language.objects.LogicalClassNode;
3436
import org.truffleruby.language.objects.WriteObjectFieldNode;
3537

@@ -285,6 +287,71 @@ public boolean isArrayElementRemovable(long index,
285287
}
286288
// endregion
287289

290+
// region Iterable Messages
291+
@ExportMessage
292+
public boolean hasIterator(
293+
@CachedContext(RubyLanguage.class) RubyContext context,
294+
@Exclusive @Cached IsANode isANode) {
295+
return isANode.executeIsA(this, context.getCoreLibrary().enumerableModule);
296+
}
297+
298+
@ExportMessage
299+
public Object getIterator(
300+
@CachedLibrary("this") InteropLibrary interopLibrary,
301+
@CachedContext(RubyLanguage.class) RubyContext context,
302+
@Exclusive @Cached DispatchNode dispatchNode) throws UnsupportedMessageException {
303+
if (!interopLibrary.hasIterator(this)) {
304+
throw UnsupportedMessageException.create();
305+
}
306+
return dispatchNode.call(context.getCoreLibrary().truffleInteropOperationsModule, "get_iterator", this);
307+
}
308+
309+
@ExportMessage
310+
public boolean isIterator(
311+
@CachedContext(RubyLanguage.class) RubyContext context,
312+
@Exclusive @Cached IsANode isANode) {
313+
return isANode.executeIsA(this, context.getCoreLibrary().enumeratorClass);
314+
}
315+
316+
@ExportMessage
317+
public boolean hasIteratorNextElement(
318+
@CachedLibrary("this") InteropLibrary interopLibrary,
319+
@CachedContext(RubyLanguage.class) RubyContext context,
320+
@Exclusive @Cached DispatchNode dispatchNode,
321+
@Exclusive @Cached BooleanCastNode booleanCastNode) throws UnsupportedMessageException {
322+
if (!interopLibrary.isIterator(this)) {
323+
throw UnsupportedMessageException.create();
324+
}
325+
return booleanCastNode.executeToBoolean(
326+
dispatchNode.call(
327+
context.getCoreLibrary().truffleInteropOperationsModule,
328+
"enumerator_has_next?",
329+
this));
330+
}
331+
332+
@ExportMessage
333+
public Object getIteratorNextElement(
334+
@CachedLibrary("this") InteropLibrary interopLibrary,
335+
@CachedContext(RubyLanguage.class) RubyContext context,
336+
@Exclusive @Cached DispatchNode dispatchNode,
337+
@Exclusive @Cached IsANode isANode,
338+
@Exclusive @Cached ConditionProfile stopIterationProfile)
339+
throws UnsupportedMessageException, StopIterationException {
340+
if (!interopLibrary.isIterator(this)) {
341+
throw UnsupportedMessageException.create();
342+
}
343+
try {
344+
return dispatchNode.call(this, "next");
345+
} catch (RaiseException e) {
346+
if (stopIterationProfile
347+
.profile(isANode.executeIsA(e.getException(), context.getCoreLibrary().stopIterationClass))) {
348+
throw StopIterationException.create(e);
349+
}
350+
throw e;
351+
}
352+
}
353+
// endregion
354+
288355
// region Pointer
289356
@ExportMessage
290357
public boolean isPointer(
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# frozen_string_literal: true
2+
3+
# Copyright (c) 2021, 2021 Oracle and/or its affiliates. All rights reserved. This
4+
# code is released under a tri EPL/GPL/LGPL license. You can use it,
5+
# redistribute it and/or modify it under the terms of the:
6+
#
7+
# Eclipse Public License version 2.0, or
8+
# GNU General Public License version 2, or
9+
# GNU Lesser General Public License version 2.1.
10+
11+
module Truffle
12+
module InteropOperations
13+
TO_ENUM = Kernel.instance_method(:to_enum)
14+
15+
def self.get_iterator(obj)
16+
TO_ENUM.bind_call(obj, :each)
17+
end
18+
19+
def self.enumerator_has_next?(enum)
20+
begin
21+
enum.peek
22+
true
23+
rescue StopIteration
24+
false
25+
end
26+
end
27+
28+
end
29+
end

0 commit comments

Comments
 (0)