00001 /** @file tcl/commandgroup.cc represents a set of overloaded 00002 tcl::command objects */ 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 11876 2009-10-22 15:53:06Z icore $ 00011 // $HeadURL: svn://isvn.usc.edu/software/invt/trunk/saliency/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 // 00032 /////////////////////////////////////////////////////////////////////// 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 11876 2009-10-22 15:53:06Z icore $ $HeadURL: svn://isvn.usc.edu/software/invt/trunk/saliency/src/tcl/commandgroup.cc $"; 00494 #endif // !GROOVX_TCL_COMMANDGROUP_CC_UTC20050628162421_DEFINED