0001: /*
0002: * ------------------------------------------------------------------------
0003: * PACKAGE: [incr Tcl]
0004: * DESCRIPTION: Object-Oriented Extensions to Tcl
0005: *
0006: * [incr Tcl] provides object-oriented extensions to Tcl, much as
0007: * C++ provides object-oriented extensions to C. It provides a means
0008: * of encapsulating related procedures together with their shared data
0009: * in a local namespace that is hidden from the outside world. It
0010: * promotes code re-use through inheritance. More than anything else,
0011: * it encourages better organization of Tcl applications through the
0012: * object-oriented paradigm, leading to code that is easier to
0013: * understand and maintain.
0014: *
0015: * This part handles ensembles, which support compound commands in Tcl.
0016: * The usual "info" command is an ensemble with parts like "info body"
0017: * and "info globals". Extension developers can extend commands like
0018: * "info" by adding their own parts to the ensemble.
0019: *
0020: * ========================================================================
0021: * AUTHOR: Michael J. McLennan
0022: * Bell Labs Innovations for Lucent Technologies
0023: * mmclennan@lucent.com
0024: * http://www.tcltk.com/itcl
0025: *
0026: * RCS: $Id: Ensemble.java,v 1.3 2006/01/26 19:49:18 mdejong Exp $
0027: * ========================================================================
0028: * Copyright (c) 1993-1998 Lucent Technologies, Inc.
0029: * ------------------------------------------------------------------------
0030: * See the file "license.itcl" for information on usage and redistribution
0031: * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
0032: */
0033:
0034: package itcl.lang;
0035:
0036: import tcl.lang.*;
0037:
0038: // Data used to represent an ensemble:
0039:
0040: class EnsemblePart {
0041: String name; // name of this part
0042: int minChars; // chars needed to uniquely identify part
0043: Command cmd; // command handling this part
0044: WrappedCommand wcmd; // wrapped for command
0045: String usage; // usage string describing syntax
0046: Ensemble ensemble; // ensemble containing this part
0047: }
0048:
0049: // Data shared by ensemble access commands and ensemble parser:
0050:
0051: class EnsembleParser implements AssocData {
0052: Interp master; // master interp containing ensembles
0053: Interp parser; // slave interp for parsing
0054: Ensemble ensData; // add parts to this ensemble
0055:
0056: public void disposeAssocData(Interp interp) {
0057: Ensemble.DeleteEnsParser(this , this .master);
0058: }
0059: }
0060:
0061: // This class defines a Tcl object type that takes the
0062: // place of a part name during ensemble invocations. When an
0063: // error occurs and the caller tries to print objv[0], it will
0064: // get a string that contains a complete path to the ensemble
0065: // part.
0066:
0067: class ItclEnsInvoc implements InternalRep /*, CommandWithDispose*/{
0068: EnsemblePart ensPart;
0069: TclObject chainObj;
0070:
0071: // Implement InternalRep interface
0072: // Note: SetEnsInvocFromAny is not used
0073:
0074: public InternalRep duplicate() {
0075: return Ensemble.DupEnsInvocInternalRep(this );
0076: }
0077:
0078: public void dispose() {
0079: Ensemble.FreeEnsInvocInternalRep(this );
0080: }
0081:
0082: public String toString() {
0083: return Ensemble.UpdateStringOfEnsInvoc(this );
0084: }
0085:
0086: public static TclObject newInstance() {
0087: return new TclObject(new ItclEnsInvoc());
0088: }
0089:
0090: /*
0091: // Implement CommandWithDispose interface
0092:
0093: public void cmdProc(Interp interp, TclObject argv[])
0094: throws TclException {}
0095:
0096: public void disposeCmd() {}
0097: */
0098: }
0099:
0100: // Data/Methods in Ensemble class
0101:
0102: class Ensemble {
0103: Interp interp; // interpreter containing this ensemble
0104: EnsemblePart[] parts; // list of parts in this ensemble
0105: int numParts; // number of parts in part list
0106: int maxParts; // current size of parts list
0107: WrappedCommand wcmd; // command representing this ensemble
0108: EnsemblePart parent; // parent part for sub-ensembles
0109:
0110: // NULL => toplevel ensemble
0111:
0112: // Helper function used throughout this module to parse
0113: // and Ensemble name into an array of String objects.
0114:
0115: static String[] SplitEnsemble(Interp interp, String ensName)
0116: throws TclException {
0117: TclObject list = TclString.newInstance(ensName);
0118: TclObject[] objArgv;
0119: String[] strArgv;
0120:
0121: objArgv = TclList.getElements(interp, list);
0122: strArgv = new String[objArgv.length];
0123:
0124: for (int i = 0; i < objArgv.length; i++) {
0125: strArgv[i] = objArgv[i].toString();
0126: }
0127:
0128: return strArgv;
0129: }
0130:
0131: // Helper function that merges an ensemble array back
0132: // into a valid Tcl list contained in a String.
0133:
0134: static String MergeEnsemble(Interp interp, String[] nameArgv,
0135: int nameArgc) throws TclException {
0136: TclObject list = TclList.newInstance();
0137:
0138: for (int i = 0; i < nameArgc; i++) {
0139: TclList.append(interp, list, TclString
0140: .newInstance(nameArgv[i]));
0141: }
0142:
0143: return list.toString();
0144: }
0145:
0146: /*
0147: *----------------------------------------------------------------------
0148: *
0149: * Itcl_EnsembleInit -> Ensemble.EnsembleInit
0150: *
0151: * Called when any interpreter is created to make sure that
0152: * things are properly set up for ensembles.
0153: *
0154: * Results:
0155: * None.
0156: *
0157: * Side effects:
0158: * On the first call, the "ensemble" object type is registered
0159: * with the Tcl interpreter.
0160: *
0161: *----------------------------------------------------------------------
0162: */
0163:
0164: static void EnsembleInit(Interp interp) // interpreter being initialized
0165: {
0166: // ItclEnsInvoc obj type need not be registered with Jacl
0167:
0168: interp.createCommand("::itcl::ensemble", new EnsembleCmd(null));
0169: }
0170:
0171: /*
0172: *----------------------------------------------------------------------
0173: *
0174: * Itcl_CreateEnsemble -> Ensemble.CreateEnsemble
0175: *
0176: * Creates an ensemble command, or adds a sub-ensemble to an
0177: * existing ensemble command. The ensemble name is a space-
0178: * separated list. The first word in the list is the command
0179: * name for the top-level ensemble. Other names do not have
0180: * commands associated with them; they are merely sub-ensembles
0181: * within the ensemble. So a name like "a::b::foo bar baz"
0182: * represents an ensemble command called "foo" in the namespace
0183: * "a::b" that has a sub-ensemble "bar", that has a sub-ensemble
0184: * "baz".
0185: *
0186: * If the name is a single word, then this procedure creates
0187: * a top-level ensemble and installs an access command for it.
0188: * If a command already exists with that name, it is deleted.
0189: *
0190: * If the name has more than one word, then the leading words
0191: * are treated as a path name for an existing ensemble. The
0192: * last word is treated as the name for a new sub-ensemble.
0193: * If an part already exists with that name, it is an error.
0194: *
0195: * Results:
0196: * Raises a TclException if anything goes wrong.
0197: *
0198: *----------------------------------------------------------------------
0199: */
0200:
0201: static void CreateEnsemble(Interp interp, // interpreter to be updated
0202: String ensName) // name of the new ensemble
0203: throws TclException {
0204: Ensemble parentEnsData = null;
0205: TclObject list;
0206: String[] nameArgv = null;
0207:
0208: // Split the ensemble name into its path components.
0209:
0210: try {
0211: nameArgv = SplitEnsemble(interp, ensName);
0212: } catch (TclException ex) {
0213: CreateEnsembleFailed(interp, ensName, ex);
0214: }
0215: if (nameArgv.length < 1) {
0216: TclException ex = new TclException(interp,
0217: "invalid ensemble name \"" + ensName + "\"");
0218: CreateEnsembleFailed(interp, ensName, ex);
0219: }
0220:
0221: // If there is more than one path component, then follow
0222: // the path down to the last component, to find the containing
0223: // ensemble.
0224:
0225: parentEnsData = null;
0226: if (nameArgv.length > 1) {
0227: try {
0228: parentEnsData = FindEnsemble(interp, nameArgv,
0229: nameArgv.length - 1);
0230: } catch (TclException ex) {
0231: CreateEnsembleFailed(interp, ensName, ex);
0232: }
0233:
0234: if (parentEnsData == null) {
0235: String pname = MergeEnsemble(interp, nameArgv,
0236: nameArgv.length - 1);
0237: TclException ex = new TclException(interp,
0238: "invalid ensemble name \"" + pname + "\"");
0239: CreateEnsembleFailed(interp, ensName, ex);
0240: }
0241: }
0242:
0243: // Create the ensemble.
0244:
0245: try {
0246: CreateEnsemble(interp, parentEnsData,
0247: nameArgv[nameArgv.length - 1]);
0248: } catch (TclException ex) {
0249: CreateEnsembleFailed(interp, ensName, ex);
0250: }
0251: }
0252:
0253: // Helper function used when CreateEnsemble fails
0254:
0255: static void CreateEnsembleFailed(Interp interp, String ensName,
0256: TclException ex) throws TclException {
0257: StringBuffer buffer = new StringBuffer(64);
0258:
0259: buffer.append("\n (while creating ensemble \"");
0260: buffer.append(ensName);
0261: buffer.append("\")");
0262: interp.addErrorInfo(buffer.toString());
0263:
0264: throw ex;
0265: }
0266:
0267: /*
0268: *----------------------------------------------------------------------
0269: *
0270: * Itcl_AddEnsemblePart -> Ensemble.AddEnsemblePart
0271: *
0272: * Adds a part to an ensemble which has been created by
0273: * Itcl_CreateEnsemble. Ensembles are addressed by name, as
0274: * described in Itcl_CreateEnsemble.
0275: *
0276: * If the ensemble already has a part with the specified name,
0277: * this procedure returns an error. Otherwise, it adds a new
0278: * part to the ensemble.
0279: *
0280: * Any client data specified is automatically passed to the
0281: * handling procedure whenever the part is invoked. It is
0282: * automatically destroyed by the deleteProc when the part is
0283: * deleted.
0284: *
0285: * Results:
0286: * Raises a TclException if anything goes wrong.
0287: *
0288: *----------------------------------------------------------------------
0289: */
0290:
0291: static void AddEnsemblePart(Interp interp, // interpreter to be updated
0292: String ensName, // ensemble containing this part
0293: String partName, // name of the new part
0294: String usageInfo, // usage info for argument list
0295: Command objCmd) // handling procedure for part
0296: throws TclException {
0297: String[] nameArgv = null;
0298: Ensemble ensData = null;
0299: EnsemblePart ensPart;
0300:
0301: // Parse the ensemble name and look for a containing ensemble.
0302:
0303: try {
0304: nameArgv = SplitEnsemble(interp, ensName);
0305: } catch (TclException ex) {
0306: AddEnsemblePartFailed(interp, ensName, ex);
0307: }
0308: try {
0309: ensData = FindEnsemble(interp, nameArgv, nameArgv.length);
0310: } catch (TclException ex) {
0311: AddEnsemblePartFailed(interp, ensName, ex);
0312: }
0313:
0314: if (ensData == null) {
0315: String pname = MergeEnsemble(interp, nameArgv,
0316: nameArgv.length);
0317: TclException ex = new TclException(interp,
0318: "invalid ensemble name \"" + pname + "\"");
0319: AddEnsemblePartFailed(interp, ensName, ex);
0320: }
0321:
0322: // Install the new part into the part list.
0323:
0324: try {
0325: ensPart = AddEnsemblePart(interp, ensData, partName,
0326: usageInfo, objCmd);
0327: } catch (TclException ex) {
0328: AddEnsemblePartFailed(interp, ensName, ex);
0329: }
0330: }
0331:
0332: // Helper function used when AddEnsemblePart fails
0333:
0334: static void AddEnsemblePartFailed(Interp interp, String ensName,
0335: TclException ex) throws TclException {
0336: StringBuffer buffer = new StringBuffer();
0337:
0338: buffer.append("\n (while adding to ensemble \"");
0339: buffer.append(ensName);
0340: buffer.append("\")");
0341: interp.addErrorInfo(buffer.toString());
0342:
0343: throw ex;
0344: }
0345:
0346: // Note: Itcl_GetEnsemblePart not ported since it seems to be unused
0347: // Note: Itcl_IsEnsemble not ported since it seems to be unused
0348:
0349: /*
0350: *----------------------------------------------------------------------
0351: *
0352: * Itcl_GetEnsembleUsage -> Ensemble.GetEnsembleUsage
0353: *
0354: * Returns a summary of all of the parts of an ensemble and
0355: * the meaning of their arguments. Each part is listed on
0356: * a separate line. Having this summary is sometimes useful
0357: * when building error messages for the "@error" handler in
0358: * an ensemble.
0359: *
0360: * Ensembles are accessed by name, as described in
0361: * Itcl_CreateEnsemble.
0362: *
0363: * Results:
0364: * If the ensemble is found, its usage information is appended
0365: * to the buffer argument and the function returns true.
0366: * If anything goes wrong, this procedure returns false;
0367: *
0368: * Side effects:
0369: * Buffer passed in is modified.
0370: *
0371: *----------------------------------------------------------------------
0372: */
0373:
0374: static boolean GetEnsembleUsage(Interp interp, // interpreter containing the ensemble
0375: String ensName, // name of the ensemble
0376: StringBuffer buffer) // returns: summary of usage info
0377: {
0378: String[] nameArgv = null;
0379: Ensemble ensData;
0380: Itcl_InterpState state;
0381: String retval;
0382:
0383: // Parse the ensemble name and look for the ensemble.
0384: // Save the interpreter state before we do this. If we get
0385: // any errors, we don't want them to affect the interpreter.
0386:
0387: state = Util.SaveInterpState(interp, 0);
0388:
0389: try {
0390: nameArgv = SplitEnsemble(interp, ensName);
0391: } catch (TclException ex) {
0392: Util.RestoreInterpState(interp, state);
0393: return false;
0394: }
0395:
0396: try {
0397: ensData = FindEnsemble(interp, nameArgv, nameArgv.length);
0398: } catch (TclException ex) {
0399: Util.RestoreInterpState(interp, state);
0400: return false;
0401: }
0402:
0403: if (ensData == null) {
0404: Util.RestoreInterpState(interp, state);
0405: return false;
0406: }
0407:
0408: // Add a summary of usage information to the return buffer.
0409:
0410: GetEnsembleUsage(ensData, buffer);
0411:
0412: Util.DiscardInterpState(state);
0413:
0414: return true;
0415: }
0416:
0417: /*
0418: *----------------------------------------------------------------------
0419: *
0420: * Itcl_GetEnsembleUsageForObj -> Ensemble.GetEnsembleUsageForObj
0421: *
0422: * Returns a summary of all of the parts of an ensemble and
0423: * the meaning of their arguments. This procedure is just
0424: * like Itcl_GetEnsembleUsage, but it determines the desired
0425: * ensemble from a command line argument. The argument should
0426: * be the first argument on the command line--the ensemble
0427: * command or one of its parts.
0428: *
0429: * Results:
0430: * If the ensemble is found, its usage information is appended
0431: * onto the buffer, and this procedure returns true.
0432: * It is the responsibility of the caller to init the buffer.
0433: * If anything goes wrong, this procedure returns false.
0434: *
0435: * Side effects:
0436: * Buffer passed in is modified.
0437: *----------------------------------------------------------------------
0438: */
0439:
0440: static boolean GetEnsembleUsageForObj(Interp interp, // interpreter containing the ensemble
0441: TclObject ensObj, // argument representing ensemble
0442: StringBuffer buffer) {
0443: Ensemble ensData;
0444: TclObject chainObj = null;
0445: Command cmd;
0446:
0447: // If the argument is an ensemble part, then follow the chain
0448: // back to the command word for the entire ensemble.
0449:
0450: chainObj = ensObj;
0451: while (chainObj != null
0452: && (chainObj.getInternalRep() instanceof ItclEnsInvoc)) {
0453: ItclEnsInvoc t = (ItclEnsInvoc) chainObj.getInternalRep();
0454: chainObj = t.chainObj;
0455: }
0456:
0457: if (chainObj != null) {
0458: cmd = interp.getCommand(chainObj.toString());
0459: if (cmd != null && (cmd instanceof HandleEnsemble)) {
0460: ensData = ((HandleEnsemble) cmd).ensData;
0461: GetEnsembleUsage(ensData, buffer);
0462: return true;
0463: }
0464: }
0465: return false;
0466: }
0467:
0468: /*
0469: *----------------------------------------------------------------------
0470: *
0471: * GetEnsembleUsage -> Ensemble.GetEnsembleUsage
0472: *
0473: *
0474: * Returns a summary of all of the parts of an ensemble and
0475: * the meaning of their arguments. Each part is listed on
0476: * a separate line. This procedure is used internally to
0477: * generate usage information for error messages.
0478: *
0479: * Results:
0480: * Appends usage information onto the bufer.
0481: *
0482: * Side effects:
0483: * None.
0484: *
0485: *----------------------------------------------------------------------
0486: */
0487:
0488: static void GetEnsembleUsage(Ensemble ensData, // ensemble data
0489: StringBuffer buffer) // returns: summary of usage info
0490: {
0491: String spaces = " ";
0492: boolean isOpenEnded = false;
0493:
0494: EnsemblePart ensPart;
0495:
0496: for (int i = 0; i < ensData.numParts; i++) {
0497: ensPart = ensData.parts[i];
0498:
0499: if (ensPart.name.equals("@error")) {
0500: isOpenEnded = true;
0501: } else {
0502: buffer.append(spaces);
0503: GetEnsemblePartUsage(ensPart, buffer);
0504: spaces = "\n ";
0505: }
0506: }
0507: if (isOpenEnded) {
0508: buffer.append("\n...and others described on the man page");
0509: }
0510: }
0511:
0512: /*
0513: *----------------------------------------------------------------------
0514: *
0515: * GetEnsemblePartUsage -> Ensemble.GetEnsemblePartUsage
0516: *
0517: * Determines the usage for a single part within an ensemble,
0518: * and appends a summary onto a dynamic string. The usage
0519: * is a combination of the part name and the argument summary.
0520: *
0521: * Results:
0522: * Returns usage information in the buffer.
0523: *
0524: * Side effects:
0525: * None.
0526: *
0527: *----------------------------------------------------------------------
0528: */
0529:
0530: static void GetEnsemblePartUsage(EnsemblePart ensPart, // ensemble part for usage info
0531: StringBuffer buffer) // returns: usage information
0532: {
0533: EnsemblePart part;
0534: WrappedCommand wcmd;
0535: String name;
0536: Itcl_List trail;
0537: Itcl_ListElem elem;
0538:
0539: // Build the trail of ensemble names leading to this part.
0540:
0541: trail = new Itcl_List();
0542: Util.InitList(trail);
0543: for (part = ensPart; part != null; part = part.ensemble.parent) {
0544: Util.InsertList(trail, part);
0545: }
0546:
0547: wcmd = ensPart.ensemble.wcmd;
0548: name = ensPart.ensemble.interp.getCommandName(wcmd);
0549: Util.AppendElement(buffer, name);
0550:
0551: for (elem = Util.FirstListElem(trail); elem != null; elem = Util
0552: .NextListElem(elem)) {
0553: part = (EnsemblePart) Util.GetListValue(elem);
0554: Util.AppendElement(buffer, part.name);
0555: }
0556: Util.DeleteList(trail);
0557:
0558: // If the part has usage info, use it directly.
0559:
0560: if (ensPart.usage != null && ensPart.usage.length() > 0) {
0561: buffer.append(" ");
0562: buffer.append(ensPart.usage);
0563: }
0564:
0565: // If the part is itself an ensemble, summarize its usage.
0566: else if (ensPart.cmd != null
0567: && (ensPart.cmd instanceof HandleEnsemble)) {
0568: buffer.append(" option ?arg arg ...?");
0569: }
0570: }
0571:
0572: /*
0573: *----------------------------------------------------------------------
0574: *
0575: * CreateEnsemble -> Ensemble.CreateEnsemble
0576: *
0577: * Creates an ensemble command, or adds a sub-ensemble to an
0578: * existing ensemble command. Works like Itcl_CreateEnsemble,
0579: * except that the ensemble name is a single name, not a path.
0580: * If a parent ensemble is specified, then a new ensemble is
0581: * added to that parent. If a part already exists with the
0582: * same name, it is an error. If a parent ensemble is not
0583: * specified, then a top-level ensemble is created. If a
0584: * command already exists with the same name, it is deleted.
0585: *
0586: * Results:
0587: * If anything goes wrong, this procedure raises a TclException
0588: *
0589: * Side effects:
0590: * None.
0591: *
0592: *----------------------------------------------------------------------
0593: */
0594:
0595: static void CreateEnsemble(Interp interp, // interpreter to be updated
0596: Ensemble parentEnsData, // parent ensemble or null
0597: String ensName) // name of the new ensemble
0598: throws TclException {
0599: Ensemble ensData;
0600: EnsemblePart ensPart;
0601: WrappedCommand wcmd;
0602:
0603: // Create the data associated with the ensemble.
0604:
0605: ensData = new Ensemble();
0606: ensData.interp = interp;
0607: ensData.numParts = 0;
0608: ensData.maxParts = 10;
0609: ensData.parts = new EnsemblePart[ensData.maxParts];
0610: ensData.wcmd = null;
0611: ensData.parent = null;
0612:
0613: // If there is no parent data, then this is a top-level
0614: // ensemble. Create the ensemble by installing its access
0615: // command.
0616: //
0617: if (parentEnsData == null) {
0618: interp.createCommand(ensName, new HandleEnsemble(ensData));
0619: wcmd = Namespace.findCommand(interp, ensName, null,
0620: TCL.NAMESPACE_ONLY);
0621: ensData.wcmd = wcmd;
0622: return;
0623: }
0624:
0625: // Otherwise, this ensemble is contained within another parent.
0626: // Install the new ensemble as a part within its parent.
0627:
0628: try {
0629: ensPart = CreateEnsemblePart(interp, parentEnsData, ensName);
0630: } catch (TclException ex) {
0631: DeleteEnsemble(ensData);
0632: throw ex;
0633: }
0634:
0635: ensData.wcmd = parentEnsData.wcmd;
0636: ensData.parent = ensPart;
0637:
0638: // For an ensemble part that is itself an ensemble,
0639: // create an instance of HandleInstance and associate
0640: // it with the Ensemble instance. Note that the
0641: // Command instance is not installed into the interpreter.
0642:
0643: ensPart.cmd = new HandleEnsemble(ensData);
0644: }
0645:
0646: /*
0647: *----------------------------------------------------------------------
0648: *
0649: * AddEnsemblePart -> Ensemble.AddEnsemblePart
0650: *
0651: * Adds a part to an existing ensemble. Works like
0652: * Itcl_AddEnsemblePart, but the part name is a single word,
0653: * not a path.
0654: *
0655: * If the ensemble already has a part with the specified name,
0656: * this procedure returns an error. Otherwise, it adds a new
0657: * part to the ensemble.
0658: *
0659: * Any client data specified is automatically passed to the
0660: * handling procedure whenever the part is invoked. It is
0661: * automatically destroyed by the deleteProc when the part is
0662: * deleted.
0663: *
0664: * Results:
0665: * If anything goes wrong, this procedure raises a TclException
0666: *
0667: * Side effects:
0668: * None.
0669: *
0670: *----------------------------------------------------------------------
0671: */
0672:
0673: static EnsemblePart AddEnsemblePart(Interp interp, // interpreter to be updated
0674: Ensemble ensData, // ensemble that will contain this part
0675: String partName, // name of the new part
0676: String usageInfo, // usage info for argument list
0677: Command objProc) // handling procedure for part
0678: throws TclException {
0679: EnsemblePart ensPart;
0680: WrappedCommand wcmd;
0681:
0682: // Install the new part into the part list.
0683:
0684: ensPart = CreateEnsemblePart(interp, ensData, partName);
0685:
0686: if (usageInfo != null) {
0687: ensPart.usage = usageInfo;
0688: }
0689:
0690: // Install the passed in Command in the ensemble part.
0691:
0692: wcmd = new WrappedCommand();
0693: wcmd.ns = ensData.wcmd.ns;
0694: wcmd.cmd = objProc;
0695: ensPart.cmd = objProc;
0696: ensPart.wcmd = wcmd;
0697:
0698: return ensPart;
0699: }
0700:
0701: /*
0702: *----------------------------------------------------------------------
0703: *
0704: * DeleteEnsemble -> Ensemble.DeleteEnsemble
0705: *
0706: * Invoked when the command associated with an ensemble is
0707: * destroyed, to delete the ensemble. Destroys all parts
0708: * included in the ensemble, and frees all memory associated
0709: * with it.
0710: *
0711: * Results:
0712: * None.
0713: *
0714: * Side effects:
0715: * None.
0716: *
0717: *----------------------------------------------------------------------
0718: */
0719:
0720: static void DeleteEnsemble(Ensemble ensData) {
0721: // BE CAREFUL: Each ensemble part removes itself from the list.
0722: // So keep deleting the first part until all parts are gone.
0723: while (ensData.numParts > 0) {
0724: DeleteEnsemblePart(ensData.parts[0]);
0725: }
0726: ensData.parts = null;
0727: }
0728:
0729: /*
0730: *----------------------------------------------------------------------
0731: *
0732: * FindEnsemble -> Ensemble.FindEnsemble
0733: *
0734: * Searches for an ensemble command and follows a path to
0735: * sub-ensembles.
0736: *
0737: * Results:
0738: * If the ensemble name is invalid then null will be returned.
0739: * If anything goes wrong, this procedure raises a TclException
0740: *
0741: * Side effects:
0742: * None.
0743: *
0744: *----------------------------------------------------------------------
0745: */
0746:
0747: static Ensemble FindEnsemble(Interp interp, // interpreter containing the ensemble
0748: String[] nameArgv, // path of names leading to ensemble
0749: int nameArgc) // number of nameArgv to process
0750: throws TclException {
0751: WrappedCommand wcmd;
0752: Command cmd;
0753: Ensemble ensData;
0754: EnsemblePart ensPart;
0755:
0756: // If there are no names in the path, then return an error.
0757:
0758: if (nameArgc < 1) {
0759: return null; // Caller should create "invalid ensemble" error.
0760: }
0761:
0762: // Use the first name to find the command for the top-level
0763: // ensemble.
0764:
0765: wcmd = Namespace.findCommand(interp, nameArgv[0], null,
0766: TCL.LEAVE_ERR_MSG);
0767:
0768: if (wcmd == null || !(wcmd.cmd instanceof HandleEnsemble)) {
0769: throw new TclException(interp, "command \"" + nameArgv[0]
0770: + "\" is not an ensemble");
0771: }
0772: ensData = ((HandleEnsemble) wcmd.cmd).ensData;
0773:
0774: // Follow the trail of sub-ensemble names.
0775:
0776: for (int i = 1; i < nameArgc; i++) {
0777: ensPart = FindEnsemblePart(interp, ensData, nameArgv[i]);
0778: if (ensPart == null) {
0779: String pname = MergeEnsemble(interp, nameArgv, i);
0780: TclException ex = new TclException(interp,
0781: "invalid ensemble name \"" + pname + "\"");
0782: }
0783:
0784: cmd = ensPart.cmd;
0785: if (cmd == null || !(cmd instanceof HandleEnsemble)) {
0786: throw new TclException(interp, "part \"" + nameArgv[i]
0787: + "\" is not an ensemble");
0788: }
0789: ensData = ((HandleEnsemble) cmd).ensData;
0790: }
0791:
0792: return ensData;
0793: }
0794:
0795: /*
0796: *----------------------------------------------------------------------
0797: *
0798: * CreateEnsemblePart -> Ensemble.CreateEnsemblePart
0799: *
0800: * Creates a new part within an ensemble.
0801: *
0802: * Results:
0803: * If anything goes wrong, this procedure raises a TclException
0804: *
0805: * Side effects:
0806: * None.
0807: *
0808: *----------------------------------------------------------------------
0809: */
0810:
0811: static EnsemblePart CreateEnsemblePart(Interp interp, // interpreter containing the ensemble
0812: Ensemble ensData, // ensemble being modified
0813: String partName) // name of the new part
0814: throws TclException {
0815: int i, pos;
0816: EnsemblePart[] partList;
0817: EnsemblePart part;
0818:
0819: // If a matching entry was found, then return an error.
0820:
0821: FindEnsemblePartIndexResult res = FindEnsemblePartIndex(
0822: ensData, partName);
0823:
0824: if (res.status) {
0825: throw new TclException(interp, "part \"" + partName
0826: + "\" already exists in ensemble");
0827: }
0828: pos = res.pos;
0829:
0830: // Otherwise, make room for a new entry. Keep the parts in
0831: // lexicographical order, so we can search them quickly
0832: // later.
0833:
0834: if (ensData.numParts >= ensData.maxParts) {
0835: partList = new EnsemblePart[ensData.maxParts * 2];
0836: for (i = 0; i < ensData.maxParts; i++) {
0837: partList[i] = ensData.parts[i];
0838: }
0839: ensData.parts = null;
0840: ensData.parts = partList;
0841: ensData.maxParts = partList.length;
0842: }
0843:
0844: for (i = ensData.numParts; i > pos; i--) {
0845: ensData.parts[i] = ensData.parts[i - 1];
0846: }
0847: ensData.numParts++;
0848:
0849: part = new EnsemblePart();
0850: part.name = partName;
0851: part.cmd = null;
0852: part.usage = null;
0853: part.ensemble = ensData;
0854:
0855: ensData.parts[pos] = part;
0856:
0857: // Compare the new part against the one on either side of
0858: // it. Determine how many letters are needed in each part
0859: // to guarantee that an abbreviated form is unique. Update
0860: // the parts on either side as well, since they are influenced
0861: // by the new part.
0862:
0863: ComputeMinChars(ensData, pos);
0864: ComputeMinChars(ensData, pos - 1);
0865: ComputeMinChars(ensData, pos + 1);
0866:
0867: return part;
0868: }
0869:
0870: /*
0871: *----------------------------------------------------------------------
0872: *
0873: * DeleteEnsemblePart -> Ensemble.DeleteEnsemblePart
0874: *
0875: * Deletes a single part from an ensemble. The part must have
0876: * been created previously by CreateEnsemblePart.
0877: *
0878: * Invoke disposeCmd() if the part has a delete callback.
0879: *
0880: * Results:
0881: * None.
0882: *
0883: * Side effects:
0884: * Delete proc is called.
0885: *
0886: *----------------------------------------------------------------------
0887: */
0888:
0889: static void DeleteEnsemblePart(EnsemblePart ensPart) // part being destroyed
0890: {
0891: int i, pos;
0892: Ensemble ensData;
0893: Command cmd = ensPart.cmd;
0894:
0895: // If this part has a delete proc, then call it to free
0896: // up the client data.
0897:
0898: if (cmd instanceof CommandWithDispose) {
0899: ((CommandWithDispose) cmd).disposeCmd();
0900: }
0901: ensPart.cmd = null;
0902:
0903: // Find this part within its ensemble, and remove it from
0904: // the list of parts.
0905:
0906: FindEnsemblePartIndexResult res = FindEnsemblePartIndex(
0907: ensPart.ensemble, ensPart.name);
0908:
0909: if (res.status) {
0910: pos = res.pos;
0911: ensData = ensPart.ensemble;
0912: for (i = pos; i < ensData.numParts - 1; i++) {
0913: ensData.parts[i] = ensData.parts[i + 1];
0914: }
0915: ensData.numParts--;
0916: }
0917:
0918: // Free the memory associated with the part.
0919:
0920: if (ensPart.usage != null) {
0921: ensPart.usage = null;
0922: }
0923: ensPart.name = null;
0924: }
0925:
0926: /*
0927: *----------------------------------------------------------------------
0928: *
0929: * FindEnsemblePart -> Ensemble.FindEnsemblePart
0930: *
0931: * Searches for a part name within an ensemble. Recognizes
0932: * unique abbreviations for part names.
0933: *
0934: * Results:
0935: * If the part name is not a unique abbreviation, this procedure
0936: * raises a TclException. If the part can be found, returns a
0937: * reference to the part. Otherwise, it returns NULL.
0938: *
0939: * Side effects:
0940: * None.
0941: *
0942: *----------------------------------------------------------------------
0943: */
0944:
0945: static EnsemblePart FindEnsemblePart(Interp interp, // interpreter containing the ensemble
0946: Ensemble ensData, // ensemble being searched
0947: String partName) // name of the desired part
0948: throws TclException {
0949: int pos = 0;
0950: int first, last, nlen;
0951: int i, cmp;
0952: EnsemblePart rensPart = null;
0953:
0954: // Search for the desired part name.
0955: // All parts are in lexicographical order, so use a
0956: // binary search to find the part quickly. Match only
0957: // as many characters as are included in the specified
0958: // part name.
0959:
0960: first = 0;
0961: last = ensData.numParts - 1;
0962: nlen = partName.length();
0963:
0964: while (last >= first) {
0965: pos = (first + last) / 2;
0966: if (partName.charAt(0) == ensData.parts[pos].name.charAt(0)) {
0967: cmp = partName.substring(0, nlen).compareTo(
0968: ensData.parts[pos].name);
0969: if (cmp == 0) {
0970: break; // found it!
0971: }
0972: } else if (partName.charAt(0) < ensData.parts[pos].name
0973: .charAt(0)) {
0974: cmp = -1;
0975: } else {
0976: cmp = 1;
0977: }
0978:
0979: if (cmp > 0) {
0980: first = pos + 1;
0981: } else {
0982: last = pos - 1;
0983: }
0984: }
0985:
0986: // If a matching entry could not be found, then quit.
0987:
0988: if (last < first) {
0989: return rensPart;
0990: }
0991:
0992: // If a matching entry was found, there may be some ambiguity
0993: // if the user did not specify enough characters. Find the
0994: // top-most match in the list, and see if the part name has
0995: // enough characters. If there are two parts like "foo"
0996: // and "food", this allows us to match "foo" exactly.
0997:
0998: if (nlen < ensData.parts[pos].minChars) {
0999: while (pos > 0) {
1000: pos--;
1001: if (partName.substring(0, nlen).compareTo(
1002: ensData.parts[pos].name) != 0) {
1003: pos++;
1004: break;
1005: }
1006: }
1007: }
1008: if (nlen < ensData.parts[pos].minChars) {
1009: StringBuffer buffer = new StringBuffer(64);
1010:
1011: buffer.append("ambiguous option \"" + partName
1012: + "\": should be one of...");
1013:
1014: for (i = pos; i < ensData.numParts; i++) {
1015: if (partName.substring(0, nlen).compareTo(
1016: ensData.parts[i].name) != 0) {
1017: break;
1018: }
1019: buffer.append("\n ");
1020: GetEnsemblePartUsage(ensData.parts[i], buffer);
1021: }
1022: throw new TclException(interp, buffer.toString());
1023: }
1024:
1025: // Found a match. Return the desired part.
1026:
1027: rensPart = ensData.parts[pos];
1028: return rensPart;
1029: }
1030:
1031: /*
1032: *----------------------------------------------------------------------
1033: *
1034: * FindEnsemblePartIndex -> Ensemble.FindEnsemblePartIndex
1035: *
1036: * Searches for a part name within an ensemble. The part name
1037: * must be an exact match for an existing part name in the
1038: * ensemble. This procedure is useful for managing (i.e.,
1039: * creating and deleting) parts in an ensemble.
1040: *
1041: * Results:
1042: * If an exact match is found, this procedure returns
1043: * a status of true, along with the index of the part in pos.
1044: * Otherwise, it returns a status of false, along with an
1045: * index in pos indicating where the part should be.
1046: *
1047: * Side effects:
1048: * None.
1049: *
1050: *----------------------------------------------------------------------
1051: */
1052:
1053: static FindEnsemblePartIndexResult FindEnsemblePartIndex(
1054: Ensemble ensData, // ensemble being searched
1055: String partName) // name of desired part
1056: {
1057: int pos = 0;
1058: int first, last;
1059: int cmp;
1060: int posRes;
1061:
1062: // Search for the desired part name.
1063: // All parts are in lexicographical order, so use a
1064: // binary search to find the part quickly.
1065:
1066: first = 0;
1067: last = ensData.numParts - 1;
1068:
1069: while (last >= first) {
1070: pos = (first + last) / 2;
1071: if (partName.charAt(0) == ensData.parts[pos].name.charAt(0)) {
1072: cmp = partName.compareTo(ensData.parts[pos].name);
1073: if (cmp == 0) {
1074: break; // found it!
1075: }
1076: } else if (partName.charAt(0) < ensData.parts[pos].name
1077: .charAt(0)) {
1078: cmp = -1;
1079: } else {
1080: cmp = 1;
1081: }
1082:
1083: if (cmp > 0) {
1084: first = pos + 1;
1085: } else {
1086: last = pos - 1;
1087: }
1088: }
1089:
1090: FindEnsemblePartIndexResult res = new FindEnsemblePartIndexResult();
1091:
1092: if (last >= first) {
1093: res.status = true;
1094: res.pos = pos;
1095: return res;
1096: }
1097: res.status = false;
1098: res.pos = first;
1099: return res;
1100: }
1101:
1102: static class FindEnsemblePartIndexResult {
1103: boolean status;
1104: int pos;
1105: }
1106:
1107: /*
1108: *----------------------------------------------------------------------
1109: *
1110: * ComputeMinChars -> Ensemble.ComputeMinChars
1111: *
1112: * Compares part names on an ensemble's part list and
1113: * determines the minimum number of characters needed for a
1114: * unique abbreviation. The parts on either side of a
1115: * particular part index are compared. As long as there is
1116: * a part on one side or the other, this procedure updates
1117: * the parts to have the proper minimum abbreviations.
1118: *
1119: * Results:
1120: * None.
1121: *
1122: * Side effects:
1123: * Updates three parts within the ensemble to remember
1124: * the minimum abbreviations.
1125: *
1126: *----------------------------------------------------------------------
1127: */
1128:
1129: static void ComputeMinChars(Ensemble ensData, // ensemble being modified
1130: int pos) // index of part being updated
1131: {
1132: int min, max;
1133: int p, q;
1134: String pstr, qstr;
1135:
1136: // If the position is invalid, do nothing.
1137:
1138: if (pos < 0 || pos >= ensData.numParts) {
1139: return;
1140: }
1141:
1142: // Start by assuming that only the first letter is required
1143: // to uniquely identify this part. Then compare the name
1144: // against each neighboring part to determine the real minimum.
1145:
1146: ensData.parts[pos].minChars = 1;
1147:
1148: if (pos - 1 >= 0) {
1149: pstr = ensData.parts[pos].name;
1150: p = 0;
1151: qstr = ensData.parts[pos - 1].name;
1152: q = 0;
1153: final int plen = pstr.length();
1154: final int qlen = qstr.length();
1155: for (min = 1; p < plen && q < qlen
1156: && pstr.charAt(p) == qstr.charAt(q); min++) {
1157: p++;
1158: q++;
1159: }
1160: if (min > ensData.parts[pos].minChars) {
1161: ensData.parts[pos].minChars = min;
1162: }
1163: }
1164:
1165: if (pos + 1 < ensData.numParts) {
1166: pstr = ensData.parts[pos].name;
1167: p = 0;
1168: qstr = ensData.parts[pos + 1].name;
1169: q = 0;
1170: final int plen = pstr.length();
1171: final int qlen = qstr.length();
1172: for (min = 1; p < plen && q < qlen
1173: && pstr.charAt(p) == qstr.charAt(q); min++) {
1174: p++;
1175: q++;
1176: }
1177: if (min > ensData.parts[pos].minChars) {
1178: ensData.parts[pos].minChars = min;
1179: }
1180: }
1181:
1182: max = ensData.parts[pos].name.length();
1183: if (ensData.parts[pos].minChars > max) {
1184: ensData.parts[pos].minChars = max;
1185: }
1186: }
1187:
1188: /*
1189: *----------------------------------------------------------------------
1190: *
1191: * HandleEnsemble -> Ensemble.HandleEnsemble.cmdProc
1192: *
1193: * Invoked by Tcl whenever the user issues an ensemble-style
1194: * command. Handles commands of the form:
1195: *
1196: * <ensembleName> <partName> ?<arg> <arg>...?
1197: *
1198: * Looks for the <partName> within the ensemble, and if it
1199: * exists, the procedure transfers control to it.
1200: *
1201: * Results:
1202: * If anything goes wrong, this procedure raises a TclException.
1203: *
1204: * Side effects:
1205: * None.
1206: *
1207: *----------------------------------------------------------------------
1208: */
1209:
1210: static class HandleEnsemble implements CommandWithDispose {
1211: Ensemble ensData;
1212:
1213: HandleEnsemble(Ensemble ensData) {
1214: this .ensData = ensData;
1215: }
1216:
1217: public void disposeCmd() {
1218: DeleteEnsemble(ensData);
1219: }
1220:
1221: public void cmdProc(Interp interp, // Current interp.
1222: TclObject[] objv) // Args passed to the command.
1223: throws TclException {
1224: Command cmd;
1225: EnsemblePart ensPart;
1226: String partName;
1227: final int partNameLen;
1228: //Tcl_Obj *cmdlinePtr, *chainObj;
1229: //int cmdlinec;
1230: //Tcl_Obj **cmdlinev;
1231: TclObject cmdline, chainObj;
1232: TclObject[] cmdlinev;
1233:
1234: // If a part name is not specified, return an error that
1235: // summarizes the usage for this ensemble.
1236:
1237: if (objv.length < 2) {
1238: StringBuffer buffer = new StringBuffer(64);
1239: buffer.append("wrong # args: should be one of...\n");
1240: GetEnsembleUsage(ensData, buffer);
1241:
1242: throw new TclException(interp, buffer.toString());
1243: }
1244:
1245: // Lookup the desired part. If an ambiguous abbrevition is
1246: // found, return an error immediately.
1247:
1248: partName = objv[1].toString();
1249: partNameLen = partName.length();
1250: ensPart = FindEnsemblePart(interp, ensData, partName);
1251:
1252: // If the part was not found, then look for an "@error" part
1253: // to handle the error.
1254:
1255: if (ensPart == null) {
1256: ensPart = FindEnsemblePart(interp, ensData, "@error");
1257: if (ensPart != null) {
1258: cmd = ensPart.cmd;
1259: cmd.cmdProc(interp, objv);
1260: return;
1261: }
1262: }
1263: if (ensPart == null) {
1264: EnsembleErrorCmd(ensData, interp, objv, 1);
1265: }
1266:
1267: // Pass control to the part, and return the result.
1268:
1269: chainObj = ItclEnsInvoc.newInstance();
1270: ItclEnsInvoc irep = (ItclEnsInvoc) chainObj
1271: .getInternalRep();
1272: irep.ensPart = ensPart;
1273: irep.chainObj = objv[0];
1274:
1275: objv[1].preserve();
1276: objv[0].preserve();
1277:
1278: cmdline = TclList.newInstance();
1279: TclList.append(interp, cmdline, chainObj);
1280:
1281: for (int i = 2; i < objv.length; i++) {
1282: TclList.append(interp, cmdline, objv[i]);
1283: }
1284: cmdline.preserve();
1285:
1286: try {
1287: cmdlinev = TclList.getElements(interp, cmdline);
1288:
1289: cmd = ensPart.cmd;
1290: cmd.cmdProc(interp, cmdlinev);
1291: } finally {
1292: cmdline.release();
1293: }
1294: }
1295: } // end class HandleEnsemble
1296:
1297: /*
1298: *----------------------------------------------------------------------
1299: *
1300: * Itcl_EnsembleCmd -> Ensemble.EnsembleCmd.cmdProc
1301: *
1302: * Invoked by Tcl whenever the user issues the "ensemble"
1303: * command to manipulate an ensemble. Handles the following
1304: * syntax:
1305: *
1306: * ensemble <ensName> ?<command> <arg> <arg>...?
1307: * ensemble <ensName> {
1308: * part <partName> <args> <body>
1309: * ensemble <ensName> {
1310: * ...
1311: * }
1312: * }
1313: *
1314: * Finds or creates the ensemble <ensName>, and then executes
1315: * the commands to add parts.
1316: *
1317: * Results:
1318: * If anything goes wrong, this procedure raises a TclException.
1319: *
1320: * Side effects:
1321: * None.
1322: *
1323: *----------------------------------------------------------------------
1324: */
1325:
1326: static class EnsembleCmd implements Command {
1327: EnsembleParser ensParser;
1328:
1329: EnsembleCmd(EnsembleParser ensParser) {
1330: this .ensParser = ensParser;
1331: }
1332:
1333: public void cmdProc(Interp interp, // Current interp.
1334: TclObject[] objv) // Args passed to the command.
1335: throws TclException {
1336: String ensName;
1337: EnsembleParser ensInfo;
1338: Ensemble ensData, savedEnsData;
1339: EnsemblePart ensPart;
1340: WrappedCommand wcmd;
1341: Command cmd;
1342:
1343: // Make sure that an ensemble name was specified.
1344:
1345: if (objv.length < 2) {
1346: StringBuffer buffer = new StringBuffer(64);
1347: buffer.append("wrong # args: should be \"");
1348: buffer.append(objv[0].toString());
1349: buffer.append(" name ?command arg arg...?\"");
1350: throw new TclException(interp, buffer.toString());
1351: }
1352:
1353: // If this is the "ensemble" command in the main interpreter,
1354: // then the client data will be null. Otherwise, it is
1355: // the "ensemble" command in the ensemble body parser, and
1356: // the client data indicates which ensemble we are modifying.
1357:
1358: if (ensParser != null) {
1359: ensInfo = ensParser;
1360: } else {
1361: ensInfo = GetEnsembleParser(interp);
1362: }
1363: ensData = ensInfo.ensData;
1364:
1365: // Find or create the desired ensemble. If an ensemble is
1366: // being built, then this "ensemble" command is enclosed in
1367: // another "ensemble" command. Use the current ensemble as
1368: // the parent, and find or create an ensemble part within it.
1369:
1370: ensName = objv[1].toString();
1371:
1372: if (ensData != null) {
1373: try {
1374: ensPart = FindEnsemblePart(interp, ensData, ensName);
1375: } catch (TclException ex) {
1376: ensPart = null;
1377: }
1378: if (ensPart == null) {
1379: CreateEnsemble(interp, ensData, ensName);
1380: try {
1381: ensPart = FindEnsemblePart(interp, ensData,
1382: ensName);
1383: } catch (TclException ex) {
1384: ensPart = null;
1385: }
1386: Util.Assert(ensPart != null,
1387: "Itcl_EnsembleCmd: can't create ensemble");
1388: }
1389:
1390: cmd = ensPart.cmd;
1391: if (cmd == null || !(cmd instanceof HandleEnsemble)) {
1392: throw new TclException(interp, "part \""
1393: + objv[1].toString()
1394: + "\" is not an ensemble");
1395: }
1396: ensData = ((HandleEnsemble) cmd).ensData;
1397: }
1398:
1399: // Otherwise, the desired ensemble is a top-level ensemble.
1400: // Find or create the access command for the ensemble, and
1401: // then get its data.
1402:
1403: else {
1404: try {
1405: wcmd = Namespace.findCommand(interp, ensName, null,
1406: 0);
1407: } catch (TclException ex) {
1408: wcmd = null;
1409: }
1410: if (wcmd == null) {
1411: CreateEnsemble(interp, null, ensName);
1412: wcmd = Namespace.findCommand(interp, ensName, null,
1413: 0);
1414: }
1415: if (wcmd == null) {
1416: cmd = null;
1417: } else {
1418: cmd = wcmd.cmd;
1419: }
1420:
1421: if (cmd == null || !(cmd instanceof HandleEnsemble)) {
1422: throw new TclException(interp, "command \""
1423: + objv[1].toString()
1424: + "\" is not an ensemble");
1425: }
1426: ensData = ((HandleEnsemble) cmd).ensData;
1427: }
1428:
1429: // At this point, we have the data for the ensemble that is
1430: // being manipulated. Plug this into the parser, and then
1431: // interpret the rest of the arguments in the ensemble parser.
1432:
1433: TclException evalEx = null;
1434: savedEnsData = ensInfo.ensData;
1435: ensInfo.ensData = ensData;
1436:
1437: if (objv.length == 3) {
1438: try {
1439: ensInfo.parser.eval(objv[2].toString());
1440: } catch (TclException ex) {
1441: evalEx = ex;
1442: }
1443: } else if (objv.length > 3) {
1444: TclObject tlist = TclList.newInstance();
1445: for (int i = 2; i < objv.length; i++) {
1446: TclList.append(interp, tlist, objv[i]);
1447: }
1448: try {
1449: ensInfo.parser.eval(tlist.toString());
1450: } catch (TclException ex) {
1451: evalEx = ex;
1452: }
1453: }
1454:
1455: // Copy the result from the parser interpreter to the
1456: // master interpreter. If an error was encountered,
1457: // copy the error info first, and then set the result.
1458: // Otherwise, the offending command is reported twice.
1459:
1460: if (evalEx != null) {
1461: TclObject errInfoObj = ensInfo.parser.getVar(
1462: "::errorInfo", TCL.GLOBAL_ONLY);
1463:
1464: if (errInfoObj != null) {
1465: interp.addErrorInfo(errInfoObj.toString());
1466: }
1467:
1468: if (objv.length == 3) {
1469: String msg = "\n (\"ensemble\" body line "
1470: + ensInfo.parser.getErrorLine() + ")";
1471: interp.addErrorInfo(msg);
1472: }
1473:
1474: ensInfo.ensData = savedEnsData;
1475: throw new TclException(interp, ensInfo.parser
1476: .getResult().toString());
1477: }
1478:
1479: ensInfo.ensData = savedEnsData;
1480: interp.setResult(ensInfo.parser.getResult().toString());
1481: }
1482: } // end class EnsembleCmd
1483:
1484: /*
1485: *----------------------------------------------------------------------
1486: *
1487: * GetEnsembleParser -> Ensemble.GetEnsembleParser
1488: *
1489: * Returns the slave interpreter that acts as a parser for
1490: * the body of an "ensemble" definition. The first time that
1491: * this is called for an interpreter, the parser is created
1492: * and registered as associated data. After that, it is
1493: * simply returned.
1494: *
1495: * Results:
1496: * Returns a reference to the ensemble parser data structure.
1497: *
1498: * Side effects:
1499: * On the first call, the ensemble parser is created and
1500: * registered as "itcl_ensembleParser" with the interpreter.
1501: *
1502: *----------------------------------------------------------------------
1503: */
1504:
1505: static EnsembleParser GetEnsembleParser(Interp interp) // interpreter handling the ensemble
1506: {
1507: Namespace ns, childNs;
1508: EnsembleParser ensInfo;
1509: WrappedCommand wcmd;
1510:
1511: // Look for an existing ensemble parser. If it is found,
1512: // return it immediately.
1513:
1514: ensInfo = (EnsembleParser) interp
1515: .getAssocData("itcl_ensembleParser");
1516:
1517: if (ensInfo != null) {
1518: return ensInfo;
1519: }
1520:
1521: // Create a slave interpreter that can be used to parse
1522: // the body of an ensemble definition.
1523:
1524: ensInfo = new EnsembleParser();
1525: ensInfo.master = interp;
1526: ensInfo.parser = new Interp();
1527: ensInfo.ensData = null;
1528:
1529: // Remove all namespaces and all normal commands from the
1530: // parser interpreter.
1531:
1532: ns = Namespace.getGlobalNamespace(ensInfo.parser);
1533:
1534: while ((childNs = (Namespace) ItclAccess
1535: .FirstHashEntry(ns.childTable)) != null) {
1536: Namespace.deleteNamespace(childNs);
1537: }
1538:
1539: while ((wcmd = (WrappedCommand) ItclAccess
1540: .FirstHashEntry(ns.cmdTable)) != null) {
1541: ensInfo.parser.deleteCommandFromToken(wcmd);
1542: }
1543:
1544: // Add the allowed commands to the parser interpreter:
1545: // part, delete, ensemble
1546:
1547: ensInfo.parser.createCommand("part", new EnsPartCmd(ensInfo));
1548:
1549: ensInfo.parser.createCommand("option", new EnsPartCmd(ensInfo));
1550:
1551: ensInfo.parser.createCommand("ensemble", new EnsembleCmd(
1552: ensInfo));
1553:
1554: // Install the parser data, so we'll have it the next time
1555: // we call this procedure.
1556:
1557: interp.setAssocData("itcl_ensembleParser", ensInfo);
1558:
1559: return ensInfo;
1560: }
1561:
1562: /*
1563: *----------------------------------------------------------------------
1564: *
1565: * DeleteEnsParser -> Ensemble.DeleteEnsParser
1566: *
1567: * Called when an interpreter is destroyed to clean up the
1568: * ensemble parser within it. Destroys the slave interpreter
1569: * and frees up the data associated with it.
1570: *
1571: * Results:
1572: * None.
1573: *
1574: * Side effects:
1575: * None.
1576: *
1577: *----------------------------------------------------------------------
1578: */
1579:
1580: static void DeleteEnsParser(EnsembleParser ensInfo, // parser for ensemble-related commands
1581: Interp interp) // interpreter containing the data
1582: {
1583: ensInfo.parser.dispose();
1584: }
1585:
1586: /*
1587: *----------------------------------------------------------------------
1588: *
1589: * Itcl_EnsPartCmd -> Ensemble.EnsPartCmd.cmdProc
1590: *
1591: * Invoked by Tcl whenever the user issues the "part" command
1592: * to manipulate an ensemble. This command can only be used
1593: * inside the "ensemble" command, which handles ensembles.
1594: * Handles the following syntax:
1595: *
1596: * ensemble <ensName> {
1597: * part <partName> <args> <body>
1598: * }
1599: *
1600: * Adds a new part called <partName> to the ensemble. If a
1601: * part already exists with that name, it is an error. The
1602: * new part is handled just like an ordinary Tcl proc, with
1603: * a list of <args> and a <body> of code to execute.
1604: *
1605: * Results:
1606: * If anything goes wrong, this procedure raises a TclException.
1607: *
1608: * Side effects:
1609: * None.
1610: *
1611: *----------------------------------------------------------------------
1612: */
1613:
1614: static class EnsPartCmd implements Command {
1615: EnsembleParser ensParser;
1616:
1617: EnsPartCmd(EnsembleParser ensParser) {
1618: this .ensParser = ensParser;
1619: }
1620:
1621: public void cmdProc(Interp interp, // Current interp.
1622: TclObject[] objv) // Args passed to the command.
1623: throws TclException {
1624: EnsembleParser ensInfo = ensParser;
1625: Ensemble ensData = ensInfo.ensData;
1626:
1627: boolean varArgs, space;
1628: String partName, usage;
1629: Procedure proc;
1630: WrappedCommand wcmd;
1631: //CompiledLocal *localPtr;
1632: EnsemblePart ensPart;
1633: StringBuffer buffer;
1634:
1635: if (objv.length != 4) {
1636: throw new TclException(interp,
1637: "wrong # args: should be \""
1638: + objv[0].toString()
1639: + " name args body\"");
1640: }
1641:
1642: // Create a Tcl-style proc definition using the specified args
1643: // and body. This is not a proc in the usual sense. It belongs
1644: // to the namespace that contains the ensemble, but it is
1645: // accessed through the ensemble, not through a Tcl command.
1646:
1647: partName = objv[1].toString();
1648: wcmd = ensData.wcmd;
1649:
1650: proc = ItclAccess.newProcedure(interp, wcmd.ns, partName,
1651: objv[2], objv[3], "unknown", 0);
1652:
1653: // Deduce the usage information from the argument list.
1654: // We'll register this when we create the part, in a moment.
1655:
1656: buffer = new StringBuffer();
1657: varArgs = false;
1658: space = false;
1659:
1660: TclObject[][] argList = ItclAccess.getArgList(proc);
1661:
1662: for (int i = 0; i < argList.length; i++) {
1663: TclObject vname = argList[i][0];
1664: TclObject def = argList[i][1];
1665:
1666: varArgs = false;
1667: if (vname.toString().equals("args")) {
1668: varArgs = true;
1669: } else if (def != null) {
1670: if (space) {
1671: buffer.append(" ");
1672: }
1673: buffer.append("?");
1674: buffer.append(vname);
1675: buffer.append("?");
1676: space = true;
1677: } else {
1678: if (space) {
1679: buffer.append(" ");
1680: }
1681: buffer.append(vname);
1682: space = true;
1683: }
1684: }
1685: if (varArgs) {
1686: if (space) {
1687: buffer.append(" ");
1688: }
1689: buffer.append("?arg arg ...?");
1690: }
1691:
1692: usage = buffer.toString();
1693:
1694: // Create a new part within the ensemble. If successful,
1695: // plug the command token into the proc; we'll need it later.
1696:
1697: ensPart = AddEnsemblePart(interp, ensData, partName, usage,
1698: proc);
1699:
1700: ItclAccess.setWrappedCommand(proc, ensPart.wcmd);
1701: }
1702: } // end class EnsPartCmd
1703:
1704: /*
1705: *----------------------------------------------------------------------
1706: *
1707: * Itcl_EnsembleErrorCmd -> Ensemble.EnsembleErrorCmd
1708: *
1709: * Invoked when the user tries to access an unknown part for
1710: * an ensemble. Acts as the default handler for the "@error"
1711: * part. Generates an error message like:
1712: *
1713: * bad option "foo": should be one of...
1714: * info args procname
1715: * info body procname
1716: * info cmdcount
1717: * ...
1718: *
1719: * Results:
1720: * None.
1721: *
1722: * Side effects:
1723: * Raises a TclException with an error message.
1724: *
1725: *----------------------------------------------------------------------
1726: */
1727:
1728: static void EnsembleErrorCmd(Ensemble ensData, // like client data in C version
1729: Interp interp, TclObject[] objv, int skip)
1730: throws TclException {
1731: String cmdName;
1732: StringBuffer buffer = new StringBuffer(64);
1733:
1734: cmdName = objv[skip].toString();
1735:
1736: buffer.append("bad option \"");
1737: buffer.append(cmdName);
1738: buffer.append("\": should be one of...\n");
1739: GetEnsembleUsage(ensData, buffer);
1740:
1741: throw new TclException(interp, buffer.toString());
1742: }
1743:
1744: /*
1745: *----------------------------------------------------------------------
1746: *
1747: * FreeEnsInvocInternalRep -> Ensemble.FreeEnsInvocInternalRep
1748: *
1749: * Frees the resources associated with an ensembleInvoc object's
1750: * internal representation.
1751: *
1752: * Results:
1753: * None.
1754: *
1755: * Side effects:
1756: * Decrements the ref count of the two objects referenced by
1757: * this object. If there are no more uses, this will free
1758: * the other objects.
1759: *
1760: *----------------------------------------------------------------------
1761: */
1762:
1763: static void FreeEnsInvocInternalRep(ItclEnsInvoc obj) {
1764: TclObject prevArgObj = obj.chainObj;
1765:
1766: if (prevArgObj != null) {
1767: prevArgObj.release();
1768: }
1769: }
1770:
1771: /*
1772: *----------------------------------------------------------------------
1773: *
1774: * DupEnsInvocInternalRep -> Ensemble.DupEnsInvocInternalRep
1775: *
1776: * Duplicate the given internal representation of an ensembleInvoc.
1777: *
1778: * This shouldn't be called. Normally, a temporary ensembleInvoc
1779: * object is created while an ensemble call is in progress.
1780: * This object may be converted to string form if an error occurs.
1781: * It does not stay around long, and there is no reason for it
1782: * to be duplicated.
1783: *
1784: * Results:
1785: * None.
1786: *
1787: * Side effects:
1788: * returns copy of internal rep with duplicates of the objects
1789: * pointed to by src's internal rep.
1790: *
1791: *----------------------------------------------------------------------
1792: */
1793:
1794: static InternalRep DupEnsInvocInternalRep(ItclEnsInvoc obj) // internal rep to copy.
1795:
1796: {
1797: ItclEnsInvoc dup = new ItclEnsInvoc();
1798: dup.ensPart = obj.ensPart;
1799: dup.chainObj = obj.chainObj;
1800:
1801: if (dup.chainObj != null) {
1802: dup.chainObj.preserve();
1803: }
1804:
1805: return dup;
1806: }
1807:
1808: /*
1809: *----------------------------------------------------------------------
1810: *
1811: * SetEnsInvocFromAny -> Ensemble.SetEnsInvocFromAny
1812: *
1813: * Generates the internal representation for an ensembleInvoc
1814: * object. This conversion really shouldn't take place.
1815: * Normally, a temporary ensembleInvoc object is created while
1816: * an ensemble call is in progress. This object may be converted
1817: * to string form if an error occurs. But there is no reason
1818: * for any other object to be converted to ensembleInvoc form.
1819: *
1820: * Results:
1821: * None.
1822: *
1823: * Side effects:
1824: * None.
1825: *----------------------------------------------------------------------
1826: */
1827:
1828: static void SetEnsInvocFromAny(Interp interp, // Determines the context for
1829: // name resolution
1830: TclObject obj) // The object to convert
1831: throws TclException {
1832: // unused
1833: }
1834:
1835: /*
1836: *----------------------------------------------------------------------
1837: *
1838: * UpdateStringOfEnsInvoc -> Ensemble.UpdateStringOfEnsInvoc
1839: *
1840: * Updates the string representation for an ensembleInvoc object.
1841: * This is called when an error occurs in an ensemble part, when
1842: * the code tries to print objv[0] as the command name. This
1843: * code automatically chains together all of the names leading
1844: * to the ensemble part, so the error message references the
1845: * entire command, not just the part name.
1846: *
1847: * Results:
1848: * Returns the full command name for the ensemble part.
1849: *
1850: *----------------------------------------------------------------------
1851: */
1852:
1853: static String UpdateStringOfEnsInvoc(ItclEnsInvoc obj) // internal rep
1854: {
1855: EnsemblePart ensPart = obj.ensPart;
1856: TclObject chainObj = obj.chainObj;
1857:
1858: StringBuffer buffer = new StringBuffer(64);
1859: int length;
1860: String name;
1861:
1862: // Get the string representation for the previous argument.
1863: // This will force each ensembleInvoc argument up the line
1864: // to get its string representation. So we will get the
1865: // original command name, followed by the sub-ensemble, and
1866: // the next sub-ensemble, and so on. Then add the part
1867: // name from the ensPart argument.
1868:
1869: if (chainObj != null) {
1870: name = chainObj.toString();
1871: buffer.append(name);
1872: }
1873:
1874: if (ensPart != null) {
1875: Util.AppendElement(buffer, ensPart.name);
1876: }
1877:
1878: return buffer.toString();
1879: }
1880:
1881: } // end class Ensemble
|