001: /*
002: * CallFrame.java
003: *
004: * Copyright (c) 1997 Cornell University.
005: * Copyright (c) 1997-1998 Sun Microsystems, Inc.
006: *
007: * See the file "license.terms" for information on usage and
008: * redistribution of this file, and for a DISCLAIMER OF ALL
009: * WARRANTIES.
010: *
011: * RCS: @(#) $Id: CallFrame.java,v 1.17 2006/03/27 00:06:42 mdejong Exp $
012: *
013: */
014:
015: package tcl.lang;
016:
017: import java.util.*;
018:
019: /**
020: * This class implements a frame in the call stack.
021: *
022: * This class can be overridden to define new variable scoping rules for
023: * the Tcl interpreter.
024: */
025:
026: public class CallFrame {
027: /**
028: * The interpreter associated with this call frame.
029: */
030:
031: Interp interp;
032:
033: /**
034: * The Namespace this CallFrame is executing in.
035: * Used to resolve commands and global variables.
036: */
037:
038: Namespace ns;
039:
040: /**
041: * If true, the frame was pushed to execute a Tcl procedure
042: * and may have local vars. If false, the frame was pushed to execute
043: * a namespace command and var references are treated as references
044: * to namespace vars; varTable is ignored.
045: */
046:
047: boolean isProcCallFrame;
048:
049: /**
050: * Stores the arguments of the procedure associated with this CallFrame.
051: * Is null for global level.
052: */
053:
054: TclObject[] objv;
055:
056: /**
057: * Value of interp.frame when this procedure was invoked
058: * (i.e. next in stack of all active procedures).
059: */
060:
061: CallFrame caller;
062:
063: /**
064: * Value of interp.varFrame when this procedure was invoked
065: * (i.e. determines variable scoping within caller; same as
066: * caller unless an "uplevel" command or something equivalent
067: * was active in the caller).
068: */
069:
070: CallFrame callerVar;
071:
072: /**
073: * Level of recursion. = 0 for the global level.
074: */
075:
076: int level;
077:
078: /**
079: * Stores the variables of this CallFrame.
080: */
081:
082: HashMap varTable;
083:
084: /**
085: * Array of local variables in a compiled proc frame.
086: * These include locals set in the proc, globals
087: * or other variable brought into the proc scope,
088: * and compiler generated aliases to globals.
089: * This array is always null for an interpreted proc.
090: * A compiled proc implementation known which variable
091: * is associated with each slot at compile time,
092: * so it is able to avoid a hashtable lookup each time
093: * the variable is accessed. Both scalar variables and
094: * array variables could appear in this array.
095: */
096:
097: Var[] compiledLocals;
098: String[] compiledLocalsNames;
099:
100: /**
101: * Creates a CallFrame for the global variables.
102: * @param interp current interpreter.
103: */
104:
105: CallFrame(Interp i) {
106: interp = i;
107: ns = i.globalNs;
108: varTable = null;
109: compiledLocals = null;
110: compiledLocalsNames = null;
111: caller = null;
112: callerVar = null;
113: objv = null;
114: level = 0;
115: isProcCallFrame = true;
116: }
117:
118: /**
119: * Creates a CallFrame. It changes the following variables:
120: *
121: * <ul>
122: * <li> this.caller
123: * <li> this.callerVar
124: * <li> interp.frame
125: * <li> interp.varFrame
126: * </ul>
127: * @param i current interpreter.
128: * @param proc the procedure to invoke in this call frame.
129: * @param objv the arguments to the procedure.
130: * @exception TclException if error occurs in parameter bindings.
131: */
132: CallFrame(Interp i, Procedure proc, TclObject[] objv)
133: throws TclException {
134: this (i);
135:
136: try {
137: chain(proc, objv);
138: } catch (TclException e) {
139: dispose();
140: throw e;
141: }
142: }
143:
144: /**
145: * Chain this frame into the call frame stack and binds the parameters
146: * values to the formal parameters of the procedure.
147: *
148: * @param proc the procedure.
149: * @param proc argv the parameter values.
150: * @exception TclException if wrong number of arguments.
151: */
152: void chain(Procedure proc, TclObject[] objv) throws TclException {
153: this .ns = proc.wcmd.ns;
154: this .objv = objv;
155: // FIXME : quick level hack : fix later
156: level = (interp.varFrame == null) ? 1
157: : (interp.varFrame.level + 1);
158: caller = interp.frame;
159: callerVar = interp.varFrame;
160: interp.frame = this ;
161: interp.varFrame = this ;
162:
163: // parameter bindings
164:
165: int numArgs = proc.argList.length;
166:
167: if ((!proc.isVarArgs) && (objv.length - 1 > numArgs)) {
168: wrongNumProcArgs(objv[0], proc);
169: }
170:
171: int i, j;
172: for (i = 0, j = 1; i < numArgs; i++, j++) {
173: // Handle the special case of the last formal being
174: // "args". When it occurs, assign it a list consisting of
175: // all the remaining actual arguments.
176:
177: TclObject varName = proc.argList[i][0];
178: TclObject value = null;
179:
180: if ((i == (numArgs - 1)) && proc.isVarArgs) {
181: value = TclList.newInstance();
182: value.preserve();
183: for (int k = j; k < objv.length; k++) {
184: TclList.append(interp, value, objv[k]);
185: }
186: interp.setVar(varName, value, 0);
187: value.release();
188: } else {
189: if (j < objv.length) {
190: value = objv[j];
191: } else if (proc.argList[i][1] != null) {
192: value = proc.argList[i][1];
193: } else {
194: wrongNumProcArgs(objv[0], proc);
195: }
196: interp.setVar(varName, value, 0);
197: }
198: }
199: }
200:
201: private String wrongNumProcArgs(TclObject name, Procedure proc)
202: throws TclException {
203: int i;
204: StringBuffer sbuf = new StringBuffer(200);
205: sbuf.append("wrong # args: should be \"");
206: sbuf.append(name.toString());
207: for (i = 0; i < proc.argList.length; i++) {
208: TclObject arg = proc.argList[i][0];
209: TclObject def = proc.argList[i][1];
210:
211: sbuf.append(" ");
212: if (def != null)
213: sbuf.append("?");
214: sbuf.append(arg.toString());
215: if (def != null)
216: sbuf.append("?");
217: }
218: sbuf.append("\"");
219: throw new TclException(interp, sbuf.toString());
220: }
221:
222: /**
223: * @param name the name of the variable.
224: *
225: * @return true if a variable exists and is defined inside this
226: * CallFrame, false otherwise
227: */
228:
229: static boolean exists(Interp interp, String name) {
230: try {
231: Var[] result = Var.lookupVar(interp, name, null, 0,
232: "lookup", false, false);
233: if (result == null) {
234: return false;
235: }
236: if (result[0].isVarUndefined()) {
237: return false;
238: }
239: return true;
240: } catch (TclException e) {
241: throw new TclRuntimeError("unexpected TclException: " + e);
242: }
243: }
244:
245: /**
246: * @return a List of the names of the (defined) variables
247: * in this CallFrame.
248: */
249:
250: // FIXME : need to port Tcl 8.1 implementation here
251: ArrayList getVarNames() {
252: ArrayList alist = new ArrayList();
253:
254: if (varTable == null) {
255: return alist;
256: }
257:
258: for (Iterator iter = varTable.entrySet().iterator(); iter
259: .hasNext();) {
260: Map.Entry entry = (Map.Entry) iter.next();
261: Var v = (Var) entry.getValue();
262: if (!v.isVarUndefined()) {
263: alist.add(v.hashKey);
264: }
265: }
266: return alist;
267: }
268:
269: /**
270: * @return a List of the names of the (defined) local variables
271: * in this CallFrame (excluding upvar's)
272: */
273:
274: ArrayList getLocalVarNames() {
275: ArrayList alist = new ArrayList();
276:
277: if (varTable == null) {
278: return alist;
279: }
280:
281: for (Iterator iter = varTable.entrySet().iterator(); iter
282: .hasNext();) {
283: Map.Entry entry = (Map.Entry) iter.next();
284: Var v = (Var) entry.getValue();
285: if (!v.isVarUndefined() && !v.isVarLink()) {
286: alist.add(v.hashKey);
287: }
288: }
289: return alist;
290: }
291:
292: /**
293: * Tcl_GetFrame -> getFrame
294: *
295: * Given a description of a procedure frame, such as the first
296: * argument to an "uplevel" or "upvar" command, locate the
297: * call frame for the appropriate level of procedure.
298: *
299: * The return value is 1 if string was either a number or a number
300: * preceded by "#" and it specified a valid frame. 0 is returned
301: * if string isn't one of the two things above (in this case,
302: * the lookup acts as if string were "1"). The frameArr[0] reference
303: * will be filled by the reference of the desired frame (unless an
304: * error occurs, in which case it isn't modified).
305: *
306: * @param string a string that specifies the level.
307:
308: * @exception TclException if s is a valid level specifier but
309: * refers to a bad level that doesn't exist.
310: */
311:
312: static int getFrame(Interp interp, String string,
313: CallFrame[] frameArr) throws TclException {
314: int curLevel, level, result;
315: CallFrame frame;
316:
317: // Parse string to figure out which level number to go to.
318:
319: result = 1;
320: curLevel = (interp.varFrame == null) ? 0
321: : interp.varFrame.level;
322:
323: if ((string.length() > 0) && (string.charAt(0) == '#')) {
324: level = Util.getInt(interp, string.substring(1));
325: if (level < 0) {
326: throw new TclException(interp, "bad level \"" + string
327: + "\"");
328: }
329: } else if ((string.length() > 0)
330: && Character.isDigit(string.charAt(0))) {
331: level = Util.getInt(interp, string);
332: level = curLevel - level;
333: } else {
334: level = curLevel - 1;
335: result = 0;
336: }
337:
338: // FIXME: is this a bad comment from some other proc?
339: // Figure out which frame to use, and modify the interpreter so
340: // its variables come from that frame.
341:
342: if (level == 0) {
343: frame = null;
344: } else {
345: for (frame = interp.varFrame; frame != null; frame = frame.callerVar) {
346: if (frame.level == level) {
347: break;
348: }
349: }
350: if (frame == null) {
351: throw new TclException(interp, "bad level \"" + string
352: + "\"");
353: }
354: }
355: frameArr[0] = frame;
356: return result;
357: }
358:
359: /**
360: * This method is called when this CallFrame is no longer needed.
361: * Removes the reference of this object from the interpreter so
362: * that this object can be garbage collected.
363: * <p>
364: * For this procedure to work correctly, it must not be possible
365: * for any of the variable in the table to be accessed from Tcl
366: * commands (e.g. from trace procedures).
367: */
368:
369: protected void dispose() {
370: // Unchain this frame from the call stack.
371:
372: interp.frame = caller;
373: interp.varFrame = callerVar;
374: caller = null;
375: callerVar = null;
376:
377: if (varTable != null) {
378: Var.deleteVars(interp, varTable);
379: varTable = null;
380: }
381: if (compiledLocals != null) {
382: Var.deleteVars(interp, compiledLocals);
383: compiledLocals = null;
384: compiledLocalsNames = null;
385: }
386: }
387:
388: }
|