зеркало из https://github.com/mozilla/pjs.git
Resolving bug 255891: new approach to dynamic scoping
This commit is contained in:
Родитель
229d07cac0
Коммит
d78af3b167
|
@ -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 {
|
||||
|
|
Загрузка…
Ссылка в новой задаче