/*
***** BEGIN LICENSE BLOCK *****
* Version: CPL 1.0/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Common Public
* License Version 1.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.eclipse.org/legal/cpl-v10.html
*
* Software distributed under the License is distributed on an "AS
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
* implied. See the License for the specific language governing
* rights and limitations under the License.
*
* Copyright (C) 2002-2004 Anders Bengtsson <ndrsbngtssn@yahoo.se>
* Copyright (C) 2001-2004 Jan Arne Petersen <jpetersen@uni-bonn.de>
* Copyright (C) 2002 Benoit Cerrina <b.cerrina@wanadoo.fr>
* Copyright (C) 2004-2007 Thomas E Enebo <enebo@acm.org>
* Copyright (C) 2004 Stefan Matthias Aust <sma@3plus4.de>
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the CPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the CPL, the GPL or the LGPL.
***** END LICENSE BLOCK *****/
package org.jruby.runtime;
import org.jruby.Ruby;
import org.jruby.RubyArray;
import org.jruby.RubyModule;
import org.jruby.ast.IterNode;
import org.jruby.ast.MultipleAsgnNode;
import org.jruby.ast.NilImplicitNode;
import org.jruby.ast.Node;
import org.jruby.ast.NodeType;
import org.jruby.ast.util.ArgsUtil;
import org.jruby.common.IRubyWarnings.ID;
import org.jruby.evaluator.AssignmentVisitor;
import org.jruby.exceptions.JumpException;
import org.jruby.parser.StaticScope;
import org.jruby.runtime.builtin.IRubyObject;
/**
* This branch of the BlockBody hierarchy represents an interpreted block that
* passes its AST nodes to the interpreter. It forms the top of the hierarchy
* of interpreted blocks. In a typical application, it is the most heavily
* consumed type of block.
*
* @see SharedScopeBlock, CompiledBlock
*/
public class InterpretedBlock extends BlockBody {
/** The node wrapping the body and parameter list for this block */
private final IterNode iterNode;
/** Whether this block has an argument list or not */
private final boolean hasVarNode;
/** The argument list, pulled out of iterNode */
private final Node varNode;
/** The body of the block, pulled out of bodyNode */
private final Node bodyNode;
/** The static scope for the block body */
private final StaticScope scope;
/** The arity of the block */
private final Arity arity;
public static Block newInterpretedClosure(ThreadContext context, IterNode iterNode, IRubyObject self) {
Frame f = context.getCurrentFrame();
return newInterpretedClosure(iterNode,
self,
Arity.procArityOf(iterNode.getVarNode()),
f,
f.getVisibility(),
context.getRubyClass(),
context.getCurrentScope());
}
public static Block newInterpretedClosure(ThreadContext context, InterpretedBlock body, IRubyObject self) {
Frame f = context.getCurrentFrame();
Binding binding = new Binding(self,
f,
f.getVisibility(),
context.getRubyClass(),
context.getCurrentScope());
return new Block(body, binding);
}
public static Block newInterpretedClosure(IterNode iterNode, IRubyObject self, Arity arity, Frame frame,
Visibility visibility, RubyModule klass, DynamicScope dynamicScope) {
NodeType argsNodeId = getArgumentTypeWackyHack(iterNode);
BlockBody body = new InterpretedBlock(
iterNode,
arity,
asArgumentType(argsNodeId));
Binding binding = new Binding(
self,
frame,
visibility,
klass,
dynamicScope);
return new Block(body, binding);
}
public InterpretedBlock(IterNode iterNode, int argumentType) {
this(iterNode, Arity.procArityOf(iterNode == null ? null : iterNode.getVarNode()), argumentType);
}
public InterpretedBlock(IterNode iterNode, Arity arity, int argumentType) {
super(argumentType);
this.iterNode = iterNode;
this.arity = arity;
this.hasVarNode = iterNode.getVarNode() != null;
this.varNode = iterNode.getVarNode();
this.bodyNode = iterNode.getBodyNode() == null ? NilImplicitNode.NIL : iterNode.getBodyNode();
this.scope = iterNode.getScope();
}
protected Frame pre(ThreadContext context, RubyModule klass, Binding binding) {
return context.preYieldSpecificBlock(binding, iterNode.getScope(), klass);
}
protected void post(ThreadContext context, Binding binding, Visibility vis, Frame lastFrame) {
binding.getFrame().setVisibility(vis);
context.postYield(binding, lastFrame);
}
public IRubyObject yield(ThreadContext context, IRubyObject value, Binding binding, Block.Type type) {
IRubyObject self = prepareSelf(binding);
Visibility oldVis = binding.getFrame().getVisibility();
Frame lastFrame = pre(context, null, binding);
try {
if (hasVarNode) {
setupBlockArg(context, varNode, value, self);
}
return evalBlockBody(context, self);
} catch (JumpException.NextJump nj) {
return handleNextJump(context, nj, type);
} finally {
post(context, binding, oldVis, lastFrame);
}
}
/**
* Yield to this block, usually passed to the current call.
*
* @param context represents the current thread-specific data
* @param value The value to yield, either a single value or an array of values
* @param self The current self
* @param klass
* @param aValue Should value be arrayified or not?
* @return
*/
public IRubyObject yield(ThreadContext context, IRubyObject value, IRubyObject self,
RubyModule klass, boolean aValue, Binding binding, Block.Type type) {
if (klass == null) {
self = prepareSelf(binding);
}
Visibility oldVis = binding.getFrame().getVisibility();
Frame lastFrame = pre(context, klass, binding);
try {
if (hasVarNode) {
if (aValue) {
setupBlockArgs(context, varNode, value, self);
} else {
setupBlockArg(context, varNode, value, self);
}
}
// This while loop is for restarting the block call in case a 'redo' fires.
return evalBlockBody(context, self);
} catch (JumpException.NextJump nj) {
return handleNextJump(context, nj, type);
} finally {
post(context, binding, oldVis, lastFrame);
}
}
private IRubyObject evalBlockBody(ThreadContext context, IRubyObject self) {
// This while loop is for restarting the block call in case a 'redo' fires.
while (true) {
try {
return bodyNode.interpret(context.getRuntime(), context, self, Block.NULL_BLOCK);
} catch (JumpException.RedoJump rj) {
context.pollThreadEvents();
// do nothing, allow loop to redo
} catch (StackOverflowError sfe) {
throw context.getRuntime().newSystemStackError("stack level too deep");
}
}
}
private IRubyObject prepareSelf(Binding binding) {
IRubyObject self = binding.getSelf();
binding.getFrame().setSelf(self);
return self;
}
private IRubyObject handleNextJump(ThreadContext context, JumpException.NextJump nj, Block.Type type) {
return type == Block.Type.LAMBDA ? context.getRuntime().getNil() : (IRubyObject)nj.getValue();
}
private void setupBlockArgs(ThreadContext context, Node varNode, IRubyObject value, IRubyObject self) {
Ruby runtime = context.getRuntime();
switch (varNode.nodeId) {
case ZEROARGNODE:
break;
case MULTIPLEASGNNODE:
value = AssignmentVisitor.multiAssign(runtime, context, self, (MultipleAsgnNode)varNode, (RubyArray)value, false);
break;
default:
defaultArgsLogic(context, runtime, self, value);
}
}
private void setupBlockArg(ThreadContext context, Node varNode, IRubyObject value, IRubyObject self) {
Ruby runtime = context.getRuntime();
switch (varNode.nodeId) {
case ZEROARGNODE:
return;
case MULTIPLEASGNNODE:
value = AssignmentVisitor.multiAssign(runtime, context, self, (MultipleAsgnNode)varNode,
ArgsUtil.convertToRubyArray(runtime, value, ((MultipleAsgnNode)varNode).getHeadNode() != null), false);
break;
default:
defaultArgLogic(context, runtime, self, value);
}
}
private final void defaultArgsLogic(ThreadContext context, Ruby ruby, IRubyObject self, IRubyObject value) {
int length = ArgsUtil.arrayLength(value);
switch (length) {
case 0:
value = ruby.getNil();
break;
case 1:
value = ((RubyArray)value).eltInternal(0);
break;
default:
ruby.getWarnings().warn(ID.MULTIPLE_VALUES_FOR_BLOCK, "multiple values for a block parameter (" + length + " for 1)");
}
varNode.assign(ruby, context, self, value, Block.NULL_BLOCK, false);
}
private final void defaultArgLogic(ThreadContext context, Ruby ruby, IRubyObject self, IRubyObject value) {
if (value == null) {
ruby.getWarnings().warn(ID.MULTIPLE_VALUES_FOR_BLOCK, "multiple values for a block parameter (0 for 1)");
}
varNode.assign(ruby, context, self, value, Block.NULL_BLOCK, false);
}
public StaticScope getStaticScope() {
return scope;
}
public Block cloneBlock(Binding binding) {
// We clone dynamic scope because this will be a new instance of a block. Any previously
// captured instances of this block may still be around and we do not want to start
// overwriting those values when we create a new one.
// ENEBO: Once we make self, lastClass, and lastMethod immutable we can remove duplicate
binding = new Binding(
binding.getSelf(),
binding.getFrame().duplicate(),
binding.getVisibility(),
binding.getKlass(),
binding.getDynamicScope());
return new Block(this, binding);
}
public IterNode getIterNode() {
return iterNode;
}
/**
* What is the arity of this block?
*
* @return the arity
*/
public Arity arity() {
return arity;
}
}