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(); } }