Resolving bug 255891: new approach to dynamic scoping

This commit is contained in:
igor%mir2.org 2004-09-01 14:17:00 +00:00
Родитель 229d07cac0
Коммит d78af3b167
6 изменённых файлов: 128 добавлений и 54 удалений

Просмотреть файл

@ -158,9 +158,8 @@ JavaScript use <i>static scope</i>, which means that variables are first looked
up in the function and then, if not found there, in the lexically enclosing
scope. This causes problems if functions you define in your shared scope
need access to variables you define in your instance scope.
<p>With Rhino 1.5, it is possible to compile functions to use <i>dynamic
scope</i>. With dynamic scope, functions look at the top-level scope of the
calling function rather than their lexical scope. So we can store information
<p>With Rhino 1.6, it is possible to use <i>dynamic scope</i>. With dynamic scope, functions look at the top-level scope of the currently executed script
rather than their lexical scope. So we can store information
that varies across scopes in the instance scope yet still share functions
that manipulate that information reside in the shared scope. </p>
<p>The <a href="http://lxr.mozilla.org/mozilla/source/js/rhino/examples/DynamicScopes.java">

Просмотреть файл

@ -40,65 +40,94 @@ import org.mozilla.javascript.*;
*/
public class DynamicScopes {
static boolean useDynamicScope;
static class MyFactory extends ContextFactory
{
protected boolean hasFeature(Context cx, int featureIndex)
{
if (featureIndex == Context.FEATURE_DYNAMIC_SCOPE) {
return useDynamicScope;
}
return super.hasFeature(cx, featureIndex);
}
}
static {
ContextFactory.initGlobal(new MyFactory());
}
/**
* Main entry point.
*
* Set up the shared scope and then spawn new threads that execute
* relative to that shared scope. Try compiling functions with and
* relative to that shared scope. Try to run functions with and
* without dynamic scope to see the effect.
*
* The expected output is
* <pre>
* sharedScope
* nested:sharedScope
* sharedScope
* nested:sharedScope
* sharedScope
* nested:sharedScope
* thread0
* nested:thread0
* thread1
* nested:thread1
* thread2
* nested:thread2
* </pre>
* The final three lines may be permuted in any order depending on
* thread scheduling.
*/
public static void main(String[] args)
throws JavaScriptException
{
Context cx = Context.enter();
try {
cx.setCompileFunctionsWithDynamicScope(false);
runScripts(cx);
cx.setCompileFunctionsWithDynamicScope(true);
runScripts(cx);
// Precompile source only once
String source = ""
+"var x = 'sharedScope';\n"
+"function f() { return x; }\n"
// Dynamic scope works with nested function too
+"function initClosure(prefix) {\n"
+" return function test() { return prefix+x; }\n"
+"}\n"
+"var closure = initClosure('nested:');\n"
+"";
Script script = cx.compileString(source, "sharedScript", 1, null);
useDynamicScope = false;
runScripts(cx, script);
useDynamicScope = true;
runScripts(cx, script);
} finally {
cx.exit();
}
}
static void runScripts(Context cx)
throws JavaScriptException
static void runScripts(Context cx, Script script)
{
// Initialize the standard objects (Object, Function, etc.)
// This must be done before scripts can be executed. The call
// returns a new scope that we will share.
ScriptableObject scope = cx.initStandardObjects(null, true);
ScriptableObject sharedScope = cx.initStandardObjects(null, true);
// Now we can evaluate a script and functions will be compiled to
// use dynamic scope if the Context is so initialized.
String source = "var x = 'sharedScope';" +
"function f() { return x; }";
cx.evaluateString(scope, source, "MySource", 1, null);
// Now we can execute the precompiled script against the scope
// to define x variable and f function in the shared scope.
script.exec(cx, sharedScope);
// Now we spawn some threads that execute a script that calls the
// function 'f'. The scope chain looks like this:
// <pre>
// ------------------
// | shared scope |
// ------------------
// ------------------ ------------------
// | per-thread scope | -prototype-> | shared scope |
// ------------------ ------------------
// ^
// |
// ------------------
// | per-thread scope |
// ------------------
// ^
// parentScope
// |
// ------------------
// | f's activation |
@ -112,9 +141,13 @@ public class DynamicScopes {
final int threadCount = 3;
Thread[] t = new Thread[threadCount];
for (int i=0; i < threadCount; i++) {
String script = "function g() { var x = 'local'; return f(); }" +
"java.lang.System.out.println(g());";
t[i] = new Thread(new PerThread(scope, script,
String source2 = ""
+"function g() { var x = 'local'; return f(); }\n"
+"java.lang.System.out.println(g());\n"
+"function g2() { var x = 'local'; return closure(); }\n"
+"java.lang.System.out.println(g2());\n"
+"";
t[i] = new Thread(new PerThread(sharedScope, source2,
"thread" + i));
}
for (int i=0; i < threadCount; i++)
@ -131,9 +164,9 @@ public class DynamicScopes {
static class PerThread implements Runnable {
PerThread(Scriptable scope, String script, String x) {
this.scope = scope;
this.script = script;
PerThread(Scriptable sharedScope, String source, String x) {
this.sharedScope = sharedScope;
this.source = source;
this.x = x;
}
@ -142,8 +175,8 @@ public class DynamicScopes {
Context cx = Context.enter();
try {
// We can share the scope.
Scriptable threadScope = cx.newObject(scope);
threadScope.setPrototype(scope);
Scriptable threadScope = cx.newObject(sharedScope);
threadScope.setPrototype(sharedScope);
// We want "threadScope" to be a new top-level
// scope, so set its parent scope to null. This
@ -154,7 +187,7 @@ public class DynamicScopes {
// Create a JavaScript property of the thread scope named
// 'x' and save a value for it.
threadScope.put("x", threadScope, x);
cx.evaluateString(threadScope, script, "threadScript", 1, null);
cx.evaluateString(threadScope, source, "threadScript", 1, null);
}
catch (JavaScriptException jse) {
// ignore
@ -163,8 +196,8 @@ public class DynamicScopes {
Context.exit();
}
}
private Scriptable scope;
private String script;
private Scriptable sharedScope;
private String source;
private String x;
}

Просмотреть файл

@ -59,7 +59,7 @@ public class CompilerEnvirons
{
setErrorReporter(cx.getErrorReporter());
this.languageVersion = cx.getLanguageVersion();
useDynamicScope = cx.hasCompileFunctionsWithDynamicScope();
useDynamicScope = cx.compileFunctionsWithDynamicScopeFlag;
generateDebugInfo = (!cx.isGeneratingDebugChanged()
|| cx.isGeneratingDebug());
reservedKeywordAsIdentifier
@ -112,11 +112,6 @@ public class CompilerEnvirons
return useDynamicScope;
}
public void setUseDynamicScope(boolean flag)
{
this.useDynamicScope = flag;
}
public final boolean isReservedKeywordAsIdentifier()
{
return reservedKeywordAsIdentifier;

Просмотреть файл

@ -211,9 +211,27 @@ public class Context
* By default {@link #hasFeature(int)} returns true if
* the current JS version is set to {@link #VERSION_DEFAULT}
* or is greater then {@link #VERSION_1_6}.
* @since 1.6 Release 1
*/
public static final int FEATURE_E4X = 6;
/**
* Control if dynamic scope should be used for name access.
* If hasFeature(FEATURE_DYNAMIC_SCOPE) returns true, then the name lookup
* during name resolution will use the top scope of the script or function
* which is at the top of JS execution stack instead of the top scope of the
* script or function from the current stack frame if the top scope of
* the top stack frame contains the top scope of the current stack frame
* on its prototype chain.
* <p>
* This is useful to define shared scope containing functions that can
* be called from scripts and functions using private scopes.
* <p>
* By default {@link #hasFeature(int)} returns false.
* @since 1.6 Release 1
*/
public static final int FEATURE_DYNAMIC_SCOPE = 7;
public static final String languageVersionProperty = "language version";
public static final String errorReporterProperty = "error reporter";
@ -1913,13 +1931,9 @@ public class Context
}
/**
* Return whether functions are compiled by this context using
* dynamic scope.
* <p>
* If functions are compiled with dynamic scope, then they execute
* in the scope of their caller, rather than in their parent scope.
* This is useful for sharing functions across multiple scopes.
* @since 1.5 Release 1
* @deprecated Use {@link #hasFeature(int)} and
* {@link #FEATURE_DYNAMIC_SCOPE} to control dynamic scoping that also works
* with nested functions defined by functions in the shared scope.
*/
public final boolean hasCompileFunctionsWithDynamicScope()
{
@ -1927,11 +1941,9 @@ public class Context
}
/**
* Set whether functions compiled by this context should use
* dynamic scope.
* <p>
* @param flag if true, compile functions with dynamic scope
* @since 1.5 Release 1
* @deprecated Use {@link #hasFeature(int)} and
* {@link #FEATURE_DYNAMIC_SCOPE} to control dynamic scoping that also works
* with nested functions defined by functions in the shared scope.
*/
public final void setCompileFunctionsWithDynamicScope(boolean flag)
{
@ -2586,7 +2598,8 @@ public class Context
private boolean generatingDebug;
private boolean generatingDebugChanged;
private boolean generatingSource=true;
private boolean compileFunctionsWithDynamicScopeFlag;
boolean compileFunctionsWithDynamicScopeFlag;
boolean useDynamicScope;
private int optimizationLevel;
private WrapFactory wrapFactory;
Debugger debugger;

Просмотреть файл

@ -229,6 +229,9 @@ public class ContextFactory
version = cx.getLanguageVersion();
return (version == Context.VERSION_DEFAULT
|| version >= Context.VERSION_1_6);
case Context.FEATURE_DYNAMIC_SCOPE:
return false;
}
// It is a bug to call the method with unknown featureIndex
throw new IllegalArgumentException(String.valueOf(featureIndex));

Просмотреть файл

@ -1758,6 +1758,9 @@ public class ScriptRuntime {
private static Object topScopeName(Context cx, Scriptable scope,
String name)
{
if (cx.useDynamicScope) {
scope = locateDynamicScope(cx, scope);
}
return ScriptableObject.getProperty(scope, name);
}
@ -1813,6 +1816,10 @@ public class ScriptRuntime {
}
}
}
// scope here is top scope
if (cx.useDynamicScope) {
scope = locateDynamicScope(cx, scope);
}
if (ScriptableObject.hasProperty(scope, id)) {
return scope;
}
@ -1837,6 +1844,9 @@ public class ScriptRuntime {
// global object. Find the global object by
// walking up the scope chain.
bound = ScriptableObject.getTopLevelScope(scope);
if (cx.useDynamicScope) {
bound = locateDynamicScope(cx, bound);
}
bound.put(id, bound, value);
/*
This code is causing immense performance problems in
@ -2848,6 +2858,26 @@ public class ScriptRuntime {
return (cx.topCallScope != null);
}
private static Scriptable locateDynamicScope(Context cx, Scriptable scope)
{
// Return cx.topCallScope is scope is present on its prototype chain
// and return scope otherwise.
// Should only be called when scope is top scope.
if (cx.topCallScope == scope) {
return scope;
}
Scriptable proto = cx.topCallScope;
for (;;) {
proto = proto.getPrototype();
if (proto == scope) {
return cx.topCallScope;
}
if (proto == null) {
return scope;
}
}
}
public static Object doTopCall(Callable callable,
Context cx, Scriptable scope,
Scriptable thisObj, Object[] args)
@ -2857,6 +2887,7 @@ public class ScriptRuntime {
Object result;
cx.topCallScope = ScriptableObject.getTopLevelScope(scope);
cx.useDynamicScope = cx.hasFeature(Context.FEATURE_DYNAMIC_SCOPE);
try {
result = callable.call(cx, scope, thisObj, args);
} finally {