eventloop.cc

Go to the documentation of this file.
00001 
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 10069 2007-04-12 18:51:44Z rjpeters $
00013 // $HeadURL: file:///lab/rjpeters/svnrepo/code/trunk/groovx/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 //
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 
00663 //
00664 // tcl::event_loop functions delegate to tcl::event_loop_impl
00665 //
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 10069 2007-04-12 18:51:44Z rjpeters $ $HeadURL: file:
00713 #endif // !GROOVX_TCL_EVENTLOOP_CC_UTC20050628162420_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.