org.jruby.runtime.InterpretedIRBlockBody Maven / Gradle / Ivy
package org.jruby.runtime;
import org.jruby.Ruby;
import org.jruby.RubyArray;
import org.jruby.RubyModule;
import org.jruby.common.IRubyWarnings.ID;
import org.jruby.ir.IRClosure;
import org.jruby.ir.interpreter.Interpreter;
import org.jruby.runtime.Block.Type;
import org.jruby.runtime.builtin.IRubyObject;
public class InterpretedIRBlockBody extends ContextAwareBlockBody {
protected final IRClosure closure;
public InterpretedIRBlockBody(IRClosure closure, Arity arity, int argumentType) {
super(closure.getStaticScope(), arity, argumentType);
this.closure = closure;
}
@Override
public IRubyObject call(ThreadContext context, Binding binding, Block.Type type) {
return call(context, IRubyObject.NULL_ARRAY, binding, type, Block.NULL_BLOCK);
}
@Override
public IRubyObject call(ThreadContext context, IRubyObject arg0, Binding binding, Block.Type type) {
return call(context, new IRubyObject[] {arg0}, binding, type, Block.NULL_BLOCK);
}
@Override
public IRubyObject call(ThreadContext context, IRubyObject arg0, IRubyObject arg1, Binding binding, Block.Type type) {
return call(context, new IRubyObject[] {arg0, arg1}, binding, type, Block.NULL_BLOCK);
}
@Override
public IRubyObject call(ThreadContext context, IRubyObject arg0, IRubyObject arg1, IRubyObject arg2, Binding binding, Block.Type type) {
return call(context, new IRubyObject[] {arg0, arg1, arg2}, binding, type, Block.NULL_BLOCK);
}
@Override
public IRubyObject call(ThreadContext context, IRubyObject[] args, Binding binding, Block.Type type) {
return call(context, args, binding, type, Block.NULL_BLOCK);
}
@Override
public IRubyObject call(ThreadContext context, IRubyObject[] args, Binding binding, Block.Type type, Block block) {
return commonYieldPath(context, prepareArgumentsForCall(context, args, type), null, null, binding, type, block);
}
@Override
public IRubyObject yield(ThreadContext context, IRubyObject value, Binding binding, Type type) {
return yield(context, value, null, null, false, binding, type);
}
@Override
public IRubyObject yield(ThreadContext context, IRubyObject value, IRubyObject self, RubyModule klass, boolean isArray, Binding binding, Type type) {
IRubyObject[] args;
if (isArray) {
if (arity().getValue() > 1) {
args = value == null ? IRubyObject.NULL_ARRAY : prepareArgumentsForYield(context, ((RubyArray)value).toJavaArray(), type);
} else {
args = assignArrayToBlockArgs(context.runtime, value);
}
} else {
args = prepareArgumentsForYield(context, value == null ? IRubyObject.NULL_ARRAY : new IRubyObject[] { value }, type);
}
return commonYieldPath(context, args, self, klass, binding, type, Block.NULL_BLOCK);
}
protected IRubyObject prepareSelf(Binding binding) {
IRubyObject self = binding.getSelf();
binding.getFrame().setSelf(self);
return self;
}
protected IRubyObject commonYieldPath(ThreadContext context, IRubyObject[] args, IRubyObject self, RubyModule klass, Binding binding, Type type, Block block) {
// SSS: Important! Use getStaticScope() to use a copy of the static-scope stored in the block-body.
// Do not use 'closure.getStaticScope()' -- that returns the original copy of the static scope.
// This matters because blocks created for Thread bodies modify the static-scope field of the block-body
// that records additional state about the block body.
//
// FIXME: Rather than modify static-scope, it seems we ought to set a field in block-body which is then
// used to tell dynamic-scope that it is a dynamic scope for a thread body. Anyway, to be revisited later!
Visibility oldVis = binding.getFrame().getVisibility();
RubyModule currentModule = getStaticScope().getModule();
Frame prevFrame = context.preYieldNoScope(binding, klass);
if (klass == null) self = prepareSelf(binding);
try {
DynamicScope prevScope = binding.getDynamicScope();
DynamicScope newScope = closure.isForLoopBody() ? prevScope : DynamicScope.newDynamicScope(getStaticScope(), prevScope);
context.pushScope(newScope);
return Interpreter.INTERPRET_BLOCK(context, self, closure, args, binding.getMethod(), block, type);
}
finally {
binding.getFrame().setVisibility(oldVis);
context.postYield(binding, prevFrame);
}
}
protected void blockArgWarning(Ruby ruby, int length) {
ruby.getWarnings().warn(ID.MULTIPLE_VALUES_FOR_BLOCK, "multiple values for a block parameter (" +
length + " for 1)");
}
private IRubyObject prepareArrayArgsForCall(Ruby ruby, IRubyObject value) {
int length = (value instanceof RubyArray) ? ((RubyArray)value).getLength() : 0;
switch (length) {
case 0: return ruby.getNil();
case 1: return ((RubyArray)value).eltInternal(0);
default: blockArgWarning(ruby, length);
}
return value;
}
private IRubyObject[] assignArrayToBlockArgs(Ruby ruby, IRubyObject value) {
switch (argumentType) {
case ZERO_ARGS:
return IRubyObject.NULL_ARRAY;
case MULTIPLE_ASSIGNMENT:
case SINGLE_RESTARG:
return (value == null) ? IRubyObject.NULL_ARRAY : ((value instanceof RubyArray) ? ((RubyArray)value).toJavaArrayMaybeUnsafe() : new IRubyObject[] { value } );
default:
return new IRubyObject[] {prepareArrayArgsForCall(ruby, value)};
}
}
protected IRubyObject[] convertToRubyArray(ThreadContext context, IRubyObject[] args) {
return (args.length == 0) ? context.runtime.getSingleNilArray()
: new IRubyObject[] {context.runtime.newArrayNoCopy(args)};
}
protected IRubyObject[] prepareArgumentsForYield(ThreadContext context, IRubyObject[] args, Block.Type type) {
// SSS FIXME: Hmm .. yield can yield to blocks other than NORMAL block type as well.
int blockArity = arity().getValue();
if (args.length == 1) {
IRubyObject soleArg = args[0];
if (soleArg instanceof RubyArray) {
if (argumentType == MULTIPLE_ASSIGNMENT) args = ((RubyArray) soleArg).toJavaArray();
} else if (blockArity > 1) {
IRubyObject toAryArg = Helpers.aryToAry(soleArg);
if (toAryArg instanceof RubyArray) args = ((RubyArray)toAryArg).toJavaArray();
else {
throw context.runtime.newTypeError(soleArg.getType().getName() + "#to_ary should return Array");
}
}
} else if (argumentType == ARRAY) {
args = convertToRubyArray(context, args);
}
return args;
}
@Override
public IRubyObject[] prepareArgumentsForCall(ThreadContext context, IRubyObject[] args, Block.Type type) {
int blockArity = arity().getValue();
switch (type) {
// SSS FIXME: How is it even possible to "call" a block?
// I thought only procs & lambdas can be called, and blocks are yielded to.
case NORMAL:
case PROC: {
if (args.length == 1) {
IRubyObject soleArg = args[0];
if (soleArg instanceof RubyArray) {
if ((argumentType == MULTIPLE_ASSIGNMENT) || ((argumentType == SINGLE_RESTARG) && (type == Block.Type.NORMAL))) {
args = ((RubyArray) soleArg).toJavaArray();
}
} else if (blockArity > 1) {
IRubyObject toAryArg = Helpers.aryToAry(soleArg);
if (toAryArg instanceof RubyArray) args = ((RubyArray)toAryArg).toJavaArray();
else {
throw context.runtime.newTypeError(soleArg.getType().getName() + "#to_ary should return Array");
}
}
} else if (argumentType == ARRAY) {
args = convertToRubyArray(context, args);
}
break;
}
case LAMBDA:
if (argumentType == ARRAY && args.length != 1) {
if (blockArity != args.length) {
context.runtime.getWarnings().warn(ID.MULTIPLE_VALUES_FOR_BLOCK, "multiple values for a block parameter (" + args.length + " for " + blockArity + ")");
}
args = convertToRubyArray(context, args);
} else {
arity().checkArity(context.runtime, args);
}
break;
}
return args;
}
@Override
public String getFile() {
return closure.getFileName();
}
@Override
public int getLine() {
return closure.getLineNumber();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy