/*
* Copyright (c) 1994, 2003, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package sun.tools.tree;
import sun.tools.java.*;
import sun.tools.asm.Assembler;
WARNING: The contents of this source file are not part of any
supported API. Code that depends on them does so at its own risk:
they are subject to change or removal without notice.
/**
* WARNING: The contents of this source file are not part of any
* supported API. Code that depends on them does so at its own risk:
* they are subject to change or removal without notice.
*/
public
class Context implements Constants {
Context prev;
Node node;
int varNumber;
LocalMember locals;
LocalMember classes;
MemberDefinition field;
int scopeNumber;
int frameNumber;
Create the initial context for a method
The incoming context is inherited from
/**
* Create the initial context for a method
* The incoming context is inherited from
*/
public Context(Context ctx, MemberDefinition field) {
this.field = field;
if (ctx == null) {
this.frameNumber = 1;
this.scopeNumber = 2;
this.varNumber = 0;
} else {
this.prev = ctx;
this.locals = ctx.locals;
this.classes = ctx.classes;
if (field != null &&
(field.isVariable() || field.isInitializer())) {
// Variables and initializers are inlined into a constructor.
// Model this by inheriting the frame number of the parent,
// which will contain a "this" parameter.
this.frameNumber = ctx.frameNumber;
this.scopeNumber = ctx.scopeNumber + 1;
} else {
this.frameNumber = ctx.scopeNumber + 1;
this.scopeNumber = this.frameNumber + 1;
}
this.varNumber = ctx.varNumber;
}
}
Create a new context, for initializing a class.
/**
* Create a new context, for initializing a class.
*/
public Context(Context ctx, ClassDefinition c) {
this(ctx, (MemberDefinition)null);
}
Create a new nested context, for a block statement
/**
* Create a new nested context, for a block statement
*/
Context(Context ctx, Node node) {
if (ctx == null) {
this.frameNumber = 1;
this.scopeNumber = 2;
this.varNumber = 0;
} else {
this.prev = ctx;
this.locals = ctx.locals;
// Inherit local classes from surrounding block,
// just as for local variables. Fixes 4074421.
this.classes = ctx.classes;
this.varNumber = ctx.varNumber;
this.field = ctx.field;
this.frameNumber = ctx.frameNumber;
this.scopeNumber = ctx.scopeNumber + 1;
this.node = node;
}
}
public Context(Context ctx) {
this(ctx, (Node)null);
}
Declare local
/**
* Declare local
*/
public int declare(Environment env, LocalMember local) {
//System.out.println( "DECLARE= " + local.getName() + "=" + varNumber + ", read=" + local.readcount + ", write=" + local.writecount + ", hash=" + local.hashCode());
local.scopeNumber = scopeNumber;
if (this.field == null && idThis.equals(local.getName())) {
local.scopeNumber += 1; // Anticipate variable or initializer.
}
if (local.isInnerClass()) {
local.prev = classes;
classes = local;
return 0;
}
// Originally the statement:
//
// local.subModifiers(M_INLINEABLE);
//
// was here with the comment:
//
// // prevent inlining across call sites
//
// This statement prevented constant local variables from
// inlining. It didn't seem to do anything useful.
//
// The statement has been removed and an assertion has been
// added which mandates that the only members which are marked
// with M_INLINEABLE are the ones for which isConstant() is true.
// (Fix for 4106244.)
//
// Addition to the above comment: they might also be
// final variables initialized with 'this', 'super', or other
// final identifiers. See VarDeclarationStatement.inline().
// So I've removed the assertion. The original subModifiers
// call appears to have been there to fix nested class translation
// breakage, which has been fixed in VarDeclarationStatement
// now instead. (Fix for 4073244.)
local.prev = locals;
locals = local;
local.number = varNumber;
varNumber += local.getType().stackSize();
return local.number;
}
Get a local variable by name
/**
* Get a local variable by name
*/
public
LocalMember getLocalField(Identifier name) {
for (LocalMember f = locals ; f != null ; f = f.prev) {
if (name.equals(f.getName())) {
return f;
}
}
return null;
}
Get the scope number for a reference to a member of this class
(Larger scope numbers are more deeply nested.)
See Also: - scopeNumber.scopeNumber
/**
* Get the scope number for a reference to a member of this class
* (Larger scope numbers are more deeply nested.)
* @see LocalMember#scopeNumber
*/
public
int getScopeNumber(ClassDefinition c) {
for (Context ctx = this; ctx != null; ctx = ctx.prev) {
if (ctx.field == null) continue;
if (ctx.field.getClassDefinition() == c) {
return ctx.frameNumber;
}
}
return -1;
}
private
MemberDefinition getFieldCommon(Environment env, Identifier name,
boolean apparentOnly) throws AmbiguousMember, ClassNotFound {
// Note: This is structured as a pair of parallel lookups.
// If we were to redesign Context, we might prefer to walk
// along a single chain of scopes.
LocalMember lf = getLocalField(name);
int ls = (lf == null) ? -2 : lf.scopeNumber;
ClassDefinition thisClass = field.getClassDefinition();
// Also look for a class member in a shallower scope.
for (ClassDefinition c = thisClass;
c != null;
c = c.getOuterClass()) {
MemberDefinition f = c.getVariable(env, name, thisClass);
if (f != null && getScopeNumber(c) > ls) {
if (apparentOnly && f.getClassDefinition() != c) {
continue;
}
return f;
}
}
return lf;
}
Assign a number to a class field.
(This is used to track definite assignment of some blank finals.)
/**
* Assign a number to a class field.
* (This is used to track definite assignment of some blank finals.)
*/
public int declareFieldNumber(MemberDefinition field) {
return declare(null, new LocalMember(field));
}
Retrieve a number previously assigned by declareMember().
Return -1 if there was no such assignment in this context.
/**
* Retrieve a number previously assigned by declareMember().
* Return -1 if there was no such assignment in this context.
*/
public int getFieldNumber(MemberDefinition field) {
for (LocalMember f = locals ; f != null ; f = f.prev) {
if (f.getMember() == field) {
return f.number;
}
}
return -1;
}
Return the local field or member field corresponding to a number.
Return null if there is no such field.
/**
* Return the local field or member field corresponding to a number.
* Return null if there is no such field.
*/
public MemberDefinition getElement(int number) {
for (LocalMember f = locals ; f != null ; f = f.prev) {
if (f.number == number) {
MemberDefinition field = f.getMember();
return (field != null) ? field : f;
}
}
return null;
}
Get a local class by name
/**
* Get a local class by name
*/
public
LocalMember getLocalClass(Identifier name) {
for (LocalMember f = classes ; f != null ; f = f.prev) {
if (name.equals(f.getName())) {
return f;
}
}
return null;
}
private
MemberDefinition getClassCommon(Environment env, Identifier name,
boolean apparentOnly) throws ClassNotFound {
LocalMember lf = getLocalClass(name);
int ls = (lf == null) ? -2 : lf.scopeNumber;
// Also look for a class member in a shallower scope.
for (ClassDefinition c = field.getClassDefinition();
c != null;
c = c.getOuterClass()) {
// QUERY: We may need to get the inner class from a
// superclass of 'c'. This call is prepared to
// resolve the superclass if necessary. Can we arrange
// to assure that it is always previously resolved?
// This is one of a small number of problematic calls that
// requires 'getSuperClass' to resolve superclasses on demand.
// See 'ClassDefinition.getInnerClass(env, nm)'.
MemberDefinition f = c.getInnerClass(env, name);
if (f != null && getScopeNumber(c) > ls) {
if (apparentOnly && f.getClassDefinition() != c) {
continue;
}
return f;
}
}
return lf;
}
Get either a local variable, or a field in a current class
/**
* Get either a local variable, or a field in a current class
*/
public final
MemberDefinition getField(Environment env, Identifier name) throws AmbiguousMember, ClassNotFound {
return getFieldCommon(env, name, false);
}
Like getField, except that it skips over inherited fields.
Used for error checking.
/**
* Like getField, except that it skips over inherited fields.
* Used for error checking.
*/
public final
MemberDefinition getApparentField(Environment env, Identifier name) throws AmbiguousMember, ClassNotFound {
return getFieldCommon(env, name, true);
}
Check if the given field is active in this context.
/**
* Check if the given field is active in this context.
*/
public boolean isInScope(LocalMember field) {
for (LocalMember f = locals ; f != null ; f = f.prev) {
if (field == f) {
return true;
}
}
return false;
}
Notice a reference (usually an uplevel one).
Update the references list of every enclosing class
which is enclosed by the scope of the target.
Update decisions about which uplevels to make into fields.
Return the uplevel reference descriptor, or null if it's local.
The target must be in scope in this context.
So, call this method only from the check phase.
(In other phases, the context may be less complete.)
This can and should be called both before and after classes are frozen.
It should be a no-op, and will raise a compiler error if not.
/**
* Notice a reference (usually an uplevel one).
* Update the references list of every enclosing class
* which is enclosed by the scope of the target.
* Update decisions about which uplevels to make into fields.
* Return the uplevel reference descriptor, or null if it's local.
* <p>
* The target must be in scope in this context.
* So, call this method only from the check phase.
* (In other phases, the context may be less complete.)
* <p>
* This can and should be called both before and after classes are frozen.
* It should be a no-op, and will raise a compiler error if not.
*/
public UplevelReference noteReference(Environment env, LocalMember target) {
int targetScopeNumber = !isInScope(target) ? -1 : target.scopeNumber;
// Walk outward visiting each scope.
// Note each distinct frame (i.e., enclosing method).
// For each frame in which the variable is uplevel,
// record the event in the references list of the enclosing class.
UplevelReference res = null;
int currentFrameNumber = -1;
for (Context refctx = this; refctx != null; refctx = refctx.prev) {
if (currentFrameNumber == refctx.frameNumber) {
continue; // we're processing frames, not contexts
}
currentFrameNumber = refctx.frameNumber;
if (targetScopeNumber >= currentFrameNumber) {
break; // the target is native to this frame
}
// process a frame which is using this variable as an uplevel
ClassDefinition refc = refctx.field.getClassDefinition();
UplevelReference r = refc.getReference(target);
r.noteReference(env, refctx);
// remember the reference pertaining to the innermost frame
if (res == null) {
res = r;
}
}
return res;
}
Implement a reference (usually an uplevel one).
Call noteReference() first, to make sure the reference
lists are up to date.
The resulting expression tree does not need checking;
it can be code-generated right away.
If the reference is not uplevel, the result is an IDENT or THIS.
/**
* Implement a reference (usually an uplevel one).
* Call noteReference() first, to make sure the reference
* lists are up to date.
* <p>
* The resulting expression tree does not need checking;
* it can be code-generated right away.
* If the reference is not uplevel, the result is an IDENT or THIS.
*/
public Expression makeReference(Environment env, LocalMember target) {
UplevelReference r = noteReference(env, target);
// Now create a referencing expression.
if (r != null) {
return r.makeLocalReference(env, this);
} else if (idThis.equals(target.getName())) {
return new ThisExpression(0, target);
} else {
return new IdentifierExpression(0, target);
}
}
Return a local expression which can serve as the base reference
for the given field. If the field is a constructor, return an
expression for the implicit enclosing instance argument.
Return null if there is no need for such an argument,
or if there was an error.
/**
* Return a local expression which can serve as the base reference
* for the given field. If the field is a constructor, return an
* expression for the implicit enclosing instance argument.
* <p>
* Return null if there is no need for such an argument,
* or if there was an error.
*/
public Expression findOuterLink(Environment env, long where,
MemberDefinition f) {
// reqc is the base pointer type required to use f
ClassDefinition fc = f.getClassDefinition();
ClassDefinition reqc = f.isStatic() ? null
: !f.isConstructor() ? fc
: fc.isTopLevel() ? null
: fc.getOuterClass();
if (reqc == null) {
return null;
}
return findOuterLink(env, where, reqc, f, false);
}
private static boolean match(Environment env,
ClassDefinition thisc, ClassDefinition reqc) {
try {
return thisc == reqc
|| reqc.implementedBy(env, thisc.getClassDeclaration());
} catch (ClassNotFound ee) {
return false;
}
}
public Expression findOuterLink(Environment env, long where,
ClassDefinition reqc,
MemberDefinition f,
boolean needExactMatch) {
if (field.isStatic()) {
if (f == null) {
// say something like: undefined variable A.this
Identifier nm = reqc.getName().getFlatName().getName();
env.error(where, "undef.var", Identifier.lookup(nm,idThis));
} else if (f.isConstructor()) {
env.error(where, "no.outer.arg", reqc, f.getClassDeclaration());
} else if (f.isMethod()) {
env.error(where, "no.static.meth.access",
f, f.getClassDeclaration());
} else {
env.error(where, "no.static.field.access", f.getName(),
f.getClassDeclaration());
}
// This is an attempt at error recovery.
// Unfortunately, the constructor may throw
// a null pointer exception after failing to resolve
// 'idThis'. Since an error message has already been
// issued previously, this exception is caught and
// silently ignored. Ideally, we should avoid throwing
// the exception.
Expression e = new ThisExpression(where, this);
e.type = reqc.getType();
return e;
}
// use lp to scan for current instances (locals named "this")
LocalMember lp = locals;
// thise is a link expression being built up
Expression thise = null;
// root is the local variable (idThis) at the far left of thise
LocalMember root = null;
// thisc is the class of the link expression thise
ClassDefinition thisc = null;
// conCls is the class of the "this", in a constructor
ClassDefinition conCls = null;
if (field.isConstructor()) {
conCls = field.getClassDefinition();
}
if (!field.isMethod()) {
thisc = field.getClassDefinition();
thise = new ThisExpression(where, this);
}
while (true) {
if (thise == null) {
// start fresh from lp
while (lp != null && !idThis.equals(lp.getName())) {
lp = lp.prev;
}
if (lp == null) {
break;
}
thise = new ThisExpression(where, lp);
thisc = lp.getClassDefinition();
root = lp;
lp = lp.prev;
}
// Require exact class identity when called with
// 'needExactMatch' true. This is done when checking
// the '<class>.this' syntax. Fixes 4102393 and 4133457.
if (thisc == reqc ||
(!needExactMatch && match(env, thisc, reqc))) {
break;
}
// move out one step, if the current instance has an outer link
MemberDefinition outerMember = thisc.findOuterMember();
if (outerMember == null) {
thise = null;
continue; // try to find more help in lp
}
ClassDefinition prevc = thisc;
thisc = prevc.getOuterClass();
if (prevc == conCls) {
// Must pick up "this$C" from the constructor argument,
// not from "this.this$C", since the latter may not be
// initialized properly. (This way is cheaper too.)
Identifier nm = outerMember.getName();
IdentifierExpression arg = new IdentifierExpression(where, nm);
arg.bind(env, this);
thise = arg;
} else {
thise = new FieldExpression(where, thise, outerMember);
}
}
if (thise != null) {
// mark crossed scopes
// ?????
//ensureAvailable(root);
return thise;
}
if (f == null) {
// say something like: undefined variable A.this
Identifier nm = reqc.getName().getFlatName().getName();
env.error(where, "undef.var", Identifier.lookup(nm,idThis));
} else if (f.isConstructor()) {
env.error(where, "no.outer.arg", reqc, f.getClassDefinition());
} else {
env.error(where, "no.static.field.access", f, field);
}
// avoid floodgating:
Expression e = new ThisExpression(where, this);
e.type = reqc.getType();
return e;
}
Is there a "this" of type reqc in scope?
/**
* Is there a "this" of type reqc in scope?
*/
public static boolean outerLinkExists(Environment env,
ClassDefinition reqc,
ClassDefinition thisc) {
while (!match(env, thisc, reqc)) {
if (thisc.isTopLevel()) {
return false;
}
thisc = thisc.getOuterClass();
}
return true;
}
From which enclosing class do members of this type come?
/**
* From which enclosing class do members of this type come?
*/
public ClassDefinition findScope(Environment env, ClassDefinition reqc) {
ClassDefinition thisc = field.getClassDefinition();
while (thisc != null && !match(env, thisc, reqc)) {
thisc = thisc.getOuterClass();
}
return thisc;
}
Resolve a type name from within a local scope.
See Also: - resolveName.resolveName
/**
* Resolve a type name from within a local scope.
* @see Environment#resolveName
*/
Identifier resolveName(Environment env, Identifier name) {
// This logic is pretty much exactly parallel to that of
// Environment.resolveName().
if (name.isQualified()) {
// Try to resolve the first identifier component,
// because inner class names take precedence over
// package prefixes. (Cf. Environment.resolveName.)
Identifier rhead = resolveName(env, name.getHead());
if (rhead.hasAmbigPrefix()) {
// The first identifier component refers to an
// ambiguous class. Limp on. We throw away the
// rest of the classname as it is irrelevant.
// (part of solution for 4059855).
return rhead;
}
if (!env.classExists(rhead)) {
return env.resolvePackageQualifiedName(name);
}
try {
return env.getClassDefinition(rhead).
resolveInnerClass(env, name.getTail());
} catch (ClassNotFound ee) {
// return partially-resolved name someone else can fail on
return Identifier.lookupInner(rhead, name.getTail());
}
}
// Look for an unqualified name in enclosing scopes.
try {
MemberDefinition f = getClassCommon(env, name, false);
if (f != null) {
return f.getInnerClass().getName();
}
} catch (ClassNotFound ee) {
// a missing superclass, or something catastrophic
}
// look in imports, etc.
return env.resolveName(name);
}
Return the name of a lexically apparent type,
skipping inherited members, and ignoring
the current pacakge and imports.
This is used for error checking.
/**
* Return the name of a lexically apparent type,
* skipping inherited members, and ignoring
* the current pacakge and imports.
* This is used for error checking.
*/
public
Identifier getApparentClassName(Environment env, Identifier name) {
if (name.isQualified()) {
// Try to resolve the first identifier component,
// because inner class names take precedence over
// package prefixes. (Cf. Environment.resolveName.)
Identifier rhead = getApparentClassName(env, name.getHead());
return (rhead == null) ? idNull
: Identifier.lookup(rhead,
name.getTail());
}
// Look for an unqualified name in enclosing scopes.
try {
MemberDefinition f = getClassCommon(env, name, true);
if (f != null) {
return f.getInnerClass().getName();
}
} catch (ClassNotFound ee) {
// a missing superclass, or something catastrophic
}
// the enclosing class name is the only apparent package member:
Identifier topnm = field.getClassDefinition().getTopClass().getName();
if (topnm.getName().equals(name)) {
return topnm;
}
return idNull;
}
Raise an error if a blank final was definitely unassigned
on entry to a loop, but has possibly been assigned on the
back-branch. If this is the case, the loop may be assigning
it multiple times.
/**
* Raise an error if a blank final was definitely unassigned
* on entry to a loop, but has possibly been assigned on the
* back-branch. If this is the case, the loop may be assigning
* it multiple times.
*/
public void checkBackBranch(Environment env, Statement loop,
Vset vsEntry, Vset vsBack) {
for (LocalMember f = locals ; f != null ; f = f.prev) {
if (f.isBlankFinal()
&& vsEntry.testVarUnassigned(f.number)
&& !vsBack.testVarUnassigned(f.number)) {
env.error(loop.where, "assign.to.blank.final.in.loop",
f.getName());
}
}
}
Check if a field can reach another field (only considers
forward references, not the access modifiers).
/**
* Check if a field can reach another field (only considers
* forward references, not the access modifiers).
*/
public boolean canReach(Environment env, MemberDefinition f) {
return field.canReach(env, f);
}
Get the context that corresponds to a label, return null if
not found.
/**
* Get the context that corresponds to a label, return null if
* not found.
*/
public
Context getLabelContext(Identifier lbl) {
for (Context ctx = this ; ctx != null ; ctx = ctx.prev) {
if ((ctx.node != null) && (ctx.node instanceof Statement)) {
if (((Statement)(ctx.node)).hasLabel(lbl))
return ctx;
}
}
return null;
}
Get the destination context of a break
/**
* Get the destination context of a break
*/
public
Context getBreakContext(Identifier lbl) {
if (lbl != null) {
return getLabelContext(lbl);
}
for (Context ctx = this ; ctx != null ; ctx = ctx.prev) {
if (ctx.node != null) {
switch (ctx.node.op) {
case SWITCH:
case FOR:
case DO:
case WHILE:
return ctx;
}
}
}
return null;
}
Get the destination context of a continue
/**
* Get the destination context of a continue
*/
public
Context getContinueContext(Identifier lbl) {
if (lbl != null) {
return getLabelContext(lbl);
}
for (Context ctx = this ; ctx != null ; ctx = ctx.prev) {
if (ctx.node != null) {
switch (ctx.node.op) {
case FOR:
case DO:
case WHILE:
return ctx;
}
}
}
return null;
}
Get the destination context of a return (the method body)
/**
* Get the destination context of a return (the method body)
*/
public
CheckContext getReturnContext() {
for (Context ctx = this ; ctx != null ; ctx = ctx.prev) {
// The METHOD node is set up by Statement.checkMethod().
if (ctx.node != null && ctx.node.op == METHOD) {
return (CheckContext)ctx;
}
}
return null;
}
Get the context of the innermost surrounding try-block.
Consider only try-blocks contained within the same method.
(There could be others when searching from within a method
of a local class, but they are irrelevant to our purpose.)
This is used for recording DA/DU information preceding
all abnormal transfers of control: break, continue, return,
and throw.
/**
* Get the context of the innermost surrounding try-block.
* Consider only try-blocks contained within the same method.
* (There could be others when searching from within a method
* of a local class, but they are irrelevant to our purpose.)
* This is used for recording DA/DU information preceding
* all abnormal transfers of control: break, continue, return,
* and throw.
*/
public
CheckContext getTryExitContext() {
for (Context ctx = this;
ctx != null && ctx.node != null && ctx.node.op != METHOD;
ctx = ctx.prev) {
if (ctx.node.op == TRY) {
return (CheckContext)ctx;
}
}
return null;
}
Get the nearest inlined context
/**
* Get the nearest inlined context
*/
Context getInlineContext() {
for (Context ctx = this ; ctx != null ; ctx = ctx.prev) {
if (ctx.node != null) {
switch (ctx.node.op) {
case INLINEMETHOD:
case INLINENEWINSTANCE:
return ctx;
}
}
}
return null;
}
Get the context of a field that is being inlined
/**
* Get the context of a field that is being inlined
*/
Context getInlineMemberContext(MemberDefinition field) {
for (Context ctx = this ; ctx != null ; ctx = ctx.prev) {
if (ctx.node != null) {
switch (ctx.node.op) {
case INLINEMETHOD:
if (((InlineMethodExpression)ctx.node).field.equals(field)) {
return ctx;
}
break;
case INLINENEWINSTANCE:
if (((InlineNewInstanceExpression)ctx.node).field.equals(field)) {
return ctx;
}
}
}
}
return null;
}
Remove variables from the vset set that are no longer part of
this context.
/**
* Remove variables from the vset set that are no longer part of
* this context.
*/
public final Vset removeAdditionalVars(Vset vset) {
return vset.removeAdditionalVars(varNumber);
}
public final int getVarNumber() {
return varNumber;
}
Return the number of the innermost current instance reference.
/**
* Return the number of the innermost current instance reference.
*/
public int getThisNumber() {
LocalMember thisf = getLocalField(idThis);
if (thisf != null
&& thisf.getClassDefinition() == field.getClassDefinition()) {
return thisf.number;
}
// this is a variable; there is no "this" (should not happen)
return varNumber;
}
Return the field containing the present context.
/**
* Return the field containing the present context.
*/
public final MemberDefinition getField() {
return field;
}
Extend an environment with the given context.
The resulting environment behaves the same as
the given one, except that resolveName() takes
into account local class names in this context.
/**
* Extend an environment with the given context.
* The resulting environment behaves the same as
* the given one, except that resolveName() takes
* into account local class names in this context.
*/
public static Environment newEnvironment(Environment env, Context ctx) {
return new ContextEnvironment(env, ctx);
}
}
final
class ContextEnvironment extends Environment {
Context ctx;
Environment innerEnv;
ContextEnvironment(Environment env, Context ctx) {
super(env, env.getSource());
this.ctx = ctx;
this.innerEnv = env;
}
public Identifier resolveName(Identifier name) {
return ctx.resolveName(innerEnv, name);
}
}