Skip to content

Commit 24cdd13

Browse files
bjfisheregon
authored andcommitted
Implement interop iterable messages for ruby enumerables
1 parent c167083 commit 24cdd13

File tree

4 files changed

+139
-7
lines changed

4 files changed

+139
-7
lines changed

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/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)