00001 /** @file tcl/eventloop.cc singleton class that operates the tcl main 00002 event loop, reading commands from a script file or from stdin, 00003 with readline-enabled command-line editing */ 00004 00005 /////////////////////////////////////////////////////////////////////// 00006 // 00007 // Copyright (c) 2002-2004 California Institute of Technology 00008 // Copyright (c) 2004-2007 University of Southern California 00009 // Rob Peters <rjpeters at usc dot edu> 00010 // 00011 // created: Mon Jul 22 16:34:05 2002 00012 // commit: $Id: eventloop.cc 11876 2009-10-22 15:53:06Z icore $ 00013 // $HeadURL: svn://isvn.usc.edu/software/invt/trunk/saliency/src/tcl/eventloop.cc $ 00014 // 00015 // -------------------------------------------------------------------- 00016 // 00017 // This file is part of GroovX 00018 // [http://ilab.usc.edu/rjpeters/groovx/] 00019 // 00020 // GroovX is free software; you can redistribute it and/or modify it 00021 // under the terms of the GNU General Public License as published by 00022 // the Free Software Foundation; either version 2 of the License, or 00023 // (at your option) any later version. 00024 // 00025 // GroovX is distributed in the hope that it will be useful, but 00026 // WITHOUT ANY WARRANTY; without even the implied warranty of 00027 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00028 // General Public License for more details. 00029 // 00030 // You should have received a copy of the GNU General Public License 00031 // along with GroovX; if not, write to the Free Software Foundation, 00032 // Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. 00033 // 00034 /////////////////////////////////////////////////////////////////////// 00035 00036 #ifndef GROOVX_TCL_EVENTLOOP_CC_UTC20050628162420_DEFINED 00037 #define GROOVX_TCL_EVENTLOOP_CC_UTC20050628162420_DEFINED 00038 00039 #include "tcl/eventloop.h" 00040 00041 #include "tcl/interp.h" 00042 00043 #include "rutz/backtrace.h" 00044 #include "rutz/backtraceformat.h" 00045 #include "rutz/error.h" 00046 #include "rutz/fstring.h" 00047 #include "rutz/sfmt.h" 00048 00049 #include <iostream> 00050 #include <sstream> 00051 #include <string> 00052 #include <tk.h> 00053 #include <unistd.h> 00054 00055 #ifndef GVX_NO_READLINE 00056 #define GVX_WITH_READLINE 00057 #endif 00058 00059 #ifdef GVX_WITH_READLINE 00060 # include <cstdlib> // for malloc/free 00061 # include <readline/readline.h> 00062 # include <readline/history.h> 00063 #endif 00064 00065 #include "rutz/trace.h" 00066 #include "rutz/debug.h" 00067 GVX_DBG_REGISTER 00068 00069 namespace tcl 00070 { 00071 class event_loop_impl; 00072 } 00073 00074 namespace 00075 { 00076 void c_exit_handler(void* /*clientdata*/) throw() 00077 { 00078 #ifdef GVX_WITH_READLINE 00079 rl_callback_handler_remove(); 00080 #endif 00081 } 00082 } 00083 00084 // Singleton implementation class for tcl::event_loop 00085 class tcl::event_loop_impl 00086 { 00087 private: 00088 static event_loop_impl* s_event_loop_impl; 00089 00090 // Data members 00091 00092 int m_argc; 00093 const char** m_argv; 00094 tcl::interpreter m_interp; 00095 const char* m_startup_filename; 00096 const char* m_argv0; 00097 Tcl_Channel m_stdin_chan; 00098 std::string m_command; // Build lines of tty input into Tcl commands. 00099 bool m_got_partial; 00100 bool m_is_interactive; // True if input is a terminal-like device. 00101 rutz::fstring m_command_line; // Entire command-line as a string 00102 bool m_no_window; // whether this is a windowless environment 00103 00104 // Function members 00105 00106 event_loop_impl(int argc, char** argv, bool nowindow); 00107 00108 int history_next(); 00109 00110 void do_prompt(const char* text, unsigned int length); 00111 00112 enum prompt_type { FULL, PARTIAL }; 00113 00114 void prompt(prompt_type t); 00115 00116 void grab_input(); 00117 00118 void handle_line(const char* line, int count); 00119 00120 void eval_command(); 00121 00122 static void c_stdin_proc(void* /*clientdata*/, int /*mask*/); 00123 00124 public: 00125 static void create(int argc, char** argv, bool nowindow) 00126 { 00127 GVX_ASSERT(s_event_loop_impl == 0); 00128 00129 s_event_loop_impl = new event_loop_impl(argc, argv, nowindow); 00130 Tcl_CreateExitHandler(c_exit_handler, static_cast<void*>(0)); 00131 } 00132 00133 static event_loop_impl* get() 00134 { 00135 if (s_event_loop_impl == 0) 00136 { 00137 throw rutz::error("no tcl::event_loop object has yet been created", 00138 SRC_POS); 00139 } 00140 00141 return s_event_loop_impl; 00142 } 00143 00144 bool is_interactive() const { return m_is_interactive; } 00145 00146 tcl::interpreter& interp() { return m_interp; } 00147 00148 void run(); 00149 00150 int argc() const { return m_argc; } 00151 00152 const char* const* argv() const { return m_argv; } 00153 00154 rutz::fstring command_line() const { return m_command_line; } 00155 00156 #ifdef GVX_WITH_READLINE 00157 static void readline_line_complete(char* line); 00158 #endif 00159 }; 00160 00161 tcl::event_loop_impl* tcl::event_loop_impl::s_event_loop_impl = 0; 00162 00163 //--------------------------------------------------------------------- 00164 // 00165 // tcl::event_loop_impl::event_loop_impl() 00166 // 00167 //--------------------------------------------------------------------- 00168 00169 tcl::event_loop_impl::event_loop_impl(int argc, char** argv, bool nowindow) : 00170 m_argc(argc), 00171 m_argv(const_cast<const char**>(argv)), 00172 m_interp(Tcl_CreateInterp()), 00173 m_startup_filename(0), 00174 m_argv0(0), 00175 m_stdin_chan(0), 00176 m_command(), 00177 m_got_partial(false), 00178 m_is_interactive(isatty(0)), 00179 m_command_line(), 00180 m_no_window(nowindow) 00181 { 00182 GVX_TRACE("tcl::event_loop_impl::event_loop_impl"); 00183 00184 Tcl_FindExecutable(argv[0]); 00185 00186 { 00187 std::ostringstream buf; 00188 00189 buf << argv[0]; 00190 00191 for (int i = 1; i < argc; ++i) 00192 { 00193 buf << " " << argv[i]; 00194 } 00195 00196 m_command_line = rutz::fstring(buf.str().c_str()); 00197 } 00198 00199 // Parse command-line arguments. If the next argument doesn't start 00200 // with a "-" then strip it off and use it as the name of a script 00201 // file to process. 00202 00203 if ((argc > 1) && (argv[1][0] != '-')) 00204 { 00205 m_argv0 = m_startup_filename = argv[1]; 00206 --argc; 00207 ++argv; 00208 m_is_interactive = false; 00209 } 00210 else 00211 { 00212 m_argv0 = argv[0]; 00213 } 00214 00215 // Make command-line arguments available in the Tcl variables "argc" 00216 // and "argv". 00217 00218 m_interp.set_global_var("argc", tcl::convert_from(argc-1)); 00219 00220 char* args = Tcl_Merge(argc-1, 00221 const_cast<const char**>(argv+1)); 00222 m_interp.set_global_var("argv", tcl::convert_from(args)); 00223 Tcl_Free(args); 00224 00225 m_interp.set_global_var("argv0", tcl::convert_from(m_argv0)); 00226 00227 m_interp.set_global_var("tcl_interactive", 00228 tcl::convert_from(m_is_interactive ? 1 : 0)); 00229 00230 #ifdef GVX_WITH_READLINE 00231 using_history(); 00232 #endif 00233 } 00234 00235 //--------------------------------------------------------------------- 00236 // 00237 // Get the next number in the history count. 00238 // 00239 //--------------------------------------------------------------------- 00240 00241 int tcl::event_loop_impl::history_next() 00242 { 00243 GVX_TRACE("tcl::event_loop_impl::history_next"); 00244 00245 #ifdef GVX_WITH_READLINE 00246 return history_length+1; 00247 #else 00248 tcl::obj obj = m_interp.get_result<Tcl_Obj*>(); 00249 00250 m_interp.eval("history nextid", tcl::IGNORE_ERROR); 00251 00252 int result = m_interp.get_result<int>(); 00253 00254 m_interp.set_result(obj); 00255 00256 return result; 00257 #endif 00258 } 00259 00260 //--------------------------------------------------------------------- 00261 // 00262 // Actually write the prompt characters to the terminal. 00263 // 00264 //--------------------------------------------------------------------- 00265 00266 void tcl::event_loop_impl::do_prompt(const char* text, 00267 #ifdef GVX_WITH_READLINE 00268 unsigned int /*length*/ 00269 #else 00270 unsigned int length 00271 #endif 00272 ) 00273 { 00274 GVX_TRACE("tcl::event_loop_impl::do_prompt"); 00275 00276 rutz::fstring color_prompt = text; 00277 00278 if (isatty(1)) 00279 { 00280 #if defined(GVX_WITH_READLINE) && defined(RL_PROMPT_START_IGNORE) 00281 color_prompt = rutz::sfmt("%c\033[1;32m%c%s%c\033[0m%c", 00282 RL_PROMPT_START_IGNORE, 00283 RL_PROMPT_END_IGNORE, 00284 text, 00285 RL_PROMPT_START_IGNORE, 00286 RL_PROMPT_END_IGNORE); 00287 #else 00288 color_prompt = rutz::sfmt("\033[1;32m%s\033[0m", text); 00289 #endif 00290 } 00291 00292 #ifdef GVX_WITH_READLINE 00293 rl_callback_handler_install(color_prompt.c_str(), 00294 readline_line_complete); 00295 #else 00296 if (length > 0) 00297 { 00298 std::cout.write(color_prompt.c_str(), color_prompt.length()); 00299 std::cout.flush(); 00300 } 00301 #endif 00302 } 00303 00304 //--------------------------------------------------------------------- 00305 // 00306 // tcl::event_loop_impl::prompt() 00307 // 00308 //--------------------------------------------------------------------- 00309 00310 void tcl::event_loop_impl::prompt(tcl::event_loop_impl::prompt_type t) 00311 { 00312 GVX_TRACE("tcl::event_loop_impl::prompt"); 00313 00314 if (t == PARTIAL) 00315 { 00316 do_prompt("", 0); 00317 } 00318 else 00319 { 00320 #ifdef GVX_WITH_READLINE 00321 const rutz::fstring text = rutz::sfmt("%s %d>>> ", m_argv0, history_next()); 00322 #else 00323 const rutz::fstring text = rutz::sfmt("%s %d> ", m_argv0, history_next()); 00324 #endif 00325 00326 do_prompt(text.c_str(), text.length()); 00327 } 00328 } 00329 00330 //--------------------------------------------------------------------- 00331 // 00332 // Callback triggered from the readline library when it has a full 00333 // line of input for us to handle. 00334 // 00335 //--------------------------------------------------------------------- 00336 00337 #ifdef GVX_WITH_READLINE 00338 00339 void tcl::event_loop_impl::readline_line_complete(char* line) 00340 { 00341 GVX_TRACE("tcl::event_loop_impl::readline_line_complete"); 00342 00343 dbg_eval_nl(3, line); 00344 00345 rl_callback_handler_remove(); 00346 00347 get()->handle_line(line, line == 0 ? -1 : int(strlen(line))); 00348 } 00349 00350 #endif 00351 00352 //--------------------------------------------------------------------- 00353 // 00354 // Pull any new characters in from the input stream. Returns the 00355 // number of characters read from the input stream. 00356 // 00357 //--------------------------------------------------------------------- 00358 00359 void tcl::event_loop_impl::grab_input() 00360 { 00361 GVX_TRACE("tcl::event_loop_impl::grab_input"); 00362 00363 #ifndef GVX_WITH_READLINE 00364 Tcl_DString line; 00365 00366 Tcl_DStringInit(&line); 00367 00368 int count = Tcl_Gets(m_stdin_chan, &line); 00369 00370 handle_line(Tcl_DStringValue(&line), count); 00371 00372 Tcl_DStringFree(&line); 00373 00374 #else // GVX_WITH_READLINE 00375 rl_callback_read_char(); 00376 #endif 00377 } 00378 00379 //--------------------------------------------------------------------- 00380 // 00381 // Handle a complete line of input (though not necessarily a complete 00382 // Tcl command). 00383 // 00384 //--------------------------------------------------------------------- 00385 00386 void tcl::event_loop_impl::handle_line(const char* line, int count) 00387 { 00388 GVX_TRACE("tcl::event_loop_impl::handle_line"); 00389 00390 if (count < 0) 00391 { 00392 if (!m_got_partial) 00393 { 00394 if (m_is_interactive) 00395 { 00396 // OK, here we're in interactive mode and we're going to 00397 // exit because stdin has been closed -- this means that 00398 // the user probably typed Ctrl-D, so let's send a 00399 // newline to stdout before exiting so that the user's 00400 // shell prompt starts on a fresh line rather than 00401 // overwriting the final tcl prompt 00402 Tcl_Channel out_chan = Tcl_GetStdChannel(TCL_STDOUT); 00403 if (out_chan) 00404 { 00405 const char nl = '\n'; 00406 Tcl_WriteChars(out_chan, &nl, 1); 00407 } 00408 00409 Tcl_Exit(0); 00410 } 00411 else 00412 { 00413 Tcl_DeleteChannelHandler(m_stdin_chan, 00414 &c_stdin_proc, 00415 static_cast<void*>(0)); 00416 } 00417 } 00418 return; 00419 } 00420 00421 GVX_ASSERT(line != 0); 00422 00423 m_command += line; 00424 m_command += "\n"; 00425 00426 dbg_eval_nl(3, m_command.c_str()); 00427 00428 if (m_command.length() > 0 && 00429 Tcl_CommandComplete(m_command.c_str())) 00430 { 00431 m_got_partial = false; 00432 eval_command(); 00433 } 00434 else 00435 { 00436 m_got_partial = true; 00437 } 00438 00439 if (m_is_interactive) 00440 { 00441 prompt(m_got_partial ? PARTIAL : FULL); 00442 } 00443 00444 m_interp.reset_result(); 00445 } 00446 00447 //--------------------------------------------------------------------- 00448 // 00449 // Executes a complete command string in the Tcl interpreter. 00450 // 00451 //--------------------------------------------------------------------- 00452 00453 void tcl::event_loop_impl::eval_command() 00454 { 00455 GVX_TRACE("tcl::event_loop_impl::eval_command"); 00456 00457 // Disable the stdin channel handler while evaluating the command; 00458 // otherwise if the command re-enters the event loop we might 00459 // process commands from stdin before the current command is 00460 // finished. Among other things, this will trash the text of the 00461 // command being evaluated. 00462 00463 Tcl_CreateChannelHandler(m_stdin_chan, 0, &c_stdin_proc, 00464 static_cast<void*>(0)); 00465 00466 bool should_display_result = false; 00467 00468 #ifdef GVX_WITH_READLINE 00469 char* expansion = 0; 00470 const int status = 00471 history_expand(const_cast<char*>(m_command.c_str()), &expansion); 00472 #else 00473 const char* expansion = m_command.data(); 00474 const int status = 0; 00475 #endif 00476 00477 dbg_eval_nl(3, m_command.c_str()); 00478 dbg_eval_nl(3, expansion); 00479 dbg_eval_nl(3, status); 00480 00481 // status: -1 --> error 00482 // 0 --> no expansions occurred 00483 // 1 --> expansions occurred 00484 // 2 --> display but don't execute 00485 00486 if (status == -1 || status == 2) // display expansion? 00487 { 00488 m_interp.append_result(expansion); 00489 should_display_result = true; 00490 } 00491 00492 if (status == 1) 00493 { 00494 Tcl_Channel out_chan = Tcl_GetStdChannel(TCL_STDOUT); 00495 if (out_chan) 00496 { 00497 Tcl_WriteChars(out_chan, expansion, -1); 00498 Tcl_Flush(out_chan); 00499 } 00500 } 00501 00502 if (status == 0 || status == 1) // execute expansion? 00503 { 00504 // The idea here is that we want to keep the readline history 00505 // and the Tcl history in sync. Tcl's "history add" command will 00506 // skip adding the string if it is empty or has whitespace 00507 // only. So, we need to make that same check here before adding 00508 // to the readline history. In fact, if we find that the command 00509 // is empty, we can just skip executing it altogether. 00510 00511 // Skip over leading whitespace 00512 #ifdef GVX_WITH_READLINE 00513 char* trimmed = expansion; 00514 #else 00515 const char* trimmed = expansion; 00516 #endif 00517 00518 while (isspace(trimmed[0]) && trimmed[0] != '\0') 00519 { 00520 ++trimmed; 00521 } 00522 00523 size_t len = strlen(trimmed); 00524 00525 if (len > 0) 00526 { 00527 int code = Tcl_RecordAndEval(m_interp.intp(), 00528 trimmed, TCL_EVAL_GLOBAL); 00529 00530 #ifdef GVX_WITH_READLINE 00531 char c = trimmed[len-1]; 00532 00533 if (c == '\n') 00534 trimmed[len-1] = '\0'; 00535 00536 add_history(trimmed); 00537 00538 trimmed[len-1] = c; 00539 #endif 00540 00541 dbg_eval_nl(3, m_interp.get_result<const char*>()); 00542 00543 should_display_result = 00544 ((m_interp.get_result<const char*>())[0] != '\0') && 00545 ((code != TCL_OK) || m_is_interactive); 00546 } 00547 } 00548 00549 if (should_display_result) 00550 { 00551 Tcl_Channel out_chan = Tcl_GetStdChannel(TCL_STDOUT); 00552 if (out_chan) 00553 { 00554 Tcl_WriteObj(out_chan, m_interp.get_result<Tcl_Obj*>()); 00555 Tcl_WriteChars(out_chan, "\n", 1); 00556 } 00557 } 00558 00559 m_stdin_chan = Tcl_GetStdChannel(TCL_STDIN); 00560 00561 if (m_stdin_chan) 00562 { 00563 Tcl_CreateChannelHandler(m_stdin_chan, TCL_READABLE, 00564 &c_stdin_proc, 00565 static_cast<void*>(0)); 00566 } 00567 00568 m_command.clear(); 00569 00570 #ifdef GVX_WITH_READLINE 00571 free(expansion); 00572 #endif 00573 } 00574 00575 //--------------------------------------------------------------------- 00576 // 00577 // This procedure is invoked by the event dispatcher whenever standard 00578 // input becomes readable. It grabs the next line of input 00579 // characters, adds them to a command being assembled, and executes 00580 // the command if it's complete. 00581 // 00582 //--------------------------------------------------------------------- 00583 00584 void tcl::event_loop_impl::c_stdin_proc(void* /*clientdata*/, int /*mask*/) 00585 { 00586 GVX_TRACE("tcl::event_loop_impl::c_stdin_proc"); 00587 00588 tcl::event_loop_impl::get()->grab_input(); 00589 } 00590 00591 //--------------------------------------------------------------------- 00592 // 00593 // tcl::event_loop_impl::run() 00594 // 00595 //--------------------------------------------------------------------- 00596 00597 void tcl::event_loop_impl::run() 00598 { 00599 GVX_TRACE("tcl::event_loop_impl::run"); 00600 00601 /* 00602 * Invoke the script specified on the command line, if any. 00603 */ 00604 00605 if (m_startup_filename != NULL) 00606 { 00607 m_interp.reset_result(); 00608 bool success = m_interp.eval_file(m_startup_filename); 00609 if (!success) 00610 { 00611 // ensure errorInfo is set properly: 00612 m_interp.add_error_info(""); 00613 00614 rutz::backtrace b; 00615 rutz::error::get_last_backtrace(b); 00616 const rutz::fstring bt = rutz::format(b); 00617 00618 std::cerr << m_interp.get_global_var<const char*>("errorInfo") 00619 << "\n" << bt << "\nError in startup script\n"; 00620 m_interp.destroy(); 00621 Tcl_Exit(1); 00622 } 00623 } 00624 else 00625 { 00626 // Evaluate the .rc file, if one has been specified. 00627 m_interp.source_rc_file(); 00628 00629 // Set up a stdin channel handler. 00630 m_stdin_chan = Tcl_GetStdChannel(TCL_STDIN); 00631 if (m_stdin_chan) 00632 { 00633 Tcl_CreateChannelHandler(m_stdin_chan, TCL_READABLE, 00634 &event_loop_impl::c_stdin_proc, 00635 static_cast<void*>(0)); 00636 } 00637 if (m_is_interactive) 00638 { 00639 this->prompt(FULL); 00640 } 00641 } 00642 00643 Tcl_Channel out_channel = Tcl_GetStdChannel(TCL_STDOUT); 00644 if (out_channel) 00645 { 00646 Tcl_Flush(out_channel); 00647 } 00648 m_interp.reset_result(); 00649 00650 // Loop indefinitely, waiting for commands to execute, until there 00651 // are no main windows left, then exit. 00652 00653 while ((m_is_interactive && m_no_window) 00654 || Tk_GetNumMainWindows() > 0) 00655 { 00656 Tcl_DoOneEvent(0); 00657 } 00658 m_interp.destroy(); 00659 Tcl_Exit(0); 00660 } 00661 00662 /////////////////////////////////////////////////////////////////////// 00663 // 00664 // tcl::event_loop functions delegate to tcl::event_loop_impl 00665 // 00666 /////////////////////////////////////////////////////////////////////// 00667 00668 tcl::event_loop::event_loop(int argc, char** argv, bool nowindow) 00669 { 00670 tcl::event_loop_impl::create(argc, argv, nowindow); 00671 } 00672 00673 tcl::event_loop::~event_loop() 00674 {} 00675 00676 bool tcl::event_loop::is_interactive() 00677 { 00678 GVX_TRACE("tcl::event_loop::is_interactive"); 00679 return tcl::event_loop_impl::get()->is_interactive(); 00680 } 00681 00682 tcl::interpreter& tcl::event_loop::interp() 00683 { 00684 GVX_TRACE("tcl::event_loop::interp"); 00685 return tcl::event_loop_impl::get()->interp(); 00686 } 00687 00688 void tcl::event_loop::run() 00689 { 00690 GVX_TRACE("tcl::event_loop::run"); 00691 tcl::event_loop_impl::get()->run(); 00692 } 00693 00694 int tcl::event_loop::argc() 00695 { 00696 GVX_TRACE("tcl::event_loop::argc"); 00697 return tcl::event_loop_impl::get()->argc(); 00698 } 00699 00700 const char* const* tcl::event_loop::argv() 00701 { 00702 GVX_TRACE("tcl::event_loop::argv"); 00703 return tcl::event_loop_impl::get()->argv(); 00704 } 00705 00706 rutz::fstring tcl::event_loop::command_line() 00707 { 00708 GVX_TRACE("tcl::event_loop::command_line"); 00709 return tcl::event_loop_impl::get()->command_line(); 00710 } 00711 00712 static const char __attribute__((used)) vcid_groovx_tcl_eventloop_cc_utc20050628162420[] = "$Id: eventloop.cc 11876 2009-10-22 15:53:06Z icore $ $HeadURL: svn://isvn.usc.edu/software/invt/trunk/saliency/src/tcl/eventloop.cc $"; 00713 #endif // !GROOVX_TCL_EVENTLOOP_CC_UTC20050628162420_DEFINED