org.jruby.RubyProc Maven / Gradle / Ivy
/***** 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) 2001 Chad Fowler
* Copyright (C) 2001 Alan Moore
* Copyright (C) 2001-2004 Jan Arne Petersen
* Copyright (C) 2002-2004 Anders Bengtsson
* Copyright (C) 2002-2005 Thomas E Enebo
* Copyright (C) 2004 Stefan Matthias Aust
* Copyright (C) 2005 Charles O Nutter
* Copyright (C) 2007 Miguel Covarrubias
*
* 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;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.jruby.anno.JRubyMethod;
import org.jruby.anno.JRubyClass;
import org.jruby.exceptions.JumpException;
import org.jruby.javasupport.util.RuntimeHelpers;
import org.jruby.lexer.yacc.ISourcePosition;
import org.jruby.parser.BlockStaticScope;
import org.jruby.parser.StaticScope;
import org.jruby.runtime.Arity;
import org.jruby.runtime.Binding;
import org.jruby.runtime.Block;
import org.jruby.runtime.BlockBody;
import org.jruby.runtime.ClassIndex;
import org.jruby.runtime.MethodBlock;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext;
import static org.jruby.CompatVersion.*;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.runtime.marshal.DataType;
/**
* @author jpetersen
*/
@JRubyClass(name="Proc")
public class RubyProc extends RubyObject implements DataType {
private Block block = Block.NULL_BLOCK;
private Block.Type type;
private ISourcePosition sourcePosition;
protected RubyProc(Ruby runtime, RubyClass rubyClass, Block.Type type) {
super(runtime, rubyClass);
this.type = type;
}
protected RubyProc(Ruby runtime, RubyClass rubyClass, Block.Type type, ISourcePosition sourcePosition) {
this(runtime, rubyClass, type);
this.sourcePosition = sourcePosition;
}
private static ObjectAllocator PROC_ALLOCATOR = new ObjectAllocator() {
public IRubyObject allocate(Ruby runtime, RubyClass klass) {
RubyProc instance = new RubyProc(runtime, runtime.getProc(), Block.Type.PROC);
instance.setMetaClass(klass);
return instance;
}
};
public static RubyClass createProcClass(Ruby runtime) {
RubyClass procClass = runtime.defineClass("Proc", runtime.getObject(), PROC_ALLOCATOR);
runtime.setProc(procClass);
procClass.index = ClassIndex.PROC;
procClass.setReifiedClass(RubyProc.class);
procClass.defineAnnotatedMethods(RubyProc.class);
return procClass;
}
public Block getBlock() {
return block;
}
// Proc class
@Deprecated
public static RubyProc newProc(Ruby runtime, Block.Type type) {
throw runtime.newRuntimeError("deprecated RubyProc.newProc with no block; do not use");
}
public static RubyProc newProc(Ruby runtime, Block block, Block.Type type) {
return newProc(runtime, block, type, null);
}
public static RubyProc newProc(Ruby runtime, Block block, Block.Type type, ISourcePosition sourcePosition) {
RubyProc proc = new RubyProc(runtime, runtime.getProc(), type, sourcePosition);
proc.setup(block);
return proc;
}
/**
* Create a new instance of a Proc object. We override this method (from RubyClass)
* since we need to deal with special case of Proc.new with no arguments or block arg. In
* this case, we need to check previous frame for a block to consume.
*/
@JRubyMethod(name = "new", rest = true, meta = true)
public static IRubyObject newInstance(ThreadContext context, IRubyObject recv, IRubyObject[] args, Block block) {
// No passed in block, lets check next outer frame for one ('Proc.new')
if (!block.isGiven()) block = context.getCurrentFrame().getBlock();
// This metaclass == recv check seems gross, but MRI seems to do the same:
// if (!proc && ruby_block->block_obj && CLASS_OF(ruby_block->block_obj) == klass) {
if (block.isGiven() && block.getProcObject() != null && block.getProcObject().getMetaClass() == recv) {
return block.getProcObject();
}
RubyProc obj = (RubyProc)((RubyClass) recv).allocate();
obj.setup(block);
obj.callMethod(context, "initialize", args, block);
return obj;
}
private void setup(Block procBlock) {
if (!procBlock.isGiven()) {
throw getRuntime().newArgumentError("tried to create Proc object without a block");
}
if (isLambda() && procBlock == null) {
// TODO: warn "tried to create Proc object without a block"
}
block = procBlock.cloneBlock();
if (isThread()) {
// modify the block with a new backref/lastline-grabbing scope
StaticScope oldScope = block.getBody().getStaticScope();
StaticScope newScope = new BlockStaticScope(oldScope.getEnclosingScope(), oldScope.getVariables());
newScope.setBackrefLastlineScope(true);
newScope.setPreviousCRefScope(oldScope.getPreviousCRefScope());
newScope.setModule(oldScope.getModule());
block.getBody().setStaticScope(newScope);
}
// force file/line info into the new block's binding
block.getBinding().setFile(block.getBody().getFile());
block.getBinding().setLine(block.getBody().getLine());
block.type = type;
block.setProcObject(this);
}
@JRubyMethod(name = "clone")
@Override
public IRubyObject rbClone() {
RubyProc newProc = newProc(getRuntime(), block, type, sourcePosition);
// TODO: CLONE_SETUP here
return newProc;
}
@JRubyMethod(name = "dup")
@Override
public IRubyObject dup() {
return newProc(getRuntime(), block, type, sourcePosition);
}
@JRubyMethod(name = "==", required = 1)
public IRubyObject op_equal(IRubyObject other) {
return getRuntime().newBoolean(other instanceof RubyProc &&
(this == other || this.block.equals(((RubyProc)other).block)));
}
@JRubyMethod(name = "to_s", compat = RUBY1_8)
@Override
public IRubyObject to_s() {
return RubyString.newString(
getRuntime(),"#");
}
@JRubyMethod(name = "to_s", compat = RUBY1_9)
public IRubyObject to_s19() {
StringBuilder sb = new StringBuilder("#");
return RubyString.newString(getRuntime(), sb.toString());
}
@JRubyMethod(name = "binding")
public IRubyObject binding() {
return getRuntime().newBinding(block.getBinding());
}
@JRubyMethod(name = {"call", "[]"}, rest = true, compat = RUBY1_8)
public IRubyObject call(ThreadContext context, IRubyObject[] args, Block block) {
return call(context, args, null, block);
}
public IRubyObject call(ThreadContext context, IRubyObject[] args) {
return call(context, args, null, Block.NULL_BLOCK);
}
private IRubyObject[] prepareProcArgs(ThreadContext context, IRubyObject[] args) {
Arity arity = block.arity();
// for procs and blocks, single array passed to multi-arg must be spread
if (arity != Arity.ONE_ARGUMENT && arity.required() != 0 &&
(arity.isFixed() || arity != Arity.OPTIONAL) &&
args.length == 1 && args[0].respondsTo("to_ary")) {
args = args[0].convertToArray().toJavaArray();
}
if (arity.isFixed()) {
List list = new ArrayList(Arrays.asList(args));
int required = arity.required();
if (required > args.length) {
for (int i = args.length; i < required; i++) {
list.add(context.getRuntime().getNil());
}
args = list.toArray(args);
} else if (required < args.length) {
args = list.subList(0, required).toArray(args);
}
}
return args;
}
@JRubyMethod(name = {"call", "[]", "yield", "==="}, rest = true, compat = RUBY1_9)
public IRubyObject call19(ThreadContext context, IRubyObject[] args, Block blockCallArg) {
if (isLambda()) block.arity().checkArity(context.getRuntime(), args.length);
if (isProc()) args = prepareProcArgs(context, args);
return call(context, args, null, blockCallArg);
}
public IRubyObject call(ThreadContext context, IRubyObject[] args, IRubyObject self, Block passedBlock) {
assert args != null;
Block newBlock = block.cloneBlock();
int jumpTarget = newBlock.getBinding().getFrame().getJumpTarget();
try {
if (self != null) newBlock.getBinding().setSelf(self);
return newBlock.call(context, args, passedBlock);
} catch (JumpException.BreakJump bj) {
return handleBreakJump(getRuntime(), newBlock, bj, jumpTarget);
} catch (JumpException.ReturnJump rj) {
return handleReturnJump(context, rj, jumpTarget);
} catch (JumpException.RetryJump rj) {
return handleRetryJump(getRuntime(), rj);
}
}
private IRubyObject handleBreakJump(Ruby runtime, Block newBlock, JumpException.BreakJump bj, int jumpTarget) {
switch(newBlock.type) {
case LAMBDA:
if (bj.getTarget() == jumpTarget) return (IRubyObject) bj.getValue();
throw runtime.newLocalJumpError(RubyLocalJumpError.Reason.BREAK, (IRubyObject)bj.getValue(), "unexpected break");
case PROC:
if (newBlock.isEscaped()) throw runtime.newLocalJumpError(RubyLocalJumpError.Reason.BREAK, (IRubyObject)bj.getValue(), "break from proc-closure");
}
throw bj;
}
private IRubyObject handleReturnJump(ThreadContext context, JumpException.ReturnJump rj, int jumpTarget) {
int target = rj.getTarget();
// lambda always just returns the value
if (target == jumpTarget && isLambda()) return (IRubyObject) rj.getValue();
// returns can't propagate out of threads. rethrow to let thread handle it
if (isThread()) throw rj;
// If the block-receiving method is not still active and the original
// enclosing frame is no longer on the stack, it's a bad return.
// FIXME: this is not very efficient for cases where it won't error
if (target == jumpTarget && !context.isJumpTargetAlive(target, 0)) {
throw context.getRuntime().newLocalJumpError(RubyLocalJumpError.Reason.RETURN,
(IRubyObject) rj.getValue(), "unexpected return");
}
// otherwise, let it propagate
throw rj;
}
private IRubyObject handleRetryJump(Ruby runtime, JumpException.RetryJump rj) {
throw runtime.newLocalJumpError(RubyLocalJumpError.Reason.RETRY, (IRubyObject)rj.getValue(), "retry not supported outside rescue");
}
@JRubyMethod(name = "arity")
public RubyFixnum arity() {
return getRuntime().newFixnum(block.arity().getValue());
}
@JRubyMethod(name = "to_proc")
public RubyProc to_proc() {
return this;
}
@JRubyMethod(name = "source_location", compat = RUBY1_9)
public IRubyObject source_location(ThreadContext context) {
Ruby runtime = context.getRuntime();
if (sourcePosition != null) {
return runtime.newArray(runtime.newString(sourcePosition.getFile()),
runtime.newFixnum(sourcePosition.getLine() + 1 /*zero-based*/));
} else if (block != null) {
Binding binding = block.getBinding();
return runtime.newArray(runtime.newString(binding.getFile()),
runtime.newFixnum(binding.getLine() + 1 /*zero-based*/));
}
return runtime.getNil();
}
@JRubyMethod(name = "parameters", compat = RUBY1_9)
public IRubyObject parameters(ThreadContext context) {
BlockBody body = this.getBlock().getBody();
if (body instanceof MethodBlock) return ((MethodBlock) body).getMethod().parameters(context);
return RuntimeHelpers.parameterListToParameters(context.getRuntime(),
body.getParameterList(), isLambda());
}
@JRubyMethod(name = "lambda?", compat = RUBY1_9)
public IRubyObject lambda_p(ThreadContext context) {
return context.getRuntime().newBoolean(isLambda());
}
private boolean isLambda() {
return type.equals(Block.Type.LAMBDA);
}
private boolean isProc() {
return type.equals(Block.Type.PROC);
}
private boolean isThread() {
return type.equals(Block.Type.THREAD);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy