/* * CallFrame.java * * Copyright (c) 1997 Cornell University. * Copyright (c) 1997-1998 Sun Microsystems, Inc. * * See the file "license.terms" for information on usage and * redistribution of this file, and for a DISCLAIMER OF ALL * WARRANTIES. * * Included in SQLite3 port to C# for use in testharness only; 2008 Noah B Hart * * RCS @(#) $Id: CallFrame.java,v 1.10 2003/01/08 02:10:17 mdejong Exp $ * */ using System.Collections; using System.Text; namespace tcl.lang { /// This class implements a frame in the call stack. /// /// This class can be overridden to define new variable scoping rules for /// the Tcl interpreter. /// public class CallFrame { internal ArrayList VarNames { // FIXME : need to port Tcl 8.1 implementation here get { ArrayList vector = new ArrayList( 10 ); if ( varTable == null ) { return vector; } for ( IEnumerator e1 = varTable.Values.GetEnumerator(); e1.MoveNext(); ) { Var v = (Var)e1.Current; if ( !v.isVarUndefined() ) { vector.Add( v.hashKey ); } } return vector; } } /// an Vector the names of the (defined) local variables /// in this CallFrame (excluding upvar's) /// internal ArrayList LocalVarNames { get { ArrayList vector = new ArrayList( 10 ); if ( varTable == null ) { return vector; } for ( IEnumerator e1 = varTable.Values.GetEnumerator(); e1.MoveNext(); ) { Var v = (Var)e1.Current; if ( !v.isVarUndefined() && !v.isVarLink() ) { vector.Add( v.hashKey ); } } return vector; } } /// The interpreter associated with this call frame. protected internal Interp interp; /// The Namespace this CallFrame is executing in. /// Used to resolve commands and global variables. /// internal NamespaceCmd.Namespace ns; /// If true, the frame was pushed to execute a Tcl procedure /// and may have local vars. If false, the frame was pushed to execute /// a namespace command and var references are treated as references /// to namespace vars; varTable is ignored. /// internal bool isProcCallFrame; /// Stores the arguments of the procedure associated with this CallFrame. /// Is null for global level. /// internal TclObject[] objv; /// Value of interp.frame when this procedure was invoked /// (i.e. next in stack of all active procedures). /// protected internal CallFrame caller; /// Value of interp.varFrame when this procedure was invoked /// (i.e. determines variable scoping within caller; same as /// caller unless an "uplevel" command or something equivalent /// was active in the caller). /// protected internal CallFrame callerVar; /// Level of recursion. = 0 for the global level. protected internal int level; /// Stores the variables of this CallFrame. protected internal Hashtable varTable; /// Creates a CallFrame for the global variables. /// current interpreter. /// internal CallFrame( Interp i ) { interp = i; ns = i.globalNs; varTable = new Hashtable(); caller = null; callerVar = null; objv = null; level = 0; isProcCallFrame = true; } /// Creates a CallFrame. It changes the following variables: /// /// /// /// current interpreter. /// /// the procedure to invoke in this call frame. /// /// the arguments to the procedure. /// /// TclException if error occurs in parameter bindings. /// internal CallFrame( Interp i, Procedure proc, TclObject[] objv ) : this( i ) { try { chain( proc, objv ); } catch ( TclException e ) { dispose(); throw; } } /// Chain this frame into the call frame stack and binds the parameters /// values to the formal parameters of the procedure. /// /// /// the procedure. /// /// argv the parameter values. /// /// TclException if wrong number of arguments. /// internal void chain( Procedure proc, TclObject[] objv ) { // FIXME: double check this ns thing in case where proc is renamed to different ns. this.ns = proc.ns; this.objv = objv; // FIXME : quick level hack : fix later level = ( interp.varFrame == null ) ? 1 : ( interp.varFrame.level + 1 ); caller = interp.frame; callerVar = interp.varFrame; interp.frame = this; interp.varFrame = this; // parameter bindings int numArgs = proc.argList.Length; if ( ( !proc.isVarArgs ) && ( objv.Length - 1 > numArgs ) ) { wrongNumProcArgs( objv[0], proc ); } int i, j; for ( i = 0, j = 1; i < numArgs; i++, j++ ) { // Handle the special case of the last formal being // "args". When it occurs, assign it a list consisting of // all the remaining actual arguments. TclObject varName = proc.argList[i][0]; TclObject value = null; if ( ( i == ( numArgs - 1 ) ) && proc.isVarArgs ) { value = TclList.newInstance(); value.preserve(); for ( int k = j; k < objv.Length; k++ ) { TclList.append( interp, value, objv[k] ); } interp.setVar( varName, value, 0 ); value.release(); } else { if ( j < objv.Length ) { value = objv[j]; } else if ( proc.argList[i][1] != null ) { value = proc.argList[i][1]; } else { wrongNumProcArgs( objv[0], proc ); } interp.setVar( varName, value, 0 ); } } } private string wrongNumProcArgs( TclObject name, Procedure proc ) { int i; StringBuilder sbuf = new StringBuilder( 200 ); sbuf.Append( "wrong # args: should be \"" ); sbuf.Append( name.ToString() ); for ( i = 0; i < proc.argList.Length; i++ ) { TclObject arg = proc.argList[i][0]; TclObject def = proc.argList[i][1]; sbuf.Append( " " ); if ( def != null ) sbuf.Append( "?" ); sbuf.Append( arg.ToString() ); if ( def != null ) sbuf.Append( "?" ); } sbuf.Append( "\"" ); throw new TclException( interp, sbuf.ToString() ); } /// the name of the variable. /// /// /// true if a variable exists and is defined inside this /// CallFrame, false otherwise /// internal static bool exists( Interp interp, string name ) { try { Var[] result = Var.lookupVar( interp, name, null, 0, "lookup", false, false ); if ( result == null ) { return false; } if ( result[0].isVarUndefined() ) { return false; } return true; } catch ( TclException e ) { throw new TclRuntimeError( "unexpected TclException: " + e.Message, e ); } } /// an Vector the names of the (defined) variables /// in this CallFrame. /// /// Tcl_GetFrame -> getFrame /// /// Given a description of a procedure frame, such as the first /// argument to an "uplevel" or "upvar" command, locate the /// call frame for the appropriate level of procedure. /// /// The return value is 1 if string was either a number or a number /// preceded by "#" and it specified a valid frame. 0 is returned /// if string isn't one of the two things above (in this case, /// the lookup acts as if string were "1"). The frameArr[0] reference /// will be filled by the reference of the desired frame (unless an /// error occurs, in which case it isn't modified). /// /// /// a string that specifies the level. /// /// TclException if s is a valid level specifier but /// refers to a bad level that doesn't exist. /// internal static int getFrame( Interp interp, string inString, CallFrame[] frameArr ) { int curLevel, level, result; CallFrame frame; // Parse string to figure out which level number to go to. result = 1; curLevel = ( interp.varFrame == null ) ? 0 : interp.varFrame.level; if ( ( inString.Length > 0 ) && ( inString[0] == '#' ) ) { level = Util.getInt( interp, inString.Substring( 1 ) ); if ( level < 0 ) { throw new TclException( interp, "bad level \"" + inString + "\"" ); } } else if ( ( inString.Length > 0 ) && System.Char.IsDigit( inString[0] ) ) { level = Util.getInt( interp, inString ); level = curLevel - level; } else { level = curLevel - 1; result = 0; } // FIXME: is this a bad comment from some other proc? // Figure out which frame to use, and modify the interpreter so // its variables come from that frame. if ( level == 0 ) { frame = null; } else { for ( frame = interp.varFrame; frame != null; frame = frame.callerVar ) { if ( frame.level == level ) { break; } } if ( frame == null ) { throw new TclException( interp, "bad level \"" + inString + "\"" ); } } frameArr[0] = frame; return result; } /// This method is called when this CallFrame is no longer needed. /// Removes the reference of this object from the interpreter so /// that this object can be garbage collected. ///

/// For this procedure to work correctly, it must not be possible /// for any of the variable in the table to be accessed from Tcl /// commands (e.g. from trace procedures). ///

protected internal void dispose() { // Unchain this frame from the call stack. interp.frame = caller; interp.varFrame = callerVar; caller = null; callerVar = null; if ( varTable != null ) { Var.deleteVars( interp, varTable ); varTable.Clear(); varTable = null; } } } }