commandgroup.cc

Go to the documentation of this file.
00001 
00003 
00004 //
00005 // Copyright (c) 2004-2004 California Institute of Technology
00006 // Copyright (c) 2004-2007 University of Southern California
00007 // Rob Peters <rjpeters at usc dot edu>
00008 //
00009 // created: Wed Jun  9 09:45:36 2004
00010 // commit: $Id: commandgroup.cc 10065 2007-04-12 05:54:56Z rjpeters $
00011 // $HeadURL: file:///lab/rjpeters/svnrepo/code/trunk/groovx/src/tcl/commandgroup.cc $
00012 //
00013 // --------------------------------------------------------------------
00014 //
00015 // This file is part of GroovX.
00016 //   [http://ilab.usc.edu/rjpeters/groovx/]
00017 //
00018 // GroovX is free software; you can redistribute it and/or modify it
00019 // under the terms of the GNU General Public License as published by
00020 // the Free Software Foundation; either version 2 of the License, or
00021 // (at your option) any later version.
00022 //
00023 // GroovX is distributed in the hope that it will be useful, but
00024 // WITHOUT ANY WARRANTY; without even the implied warranty of
00025 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00026 // General Public License for more details.
00027 //
00028 // You should have received a copy of the GNU General Public License
00029 // along with GroovX; if not, write to the Free Software Foundation,
00030 // Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
00031 //
00033 
00034 #ifndef GROOVX_TCL_COMMANDGROUP_CC_UTC20050628162421_DEFINED
00035 #define GROOVX_TCL_COMMANDGROUP_CC_UTC20050628162421_DEFINED
00036 
00037 #include "tcl/commandgroup.h"
00038 
00039 #include "tcl/command.h"
00040 #include "tcl/obj.h"
00041 #include "tcl/interp.h"
00042 
00043 #include "rutz/fileposition.h"
00044 #include "rutz/fstring.h"
00045 #include "rutz/sfmt.h"
00046 #include "rutz/shared_ptr.h"
00047 
00048 #include <list>
00049 #include <sstream>
00050 #include <tcl.h>
00051 #ifdef HAVE_TCLINT_H
00052 #include <tclInt.h> // for Tcl_GetFullCommandName()
00053 #endif
00054 
00055 #include "rutz/trace.h"
00056 #include "rutz/debug.h"
00057 GVX_DBG_REGISTER
00058 
00059 using rutz::fstring;
00060 using rutz::shared_ptr;
00061 
00062 namespace
00063 {
00064 #ifdef HAVE_TCLINT_H
00065   fstring get_full_command_name(tcl::interpreter& interp,
00066                                 Tcl_Command token)
00067   {
00068     tcl::obj result;
00069     // Note, this Tcl API call requires Tcl 8.4.6 or greater (or 8.5
00070     // or greater)
00071     Tcl_GetCommandFullName(interp.intp(), token, result.get());
00072     return tcl::convert_to<rutz::fstring>(result);
00073   }
00074 #endif
00075 
00076   void append_usage(std::ostream& dest, const fstring& usage)
00077   {
00078     if (!usage.is_empty())
00079       dest << " " << usage;
00080   }
00081 }
00082 
00083 class tcl::command_group::impl
00084 {
00085 public:
00086   impl(tcl::interpreter& intp, const fstring& cmd_name,
00087        const rutz::file_pos& src_pos)
00088     :
00089     interp(intp),
00090     cmd_token(Tcl_CreateObjCommand(interp.intp(),
00091                                    cmd_name.c_str(),
00092                                    // see comment in command_group's
00093                                    // constructor for why we pass
00094                                    // zeros here
00095                                    static_cast<Tcl_ObjCmdProc*>(0),
00096                                    static_cast<ClientData>(0),
00097                                    static_cast<Tcl_CmdDeleteProc*>(0))),
00098 #ifdef HAVE_TCLINT_H
00099     initial_cmd_name(get_full_command_name(intp, cmd_token)),
00100 #else
00101     initial_cmd_name(cmd_name),
00102 #endif
00103     cmd_list(),
00104     prof_name(rutz::sfmt("tcl/%s", cmd_name.c_str())),
00105     prof(prof_name.c_str(), src_pos.m_file_name, src_pos.m_line_no)
00106   {}
00107 
00108   ~impl() throw() {}
00109 
00110   typedef std::list<shared_ptr<tcl::command> > cmd_list_type;
00111 
00112   tcl::interpreter       interp;
00113   Tcl_Command      const cmd_token;
00114   fstring          const initial_cmd_name;
00115   cmd_list_type          cmd_list;
00116   fstring          const prof_name;
00117   rutz::prof             prof;
00118 
00119   fstring usage_warning(const fstring& argv0) const;
00120 
00121   static int c_invoke_callback(void* clientdata,
00122                                Tcl_Interp* interp,
00123                                int s_objc,
00124                                Tcl_Obj *const objv[]) throw();
00125 
00126   static void c_delete_callback(void* clientdata) throw();
00127 
00128   static void c_exit_callback(void* clientdata) throw();
00129 
00130   static tcl::command_group* lookup_helper(tcl::interpreter& interp,
00131                                            const char* name) throw();
00132 
00133 private:
00134   impl(const impl&);
00135   impl& operator=(const impl&);
00136 };
00137 
00138 fstring tcl::command_group::impl::usage_warning(
00139                                       const fstring& argv0) const
00140 {
00141 GVX_TRACE("tcl::command_group::usage_warning");
00142 
00143   std::ostringstream warning;
00144   warning << "wrong # args: should be ";
00145 
00146   if (cmd_list.size() == 1)
00147     {
00148       warning << "\"" << argv0;
00149       append_usage(warning, cmd_list.front()->usage_string());
00150       warning << "\"";
00151     }
00152   else
00153     {
00154       warning << "one of:";
00155       for (impl::cmd_list_type::const_iterator
00156              itr = cmd_list.begin(),
00157              end = cmd_list.end();
00158            itr != end;
00159            ++itr)
00160         {
00161           warning << "\n\t\"" << argv0;
00162           append_usage(warning, (*itr)->usage_string());
00163           warning << "\"";
00164         }
00165     }
00166 
00167   warning << "\n(";
00168 
00169   if (argv0 != initial_cmd_name)
00170     warning << "resolves to " << initial_cmd_name << ", ";
00171 
00172   warning << "defined at "
00173           << prof.src_file_name() << ":"
00174           << prof.src_line_no() << ")";
00175 
00176   return fstring(warning.str().c_str());
00177 }
00178 
00179 #ifdef REAL_BACKTRACE
00180 #include <execinfo.h>
00181 #endif
00182 
00183 int tcl::command_group::impl::c_invoke_callback(
00184     void* clientdata,
00185     Tcl_Interp* interp,
00186     int s_objc,
00187     Tcl_Obj *const objv[]) throw()
00188 {
00189   command_group* c = static_cast<command_group*>(clientdata);
00190 
00191   GVX_ASSERT(c != 0);
00192   GVX_ASSERT(interp == c->rep->interp.intp());
00193 
00194 #ifdef REAL_BACKTRACE
00195   typedef void* voidp;
00196 
00197   voidp addresses[64];
00198 
00199   const int n = backtrace(&addresses[0], 64);
00200 
00201   char** names = backtrace_symbols(&addresses[0], n);
00202 
00203   for (int i = 0; i < n; ++i)
00204     {
00205       dbg_print_nl(0, names[i]);
00206     }
00207 
00208   free(names);
00209 #endif
00210 
00211   return c->invoke_raw(s_objc, objv);
00212 }
00213 
00214 void tcl::command_group::impl::c_delete_callback(
00215     void* clientdata) throw()
00216 {
00217 GVX_TRACE("tcl::command_group::impl::c_delete_callback");
00218   command_group* c = static_cast<command_group*>(clientdata);
00219   GVX_ASSERT(c != 0);
00220   delete c;
00221 }
00222 
00223 void tcl::command_group::impl::c_exit_callback(
00224     void* clientdata) throw()
00225 {
00226 GVX_TRACE("tcl::command_group::c_exit_callback");
00227   command_group* c = static_cast<command_group*>(clientdata);
00228   GVX_ASSERT(c != 0);
00229   Tcl_DeleteCommandFromToken(c->rep->interp.intp(), c->rep->cmd_token);
00230 }
00231 
00232 tcl::command_group* tcl::command_group::impl::lookup_helper(
00233     tcl::interpreter& interp,
00234     const char* name) throw()
00235 {
00236 GVX_TRACE("tcl::command_group::impl::lookup_helper");
00237 
00238   /*
00239     typedef struct Tcl_CmdInfo {
00240     int isNativeObjectProc;
00241     Tcl_ObjCmdProc *objProc;
00242     void* objClientData;
00243     Tcl_CmdProc *proc;
00244     void* clientdata;
00245     Tcl_CmdDeleteProc *deleteProc;
00246     void* deleteData;
00247     Tcl_Namespace *namespacePtr;
00248     } Tcl_CmdInfo;
00249   */
00250   Tcl_CmdInfo info;
00251   const int result = Tcl_GetCommandInfo(interp.intp(), name, &info);
00252 
00253   if (result == 1 &&
00254       info.isNativeObjectProc == 1 &&
00255       info.objProc == &impl::c_invoke_callback &&
00256       info.deleteProc == &impl::c_delete_callback)
00257     {
00258       return static_cast<command_group*>(info.objClientData);
00259     }
00260   return 0;
00261 }
00262 
00263 tcl::command_group::command_group(tcl::interpreter& interp,
00264                                 const fstring& cmd_name,
00265                                 const rutz::file_pos& src_pos)
00266   :
00267   rep(new impl(interp, cmd_name, src_pos))
00268 {
00269 GVX_TRACE("tcl::command_group::command_group");
00270 
00271   // Register the command procedure. We do a two-step
00272   // initialization. When we call Tcl_CreateObjCommand in impl's
00273   // constructor, we don't fill in the clientdata/objProc/deleteProc
00274   // values there, but instead wait to fill them in here. The reason
00275   // is that we don't want to set up any callbacks from Tcl until
00276   // after we're sure that everything else in the construction
00277   // sequence has succeeded. We want to ensure that we don't have
00278   // "dangling callbacks" in case an exception escapes from an earlier
00279   // part of impl's or CommandGroups's constructor.
00280   Tcl_CmdInfo info;
00281   const int result = Tcl_GetCommandInfoFromToken(rep->cmd_token, &info);
00282   GVX_ASSERT(result == 1);
00283   GVX_ASSERT(info.isNativeObjectProc == 1);
00284   info.objClientData = static_cast<void*>(this);
00285   info.objProc = &impl::c_invoke_callback;
00286   info.deleteData = static_cast<void*>(this);
00287   info.deleteProc = &impl::c_delete_callback;
00288   Tcl_SetCommandInfoFromToken(rep->cmd_token, &info);
00289 
00290   Tcl_CreateExitHandler(&impl::c_exit_callback,
00291                         static_cast<void*>(this));
00292 }
00293 
00294 // A destruction sequence can get triggered in a number of ways:
00295 /*
00296    (1) application exit might trigger the c_exit_callback
00297 
00298    (2) the c_delete_callback might get triggered either by explicit
00299        deletion by the user (e.g. [rename]ing the command to the empty
00300        string "")
00301 
00302    General principles:
00303 
00304    (1) it is always "safe" to destroy the Tcl_Command, in the sense
00305        that it can't cause any crashes... in particular, it's OK to
00306        destroy the Tcl_Command even if rep->cmd_list is not empty; that
00307        would just mean that the remaining tcl::command objects in
00308        rep->cmd_list won't have any input sent their way
00309  */
00310 tcl::command_group::~command_group() throw()
00311 {
00312 GVX_TRACE("tcl::command_group::~command_group");
00313 
00314   Tcl_DeleteExitHandler(&impl::c_exit_callback,
00315                         static_cast<void*>(this));
00316 
00317   delete rep;
00318 }
00319 
00320 tcl::command_group* tcl::command_group::lookup(tcl::interpreter& interp,
00321                                              const char* name) throw()
00322 {
00323 GVX_TRACE("tcl::command_group::lookup");
00324 
00325   return impl::lookup_helper(interp, name);
00326 }
00327 
00328 tcl::command_group* tcl::command_group::lookup_original(
00329                                             tcl::interpreter& interp,
00330                                             const char* name) throw()
00331 {
00332 GVX_TRACE("tcl::command_group::lookup_original");
00333 
00334   const fstring script = rutz::sfmt("namespace origin %s", name);
00335   if (interp.eval(script, tcl::IGNORE_ERROR) == false)
00336     {
00337       return 0;
00338     }
00339 
00340   // else...
00341   const fstring original = interp.get_result<fstring>();
00342   return impl::lookup_helper(interp, original.c_str());
00343 }
00344 
00345 shared_ptr<tcl::command>
00346 tcl::command_group::make(tcl::interpreter& interp,
00347                          shared_ptr<tcl::function> callback,
00348                          const char* cmd_name,
00349                          const char* usage,
00350                          const arg_spec& spec,
00351                          const rutz::file_pos& src_pos)
00352 {
00353 GVX_TRACE("tcl::command_group::make");
00354 
00355   // Here we want to find the command_group that corresponds to the
00356   // given command name, creating it anew if necessary. Then, we
00357   // create a new tcl::command object and add it to the group. The
00358   // command_group object handles the actual interface with tcl, and
00359   // when the command_group gets callback from tcl, it selects among
00360   // its various tcl::command overloads by checking which one matches
00361   // the number of arguments in the current command invocation (see
00362   // invoke_raw()).
00363 
00364   command_group* group =
00365     tcl::command_group::lookup(interp, cmd_name);
00366 
00367   if (group == 0)
00368     group = new command_group(interp, cmd_name, src_pos);
00369 
00370   GVX_ASSERT(group != 0);
00371 
00372   shared_ptr<tcl::command> cmd(new tcl::command(callback, usage, spec));
00373 
00374   // We don't want to have to keep 'group' as a member of tcl::command
00375   // since it involves circular references -- tcl::command_group keeps
00376   // a list of tcl::command objects, so we'd prefer tcl::command to
00377   // not need a backreference to tcl::command_group. If it becomes
00378   // necessary to keep a back-reference, then there needs to be a way
00379   // for tcl::command_group to notify its tcl::command list that it is
00380   // destructing, so that the tcl::command objects can "forget" their
00381   // back-reference.
00382   group->add(cmd);
00383 
00384   return cmd;
00385 }
00386 
00387 void tcl::command_group::add(shared_ptr<tcl::command> p)
00388 {
00389 GVX_TRACE("tcl::command_group::add");
00390   rep->cmd_list.push_back(p);
00391 }
00392 
00393 fstring tcl::command_group::resolved_name() const
00394 {
00395 #ifdef HAVE_TCLINT_H
00396   return get_full_command_name(rep->interp, rep->cmd_token);
00397 #else
00398   return rep->initial_cmd_name;
00399 #endif
00400 }
00401 
00402 fstring tcl::command_group::usage() const
00403 {
00404 GVX_TRACE("tcl::command_group::usage");
00405 
00406   std::ostringstream result;
00407 
00408   impl::cmd_list_type::const_iterator
00409     itr = rep->cmd_list.begin(),
00410     end = rep->cmd_list.end();
00411 
00412   while (true)
00413     {
00414       result << "\t" << resolved_name();
00415       append_usage(result, (*itr)->usage_string());
00416       result << "\n";
00417       if (++itr == end)
00418         break;
00419     }
00420 
00421   result << "\t(defined at "
00422          << rep->prof.src_file_name() << ":"
00423          << rep->prof.src_line_no() << ")";
00424 
00425   return fstring(result.str().c_str());
00426 }
00427 
00428 int tcl::command_group::invoke_raw(int s_objc,
00429                                    Tcl_Obj *const objv[]) throw()
00430 {
00431 GVX_TRACE("tcl::command_group::invoke_raw");
00432 
00433   // This is to use the separate rutz::prof object that each
00434   // command_group has. This way we can trace the timing of individual
00435   // Tcl commands.
00436   rutz::trace tracer(rep->prof, GVX_TRACE_EXPR);
00437 
00438   // Should always be at least one since there is always the
00439   // command-name itself as the first argument.
00440   GVX_ASSERT(s_objc >= 1);
00441 
00442   if (GVX_DBG_LEVEL() > 1)
00443     {
00444       for (int argi = 0; argi < s_objc; ++argi)
00445         {
00446           const char* arg = Tcl_GetString(objv[argi]);
00447           dbg_print(1, argi);
00448           dbg_print(1, " argv = ");
00449           dbg_print_nl(1, arg);
00450         }
00451     }
00452 
00453   unsigned int objc = static_cast<unsigned int>(s_objc);
00454 
00455   // catch all possible exceptions since this is a callback from C
00456   try
00457     {
00458       for (impl::cmd_list_type::const_iterator
00459              itr = rep->cmd_list.begin(),
00460              end = rep->cmd_list.end();
00461            itr != end;
00462            ++itr)
00463         {
00464           if ((*itr)->rejects_argc(objc))
00465             continue;
00466 
00467           // Found a matching overload, so try it:
00468           (*itr)->call(rep->interp, objc, objv);
00469 
00470           if (GVX_DBG_LEVEL() > 1)
00471             {
00472               const char* result = rep->interp.get_result<const char*>();
00473               dbg_eval_nl(1, result);
00474             }
00475           return TCL_OK;
00476         }
00477 
00478       const fstring argv0(Tcl_GetString(objv[0]));
00479 
00480       // Here, we run out of potential overloads, so abort the command.
00481       rep->interp.reset_result();
00482       rep->interp.append_result(rep->usage_warning(argv0).c_str());
00483       return TCL_ERROR;
00484     }
00485   catch (...)
00486     {
00487       rep->interp.handle_live_exception(Tcl_GetString(objv[0]), SRC_POS);
00488     }
00489 
00490   return TCL_ERROR;
00491 }
00492 
00493 static const char __attribute__((used)) vcid_groovx_tcl_commandgroup_cc_utc20050628162421[] = "$Id: commandgroup.cc 10065 2007-04-12 05:54:56Z rjpeters $ $HeadURL: file:
00494 #endif // !GROOVX_TCL_COMMANDGROUP_CC_UTC20050628162421_DEFINED

The software described here is Copyright (c) 1998-2005, Rob Peters.
This page was generated Wed Dec 3 06:49:41 2008 by Doxygen version 1.5.5.