Skip to content

Commit

Permalink
Showing 25 changed files with 333 additions and 41 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -66,6 +66,7 @@ target
test/prawn
test/rails
test/testapp/testapp
test/truffle/*.methods
tool/nailgun/Makefile
tool/nailgun/config.log
tool/nailgun/config.status
4 changes: 4 additions & 0 deletions core/src/main/java/org/jruby/RubyIO.java
Original file line number Diff line number Diff line change
@@ -3446,6 +3446,10 @@ public static IRubyObject ioOpen(ThreadContext context, IRubyObject filename, IR
int perm;
IRubyObject cmd;

if ((filename instanceof RubyString) && ((RubyString) filename).isEmpty()) {
throw context.getRuntime().newErrnoENOENTError();
}

Object pm = EncodingUtils.vmodeVperm(vmode, vperm);

IOEncodable convconfig = new IOEncodable.ConvConfig();
5 changes: 5 additions & 0 deletions core/src/main/java/org/jruby/RubyThread.java
Original file line number Diff line number Diff line change
@@ -1258,7 +1258,12 @@ public <Data, Return> Return executeTask(ThreadContext context, Data data, Task<
try {
this.unblockFunc = task;
this.unblockArg = data;

enterSleep();

// check for interrupt before going into blocking call
context.pollThreadEvents();

return task.run(context, data);
} finally {
exitSleep();
Original file line number Diff line number Diff line change
@@ -52,25 +52,16 @@ public boolean doLongFixnum(long value) {
return true;
}

@Specialization
public boolean doBignum(RubyBignum value) {
return true;
}

@Specialization
public boolean doFloat(double value) {
return true;
}

@Specialization(guards = "neitherNilNorFalse")
@Specialization(guards = "!isNil")
public boolean doBasicObject(RubyBasicObject object) {
return true;
}

protected boolean neitherNilNorFalse(RubyBasicObject object) {
return object != getContext().getCoreLibrary().getNilObject();
}

@Override
public abstract boolean executeBoolean(VirtualFrame frame);

Original file line number Diff line number Diff line change
@@ -12,10 +12,13 @@
import java.util.*;

import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.ExactMath;
import com.oracle.truffle.api.source.*;
import com.oracle.truffle.api.dsl.*;
import com.oracle.truffle.api.frame.VirtualFrame;
import org.jruby.runtime.Visibility;
import org.jruby.truffle.nodes.RubyNode;
import org.jruby.truffle.nodes.cast.BooleanCastNodeFactory;
import org.jruby.truffle.nodes.dispatch.Dispatch;
import org.jruby.truffle.nodes.dispatch.DispatchHeadNode;
import org.jruby.truffle.nodes.yield.YieldDispatchHeadNode;
@@ -38,9 +41,15 @@ public NotNode(NotNode prev) {
super(prev);
}

@CreateCast("arguments") public RubyNode[] createCast(RubyNode[] arguments) {
return new RubyNode[] {
BooleanCastNodeFactory.create(getContext(), getSourceSection(), arguments[0])
};
}

@Specialization
public boolean not(Object value) {
return !getContext().getCoreLibrary().isTruthy(value);
public boolean not(boolean value) {
return !value;
}

}
@@ -95,25 +104,48 @@ public int objectIDFalse(boolean value) {

@Specialization
public long objectID(int value) {
return ObjectIDOperations.fixnumToID(value);
return ObjectIDOperations.smallFixnumToID(value);
}

@Specialization(rewriteOn = ArithmeticException.class)
public long objectIDSmallFixnumOverflow(long value) {
return ObjectIDOperations.smallFixnumToIDOverflow(value);
}

/* TODO: Ideally we would have this instead of the code below to speculate better. [GRAAL-903]
@Specialization(guards = "isSmallFixnum")
public long objectIDSmallFixnum(long value) {
return ObjectIDOperations.smallFixnumToID(value);
}
@Specialization(guards = "!isSmallFixnum")
public Object objectIDLargeFixnum(long value) {
return ObjectIDOperations.largeFixnumToID(getContext(), value);
} */

@Specialization
public long objectID(long value) {
return ObjectIDOperations.fixnumToID(value);
public Object objectID(long value) {
if (isSmallFixnum(value)) {
return ObjectIDOperations.smallFixnumToID(value);
} else {
return ObjectIDOperations.largeFixnumToID(getContext(), value);
}
}

@Specialization
public long objectID(double value) {
CompilerDirectives.transferToInterpreter();
throw new UnsupportedOperationException("No ID for Float yet");
public RubyBignum objectID(double value) {
return ObjectIDOperations.floatToID(getContext(), value);
}

@Specialization
public long objectID(RubyBasicObject object) {
return object.getObjectID();
}

protected boolean isSmallFixnum(long fixnum) {
return ObjectIDOperations.isSmallFixnum(fixnum);
}

}

@CoreMethod(names = {"equal?", "=="}, required = 1)
@@ -127,8 +159,7 @@ public ReferenceEqualNode(ReferenceEqualNode prev) {
super(prev);
}

// The @CreateCast is not applied when using this, so the caller needs to unbox itself.
protected abstract boolean executeEqualWithUnboxed(VirtualFrame frame, Object a, Object b);
protected abstract boolean executeReferenceEqual(VirtualFrame frame, Object a, Object b);

@Specialization public boolean equal(boolean a, boolean b) { return a == b; }
@Specialization public boolean equal(int a, int b) { return a == b; }
43 changes: 43 additions & 0 deletions core/src/main/java/org/jruby/truffle/nodes/core/FloatNodes.java
Original file line number Diff line number Diff line change
@@ -16,6 +16,7 @@
import com.oracle.truffle.api.dsl.*;
import com.oracle.truffle.api.utilities.BranchProfile;
import org.jruby.truffle.runtime.*;
import org.jruby.truffle.runtime.control.RaiseException;
import org.jruby.truffle.runtime.core.*;
import org.jruby.truffle.runtime.core.RubyArray;

@@ -301,6 +302,15 @@ public boolean less(double a, double b) {
public boolean less(double a, RubyBignum b) {
return a < b.doubleValue();
}

@Specialization(guards = "!isBignum(arguments[1])")
public boolean less(@SuppressWarnings("unused") double a, RubyBasicObject other) {
throw new RaiseException(new RubyException(
getContext().getCoreLibrary().getArgumentErrorClass(),
getContext().makeString(String.format("comparison of Float with %s failed", other.getLogicalClass().getName())),
RubyCallStack.getBacktrace(this)
));
}
}

@CoreMethod(names = "<=", required = 1)
@@ -333,6 +343,15 @@ public boolean lessEqual(double a, double b) {
public boolean lessEqual(double a, RubyBignum b) {
return a <= b.doubleValue();
}

@Specialization(guards = "!isBignum(arguments[1])")
public boolean less(@SuppressWarnings("unused") double a, RubyBasicObject other) {
throw new RaiseException(new RubyException(
getContext().getCoreLibrary().getArgumentErrorClass(),
getContext().makeString(String.format("comparison of Float with %s failed", other.getLogicalClass().getName())),
RubyCallStack.getBacktrace(this)
));
}
}

@CoreMethod(names = "==", required = 1)
@@ -365,6 +384,12 @@ public boolean equal(double a, double b) {
public boolean equal(double a, RubyBignum b) {
return a == b.doubleValue();
}

@Specialization(guards = "!isBignum(arguments[1])")
public boolean less(@SuppressWarnings("unused") double a, RubyBasicObject other) {
// TODO (nirvdrum Dec. 1, 2014): This is a stub. There is one case where this should return 'true', but it's not a trivial fix.
return false;
}
}

@CoreMethod(names = "<=>", required = 1)
@@ -414,6 +439,15 @@ public boolean greaterEqual(double a, double b) {
public boolean greaterEqual(double a, RubyBignum b) {
return a >= b.doubleValue();
}

@Specialization(guards = "!isBignum(arguments[1])")
public boolean less(@SuppressWarnings("unused") double a, RubyBasicObject other) {
throw new RaiseException(new RubyException(
getContext().getCoreLibrary().getArgumentErrorClass(),
getContext().makeString(String.format("comparison of Float with %s failed", other.getLogicalClass().getName())),
RubyCallStack.getBacktrace(this)
));
}
}

@CoreMethod(names = ">", required = 1)
@@ -446,6 +480,15 @@ public boolean equal(double a, double b) {
public boolean equal(double a, RubyBignum b) {
return a > b.doubleValue();
}

@Specialization(guards = "!isBignum(arguments[1])")
public boolean less(@SuppressWarnings("unused") double a, RubyBasicObject other) {
throw new RaiseException(new RubyException(
getContext().getCoreLibrary().getArgumentErrorClass(),
getContext().makeString(String.format("comparison of Float with %s failed", other.getLogicalClass().getName())),
RubyCallStack.getBacktrace(this)
));
}
}

@CoreMethod(names = "abs")
37 changes: 37 additions & 0 deletions core/src/main/java/org/jruby/truffle/nodes/core/HashNodes.java
Original file line number Diff line number Diff line change
@@ -18,6 +18,7 @@
import com.oracle.truffle.api.frame.*;
import com.oracle.truffle.api.utilities.BranchProfile;
import org.jruby.runtime.Visibility;
import org.jruby.truffle.nodes.RubyNode;
import org.jruby.truffle.nodes.RubyRootNode;
import org.jruby.truffle.nodes.dispatch.DispatchHeadNode;
import org.jruby.truffle.nodes.yield.YieldDispatchHeadNode;
@@ -1004,6 +1005,42 @@ public RubyHash mergeObjectArrayObjectArray(VirtualFrame frame, RubyHash hash, R

}

@CoreMethod(names = "default", optional = 1)
public abstract static class DefaultNode extends HashCoreMethodNode {

public DefaultNode(RubyContext context, SourceSection sourceSection) {
super(context, sourceSection);
}

public DefaultNode(DefaultNode prev) {
super(prev);
}

@Specialization
public Object defaultElement(VirtualFrame frame, RubyHash hash, UndefinedPlaceholder undefined) {
Object ret = hash.getDefaultValue();

// TODO (nirvdrum Dec. 1, 2014): This needs to evaluate the defaultProc if it exists before it tries defaultValue.
if (ret != null) {
return ret;
} else {
return getContext().getCoreLibrary().getNilObject();
}
}

@Specialization
public Object defaultElement(VirtualFrame frame, RubyHash hash, Object key) {
Object ret = hash.getDefaultValue();

// TODO (nirvdrum Dec. 1, 2014): This really needs to do something with the key. Dummy stub for now.
if (ret != null) {
return ret;
} else {
return getContext().getCoreLibrary().getNilObject();
}
}
}

@CoreMethod(names = "size")
public abstract static class SizeNode extends HashCoreMethodNode {

Original file line number Diff line number Diff line change
@@ -65,7 +65,7 @@ protected boolean areSame(VirtualFrame frame, Object left, Object right) {
CompilerDirectives.transferToInterpreterAndInvalidate();
referenceEqualNode = insert(BasicObjectNodesFactory.ReferenceEqualNodeFactory.create(getContext(), getSourceSection(), new RubyNode[]{null, null}));
}
return referenceEqualNode.executeEqualWithUnboxed(frame, left, right);
return referenceEqualNode.executeReferenceEqual(frame, left, right);
}

protected boolean areEqual(VirtualFrame frame, Object left, Object right) {
@@ -377,6 +377,11 @@ public RubyClass getClass(@SuppressWarnings("unused") int value) {
return getContext().getCoreLibrary().getFixnumClass();
}

@Specialization
public RubyClass getClass(@SuppressWarnings("unused") long value) {
return getContext().getCoreLibrary().getFixnumClass();
}

@Specialization
public RubyClass getClass(@SuppressWarnings("unused") RubyBignum value) {
return getContext().getCoreLibrary().getBignumClass();
52 changes: 52 additions & 0 deletions core/src/main/java/org/jruby/truffle/nodes/core/NilClassNodes.java
Original file line number Diff line number Diff line change
@@ -12,6 +12,7 @@
import com.oracle.truffle.api.source.*;
import com.oracle.truffle.api.dsl.*;
import org.jruby.truffle.runtime.*;
import org.jruby.truffle.runtime.control.RaiseException;
import org.jruby.truffle.runtime.core.*;

@CoreClass(name = "NilClass")
@@ -85,6 +86,23 @@ public int toI() {
}
}

@CoreMethod(names = "to_f", needsSelf = false)
public abstract static class ToFNode extends CoreMethodNode {

public ToFNode(RubyContext context, SourceSection sourceSection) {
super(context, sourceSection);
}

public ToFNode(ToINode prev) {
super(prev);
}

@Specialization
public double toF() {
return 0.0f;
}
}

@CoreMethod(names = "to_s", needsSelf = false)
public abstract static class ToSNode extends CoreMethodNode {

@@ -102,6 +120,40 @@ public RubyString toS() {
}
}

@CoreMethod(names = "to_h", needsSelf = false)
public abstract static class ToHNode extends CoreMethodNode {

public ToHNode(RubyContext context, SourceSection sourceSection) {
super(context, sourceSection);
}

public ToHNode(ToHNode prev) {
super(prev);
}

@Specialization
public RubyHash toH() {
return new RubyHash(getContext().getCoreLibrary().getHashClass(), null, getContext().getCoreLibrary().getNilObject(), null, 0);
}
}

@CoreMethod(names = "dup", needsSelf = false)
public abstract static class DupNode extends CoreMethodNode {

public DupNode(RubyContext context, SourceSection sourceSection) {
super(context, sourceSection);
}

public DupNode(DupNode prev) {
super(prev);
}

@Specialization
public RubyString dup() {
throw new RaiseException(getContext().getCoreLibrary().typeError("can't dup NilClass", this));
}
}

@CoreMethod(names = "&", needsSelf = false, required = 1)
public abstract static class AndNode extends CoreMethodNode {

Original file line number Diff line number Diff line change
@@ -46,7 +46,7 @@ public Object id2Ref(long id) {
return true;
} else if (id == ObjectIDOperations.FALSE) {
return false;
} else if (ObjectIDOperations.isFixnum(id)) {
} else if (ObjectIDOperations.isSmallFixnumID(id)) {
return ObjectIDOperations.toFixnum(id);
} else {
final Object object = getContext().getObjectSpaceManager().collectLiveObjects().get(id);
@@ -57,7 +57,24 @@ public Object id2Ref(long id) {
return object;
}
}
}

@Specialization(guards = "isLargeFixnumID")
public Object id2RefLargeFixnum(RubyBignum id) {
return ObjectIDOperations.toFixnum(id);
}

@Specialization(guards = "isFloatID")
public double id2RefFloat(RubyBignum id) {
return ObjectIDOperations.toFloat(id);
}

protected boolean isLargeFixnumID(RubyBignum id) {
return ObjectIDOperations.isLargeFixnumID(id);
}

protected boolean isFloatID(RubyBignum id) {
return ObjectIDOperations.isFloatID(id);
}

}
Original file line number Diff line number Diff line change
@@ -12,13 +12,16 @@
import com.oracle.truffle.api.*;
import com.oracle.truffle.api.source.*;
import com.oracle.truffle.api.frame.*;
import org.jruby.truffle.nodes.cast.BooleanCastNode;
import org.jruby.truffle.nodes.cast.BooleanCastNodeFactory;
import org.jruby.truffle.nodes.yield.*;
import org.jruby.truffle.runtime.*;
import org.jruby.truffle.runtime.core.*;

public abstract class YieldingCoreMethodNode extends CoreMethodNode {

@Child protected YieldDispatchHeadNode dispatchNode;
@Child protected BooleanCastNode booleanCastNode;

public YieldingCoreMethodNode(RubyContext context, SourceSection sourceSection) {
super(context, sourceSection);
@@ -30,12 +33,20 @@ public YieldingCoreMethodNode(YieldingCoreMethodNode prev) {
dispatchNode = prev.dispatchNode;
}

private boolean booleanCast(VirtualFrame frame, Object value) {
if (booleanCastNode == null) {
CompilerDirectives.transferToInterpreterAndInvalidate();
booleanCastNode = insert(BooleanCastNodeFactory.create(getContext(), getSourceSection(), null));
}
return booleanCastNode.executeBoolean(frame, value);
}

public Object yield(VirtualFrame frame, RubyProc block, Object... arguments) {
return dispatchNode.dispatch(frame, block, arguments);
}

public boolean yieldIsTruthy(VirtualFrame frame, RubyProc block, Object... arguments) {
return getContext().getCoreLibrary().isTruthy(yield(frame, block, arguments));
return booleanCast(frame, yield(frame, block, arguments));
}

}
Original file line number Diff line number Diff line change
@@ -9,21 +9,94 @@
*/
package org.jruby.truffle.runtime;

import com.oracle.truffle.api.ExactMath;
import org.jruby.truffle.runtime.core.RubyBignum;

import java.math.BigInteger;

/**
* <pre>
* Object IDs distribution
*
* We try to respect MRI scheme when it makes sense (Fixnum for the moment).
* Have a look at include/ruby/ruby.h below ruby_special_consts.
*
* Encoding for Fixnum (long):
* ... 0000 = false
* ... 0010 = true
* ... 0100 = nil
*
* ... xxx1 = Fixnum of value (id-1)/2 if -2^62 <= value < 2^62
* ... xxx0 = BasicObject generated id (for id > 4)
*
* Encoding for Bignum:
* ... 0001 | 64-bit long = Fixnum if value < -2^62 or value >= 2^62
* ... 0010 | 64-bit raw double bits = Float
* </pre>
*/
public abstract class ObjectIDOperations {

public static int FALSE = 0;
public static int TRUE = 1;
public static int TRUE = 2;
public static int NIL = 4;
public static int FIRST_OBJECT_ID = 6;

private static BigInteger LARGE_FIXNUM_FLAG = BigInteger.ONE.shiftLeft(64);
private static BigInteger FLOAT_FLAG = BigInteger.ONE.shiftLeft(65);

private static long SMALL_FIXNUM_MIN = -(1L << 62);
private static long SMALL_FIXNUM_MAX = (1L << 62) - 1;

// primitive => ID

public static boolean isSmallFixnum(long fixnum) {
// TODO: optimize
return SMALL_FIXNUM_MIN <= fixnum && fixnum <= SMALL_FIXNUM_MAX;
}

public static long smallFixnumToIDOverflow(long fixnum) throws ArithmeticException{
return ExactMath.addExact(ExactMath.multiplyExact(fixnum, 2), 1);
}

public static long smallFixnumToID(long fixnum) {
assert isSmallFixnum(fixnum);
return fixnum * 2 + 1;
}

public static RubyBignum largeFixnumToID(RubyContext context, long fixnum) {
assert !isSmallFixnum(fixnum);
return new RubyBignum(context.getCoreLibrary().getBignumClass(), BigInteger.valueOf(fixnum).or(LARGE_FIXNUM_FLAG));
}

public static boolean isFixnum(long id) {
public static RubyBignum floatToID(RubyContext context, double value) {
long bits = Double.doubleToRawLongBits(value);
return new RubyBignum(context.getCoreLibrary().getBignumClass(), BigInteger.valueOf(bits).or(FLOAT_FLAG));
}

// ID => primitive

public static boolean isSmallFixnumID(long id) {
return id % 2 != 0;
}

public static long toFixnum(long id) {
return (id - 1) / 2;
}

public static long fixnumToID(long fixnum) {
return fixnum * 2 + 1;
public static boolean isLargeFixnumID(RubyBignum id) {
return !id.bigIntegerValue().and(LARGE_FIXNUM_FLAG).equals(BigInteger.ZERO);
}

public static long toFixnum(RubyBignum id) {
return id.longValue();
}

public static boolean isFloatID(RubyBignum id) {
return !id.bigIntegerValue().and(FLOAT_FLAG).equals(BigInteger.ZERO);
}

public static double toFloat(RubyBignum id) {
return Double.longBitsToDouble(id.longValue());
}

}
Original file line number Diff line number Diff line change
@@ -34,10 +34,8 @@
import org.jruby.truffle.runtime.core.RubyBasicObject;
import org.jruby.truffle.runtime.core.RubyBinding;
import org.jruby.truffle.runtime.core.RubyException;
import org.jruby.truffle.runtime.core.RubyModule;
import org.jruby.truffle.runtime.core.RubyString;
import org.jruby.truffle.runtime.core.RubySymbol;
import org.jruby.truffle.runtime.core.RubyThread;
import org.jruby.truffle.runtime.subsystems.*;
import org.jruby.truffle.runtime.util.Supplier;
import org.jruby.truffle.translator.TranslatorDriver;
@@ -65,7 +63,7 @@ public class RubyContext extends ExecutionContext {
private final LexicalScope rootLexicalScope;
private final CompilerOptions compilerOptions;

private final AtomicLong nextObjectID = new AtomicLong(6);
private final AtomicLong nextObjectID = new AtomicLong(ObjectIDOperations.FIRST_OBJECT_ID);

private final ThreadLocal<Queue<Object>> throwTags = new ThreadLocal<Queue<Object>>() {

1 change: 0 additions & 1 deletion core/src/main/java/org/jruby/util/cli/OutputStrings.java
Original file line number Diff line number Diff line change
@@ -64,7 +64,6 @@ public static String getBasicUsageHelp() {
.append(" --client use the non-optimizing \"client\" JVM\n")
.append(" (improves startup; default)\n")
.append(" --server use the optimizing \"server\" JVM (improves perf)\n")
.append(" --(no-)indy use invokedynamic (Java 7+) to optimize (perf++, startup--)\n")
.append(" --manage enable remote JMX management and monitoring of the VM\n")
.append(" and JRuby\n")
.append(" --headless do not launch a GUI window, no matter what\n")
2 changes: 1 addition & 1 deletion spec/compiler/general_spec.rb
Original file line number Diff line number Diff line change
@@ -108,7 +108,7 @@ def compile_run(src, filename, line)
run("1r") {|result| expect(result).to eq (Rational(1, 1))}
run("1.1r") {|result| expect(result).to eq (Rational(11, 10))}
run("1i") {|result| expect(result).to eq (Complex(0, 1))}
pending("needs parser support") { run("1.1i") {|result| expect(result).to eq (Complex(0, 1.1))} }
run("1.1i") {|result| expect(result).to eq (Complex(0, 1.1))}
end

it "compiles interpolated strings" do
Original file line number Diff line number Diff line change
@@ -8,6 +8,6 @@ class C

describe 'NonLocalReturn' do
it 'inside a block of a define_method block body returns normally' do
C.new.foo.should_be :foo
C.new.foo.should be(:foo)
end
end
1 change: 0 additions & 1 deletion spec/truffle/tags/core/float/equal_value_tags.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
fails:Float#== returns true if self has the same value as other
fails:Float#== calls 'other == self' if coercion fails
1 change: 0 additions & 1 deletion spec/truffle/tags/core/hash/default_tags.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
fails:Hash#default returns the default value
fails:Hash#default uses the default proc to compute a default value, passing given key
fails:Hash#default calls default proc with nil arg if passed a default proc but no arg
fails:Hash#default= sets the default value
1 change: 0 additions & 1 deletion spec/truffle/tags/core/nil/dup_tags.txt

This file was deleted.

1 change: 0 additions & 1 deletion spec/truffle/tags/core/nil/to_a_tags.txt

This file was deleted.

2 changes: 0 additions & 2 deletions spec/truffle/tags/core/nil/to_f_tags.txt

This file was deleted.

1 change: 0 additions & 1 deletion spec/truffle/tags/core/nil/to_h_tags.txt

This file was deleted.

1 change: 0 additions & 1 deletion spec/truffle/tags/core/nil/to_i_tags.txt

This file was deleted.

33 changes: 33 additions & 0 deletions test/truffle/test_methods_parity.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
=begin
Run first with JRuby+Truffle, then with MRI:
$ .../jruby -X+T test_methods_parity.rb > truffle.methods
$ .../ruby test_methods_parity.rb truffle.methods > mri.methods
Compare with:
$ git diff -U10 --no-index mri.methods truffle.methods
Red is what we don't implement yet,
Green is methods not existing in MRI (which should be fixed!)
=end

start = BasicObject # Numeric

modules = Object.constants.sort.map { |c|
Object.const_get(c)
}.select { |v|
Module === v and !v.instance_methods(false).empty?
}.select { |mod|
!mod.name.end_with?("Error")
}.select { |mod|
mod <= start
}

if RUBY_ENGINE == "ruby" and truffle_file = ARGV.first
truffle_modules = File.readlines(truffle_file).map(&:chomp).grep(/^[A-Z]/)
modules = modules.select { |mod| truffle_modules.include? mod.name }
end

modules.each do |mod|
puts
puts mod.name
puts mod.instance_methods(false).sort
end

0 comments on commit 7a21fc4

Please sign in to comment.