blob: fd1fec44355b99201778080657c4174e362e060f [file] [log] [blame]
package org.jruby.runtime;
import org.jruby.parser.BlockStaticScope;
import org.jruby.parser.StaticScope;
import org.jruby.runtime.builtin.IRubyObject;
/**
* Represents the the dynamic portion of scoping information. The variableValues are the
* values of assigned local or block variables. The staticScope identifies which sort of
* scope this is (block or local).
*
* Properties of Dynamic Scopes:
* 1. static and dynamic scopes have the same number of names to values
* 2. size of variables (and thus names) is determined during parsing. So those structured do
* not need to change
*
* FIXME: When creating dynamic scopes we sometimes accidentally pass in extra parents. This
* is harmless (other than wasting memory), but we should not do that. We can fix this in two
* ways:
* 1. Fix all callers
* 2. Check parent that is passed in and make if new instance is local, then its parent is not local
*/
public class DynamicScope {
// Our values holder (name of variables are kept in staticScope)
private IRubyObject[] variableValues;
// Static scoping information for this scope
private StaticScope staticScope;
// Captured dyanmic scopes
private DynamicScope parent;
// A place to store that special hiding space that bindings need to implement things like:
// eval("a = 1", binding); eval("p a"). All binding instances must get access to this
// hidden shared scope. We store it here. This will be null if no binding has yet
// been called.
private DynamicScope bindingScope;
public DynamicScope(StaticScope staticScope, DynamicScope parent) {
this.staticScope = staticScope;
// Only use the given DynamicScope as our parent if it's based on our StaticScope's parent
// FIXME: This is kinda hacky...we shouldn't have to do this check
if (parent != null && parent.getStaticScope() == staticScope.getEnclosingScope()) {
this.parent = parent;
}
int size = staticScope.getNumberOfVariables();
if (size > 0) variableValues = new IRubyObject[size];
}
public DynamicScope cloneScope() {
return new DynamicScope(staticScope, parent);
}
/**
* Get all variable names captured (visible) by this scope (sans $~ and $_).
*
* @return a list of variable names
*/
public String[] getAllNamesInScope() {
return staticScope.getAllNamesInScope();
}
public IRubyObject[] getValues() {
return variableValues;
}
/**
* Get value from current scope or one of its captured scopes.
*
* FIXME: block variables are not getting primed to nil so we need to null check those
* until we prime them properly. Also add assert back in.
*
* @param offset zero-indexed value that represents where variable lives
* @param depth how many captured scopes down this variable should be set
* @return the value here
*/
public IRubyObject getValue(int offset, int depth) {
if (depth > 0) {
return parent.getValue(offset, depth - 1);
}
// assert variableValues != null : "No variables in getValue for Off: " + offset + ", Dep: " + depth;
// assert offset < variableValues.length : "Index to big for getValue Off: " + offset + ", Dep: " + depth + ", O: " + this;
// &foo are not getting set from somewhere...I want the following assert to be true though
//assert variableValues[offset] != null : "Getting unassigned: " + staticScope.getVariables()[offset];
return variableValues[offset];
}
/**
* Set value in current dynamic scope or one of its captured scopes.
*
* @param offset zero-indexed value that represents where variable lives
* @param value to set
* @param depth how many captured scopes down this variable should be set
*/
public void setValue(int offset, IRubyObject value, int depth) {
if (depth > 0) {
// assert parent != null : "If depth > 0, then parent should not ever be null";
parent.setValue(offset, value, depth - 1);
} else {
// assert offset < variableValues.length : "Setting " + offset + " to " + value + ", O: " + this;
variableValues[offset] = value;
}
}
/**
* Set all values which represent 'normal' parameters in a call list to this dynamic
* scope. Function calls bind to local scopes by assuming that the indexes or the
* arg list correspond to that of the local scope (plus 2 since $_ and $~ always take
* the first two slots). We pass in a second argument because we sometimes get more
* values than we are expecting. The rest get compacted by original caller into
* rest args.
*
* @param values up to size specified to be mapped as ordinary parm values
* @param size is the number of values to assign as ordinary parm values
*/
public void setArgValues(IRubyObject[] values, int size) {
System.arraycopy(values, 0, variableValues, 2, size);
}
/**
* Copy variable values back for ZSuper call.
*/
public void getArgValues(IRubyObject[] args, int size) {
if(variableValues != null && args != null && variableValues.length>=(size+2)) {
System.arraycopy(variableValues, 2, args, 0, size);
}
}
/**
*
* Make a larger dynamic scope if the static scope grew.
*
* Eval's with bindings require us to possibly change the size of the dynamic scope if
* things like 'eval "b = 2", binding' happens.
*
*/
public void growIfNeeded() {
int dynamicSize = variableValues == null ? 0: variableValues.length;
if (staticScope.getNumberOfVariables() > dynamicSize) {
IRubyObject values[] = new IRubyObject[staticScope.getNumberOfVariables()];
if (dynamicSize > 0) {
System.arraycopy(variableValues, 0, values, 0, dynamicSize);
}
variableValues = values;
}
}
// FIXME: Depending on profiling we may want to cache information on location and depth of
// both $_ and/or $~ since in some situations they may happen a lot. isDefined should be
// fairly cheap, but you never know...
public void setLastLine(IRubyObject value) {
int location = staticScope.isDefined("$_");
setValue(location & 0xffff, value, location >> 16);
}
public IRubyObject getLastLine() {
int location = staticScope.isDefined("$_");
return getValue(location & 0xffff, location >> 16);
}
public void setBackRef(IRubyObject value) {
int location = staticScope.isDefined("$~");
setValue(location & 0xffff, value, location >> 16);
}
public IRubyObject getBackRef() {
int location = staticScope.isDefined("$~");
return getValue(location & 0xffff, location >> 16);
}
public DynamicScope getBindingScope() {
return bindingScope;
}
public void setBindingScope(DynamicScope bindingScope) {
this.bindingScope = bindingScope;
}
/**
* Get next 'captured' scope.
*
* @return the scope captured by this scope for implementing closures
*
*/
public DynamicScope getNextCapturedScope() {
return parent;
}
/**
* Get the static scope associated with this DynamicScope.
*
* @return static complement to this scope
*/
public StaticScope getStaticScope() {
return staticScope;
}
public String toString() {
return toString(new StringBuffer(), "");
}
// Helper function to give a good view of current dynamic scope with captured scopes
private String toString(StringBuffer buf, String indent) {
buf.append(indent).append("Static Type[" + hashCode() + "]: " +
(staticScope instanceof BlockStaticScope ? "block" : "local")+" [");
int size = staticScope.getNumberOfVariables();
if (size != 0) {
String names[] = staticScope.getVariables();
for (int i = 0; i < size-1; i++) {
buf.append(names[i]).append("=");
if (variableValues[i] == null) {
buf.append("null");
} else {
buf.append(variableValues[i]);
}
buf.append(",");
}
buf.append(names[size-1]).append("=");
// assert variableValues.length == names.length : "V: " + variableValues.length +
// " != N: " + names.length + " for " + buf;
if (variableValues[size-1] == null) {
buf.append("null");
} else {
buf.append(variableValues[size-1]);
}
}
buf.append("]");
if (parent != null) {
buf.append("\n");
parent.toString(buf, indent + " ");
}
return buf.toString();
}
}