changeset 29259:11343ca3c125

allow Octave to operate as a server, executing commands from a queue * event-manager.h (interpreter_events::interpreter_output): New virtual fucntion. (event_manager::interpreter_output): New function. * interpreter.cc (interpeter::m_parser, interpeter::m_exit_status, interpeter::m_server_mode): New data members. (interpreter::parse_and_execute, interpreter::server_loop, (interpreter::parse_and_execute): New functions. (interpreter::execute): If server option is set, execute server_loop instead of main_loop. * pager.cc (output_system::sync): Also bypass pager if running in server mode. (output_system::do_sync): Send output to event manager if running in server mode and bypass_pager is true. * options-usage.h, octave.h, octave.cc, main.in.cc: Handle new --server option.
author John W. Eaton <jwe@octave.org>
date Thu, 17 Dec 2020 20:18:51 -0500
parents 28913793f678
children e818ceb2a74e
files libinterp/corefcn/event-manager.h libinterp/corefcn/interpreter.cc libinterp/corefcn/interpreter.h libinterp/corefcn/pager.cc libinterp/octave.cc libinterp/octave.h libinterp/options-usage.h src/main.in.cc
diffstat 8 files changed, 240 insertions(+), 8 deletions(-) [+]
line wrap: on
line diff
--- a/libinterp/corefcn/event-manager.h	Sun Jan 03 14:39:31 2021 -0500
+++ b/libinterp/corefcn/event-manager.h	Thu Dec 17 20:18:51 2020 -0500
@@ -195,6 +195,9 @@
 
     virtual void unregister_doc (const std::string& /*file*/) { }
 
+    virtual void interpreter_output (const std::string& /*msg*/) { }
+
+
     virtual void gui_status_update (const std::string& /*feature*/,
                                     const std::string& /*status*/) { }
 
@@ -503,6 +506,17 @@
         return false;
     }
 
+    bool interpreter_output (const std::string& msg)
+    {
+      if (enabled ())
+        {
+          instance->interpreter_output (msg);
+          return true;
+        }
+      else
+        return false;
+    }
+
     bool gui_status_update (const std::string& feature, const std::string& status)
     {
       if (enabled ())
--- a/libinterp/corefcn/interpreter.cc	Sun Jan 03 14:39:31 2021 -0500
+++ b/libinterp/corefcn/interpreter.cc	Thu Dec 17 20:18:51 2020 -0500
@@ -467,7 +467,10 @@
       m_cdef_manager (*this),
       m_gtk_manager (),
       m_event_manager (*this),
+      m_parser (),
       m_gh_manager (nullptr),
+      m_exit_status (0),
+      m_server_mode (false),
       m_interactive (false),
       m_read_site_files (true),
       m_read_init_files (m_app_context != nullptr),
@@ -755,6 +758,53 @@
     m_initialized = true;
   }
 
+  void interpreter::parse_and_execute (const std::string& input,
+                                       bool& incomplete_parse)
+  {
+    incomplete_parse = false;
+
+    unwind_protect_var<bool> upv (m_in_top_level_repl, true);
+
+    if (m_evaluator.at_top_level ())
+      {
+        m_evaluator.dbstep_flag (0);
+        m_evaluator.reset_debug_state ();
+      }
+
+    bool eof = false;
+
+    if (command_history::add (input))
+      m_event_manager.append_history (input);
+
+    m_exit_status = m_parser->run (input, eof);
+
+    if (m_exit_status == 0)
+      {
+        std::shared_ptr<tree_statement_list>
+          stmt_list = m_parser->statement_list ();
+
+        if (stmt_list)
+          {
+            command_editor::increment_current_command_number ();
+
+            m_evaluator.eval (stmt_list, m_interactive);
+
+            m_event_manager.set_workspace ();
+          }
+        else if (m_parser->at_end_of_input ())
+          m_exit_status = EOF;
+      }
+    else
+      incomplete_parse = true;
+
+    if (m_exit_status == -1)
+      m_exit_status = 0;
+    else
+      m_parser->reset ();
+
+    m_event_manager.pre_input_event ();
+  }
+
   // FIXME: this function is intended to be executed only once.  Should
   // we enforce that restriction?
 
@@ -801,7 +851,10 @@
             if (options.forced_interactive ())
               command_editor::blink_matching_paren (false);
 
-            exit_status = main_loop ();
+            if (options.server ())
+              exit_status = server_loop ();
+            else
+              exit_status = main_loop ();
           }
       }
     catch (const exit_exception& xe)
@@ -1335,6 +1388,91 @@
     return exit_status;
   }
 
+  int interpreter::server_loop (void)
+  {
+    // Process events from the event queue.
+
+    unwind_protect_var<bool> upv (m_server_mode, true);
+
+    m_exit_status = 0;
+
+    if (! m_parser)
+      m_parser = std::shared_ptr<push_parser> (new push_parser (*this));
+
+    do
+      {
+        try
+          {
+            // FIXME: Running the event queue should be decoupled from
+            // the command_editor.  We should also use a condition
+            // variable to manage the execution of entries in the queue
+            // and eliminate the need for the busy-wait loop.
+
+            command_editor::run_event_hooks ();
+
+            octave::sleep (0.1);
+          }
+        catch (const interrupt_exception&)
+          {
+            recover_from_exception ();
+
+            m_parser->reset ();
+
+            // Required newline when the user does Ctrl+C at the prompt.
+            if (m_interactive)
+              octave_stdout << "\n";
+          }
+        catch (const index_exception& e)
+          {
+            recover_from_exception ();
+
+            m_parser->reset ();
+
+            std::cerr << "error: unhandled index exception: "
+                      << e.message () << " -- trying to return to prompt"
+                      << std::endl;
+          }
+        catch (const execution_exception& ee)
+          {
+            m_error_system.save_exception (ee);
+            m_error_system.display_exception (ee, std::cerr);
+
+            if (m_interactive)
+              {
+                recover_from_exception ();
+
+                m_parser->reset ();
+              }
+            else
+              {
+                // We should exit with a nonzero status.
+                m_exit_status = 1;
+                break;
+              }
+          }
+        catch (const std::bad_alloc&)
+          {
+            recover_from_exception ();
+
+            m_parser->reset ();
+
+            std::cerr << "error: out of memory -- trying to return to prompt"
+                      << std::endl;
+          }
+      }
+    while (m_exit_status == 0);
+
+    if (m_exit_status == EOF)
+      {
+        if (m_interactive)
+          octave_stdout << "\n";
+
+        m_exit_status = 0;
+      }
+
+    return m_exit_status;
+  }
+
   tree_evaluator& interpreter::get_evaluator (void)
   {
     return m_evaluator;
--- a/libinterp/corefcn/interpreter.h	Sun Jan 03 14:39:31 2021 -0500
+++ b/libinterp/corefcn/interpreter.h	Thu Dec 17 20:18:51 2020 -0500
@@ -29,6 +29,7 @@
 #include "octave-config.h"
 
 #include <map>
+#include <memory>
 #include <stack>
 #include <string>
 
@@ -71,6 +72,7 @@
 
 namespace octave
 {
+  class push_parser;
   class profiler;
   class child_list;
 
@@ -147,6 +149,12 @@
 
     void initialize (void);
 
+    // Parse a line of input.  If input ends at a complete statement
+    // boundary, execute the resulting parse tree.  Useful to handle
+    // parsing user input when running in server mode.
+
+    void parse_and_execute (const std::string& input, bool& incomplete_parse);
+
     // Initialize the interpreter (if not already done by an explicit
     // call to initialize), execute startup files, --eval option code,
     // script files, and/or interactive commands.
@@ -155,6 +163,11 @@
 
     void shutdown (void);
 
+    bool server_mode (void) const
+    {
+      return m_server_mode;
+    }
+
     bool interactive (void) const
     {
       return m_interactive;
@@ -302,6 +315,11 @@
       return m_event_manager;
     }
 
+    std::shared_ptr<push_parser> get_parser (void)
+    {
+      return m_parser;
+    }
+
     gh_manager& get_gh_manager (void)
     {
       return *m_gh_manager;
@@ -505,6 +523,8 @@
 
     int main_loop (void);
 
+    int server_loop (void);
+
     void execute_atexit_fcns (void);
 
     application *m_app_context;
@@ -553,8 +573,15 @@
 
     event_manager m_event_manager;
 
+    std::shared_ptr<push_parser> m_parser;
+
     gh_manager *m_gh_manager;
 
+    int m_exit_status;
+
+    // TRUE means we are executing in the server_loop function.
+    bool m_server_mode;
+
     // TRUE means this is an interactive interpreter (forced or not).
     bool m_interactive;
 
--- a/libinterp/corefcn/pager.cc	Sun Jan 03 14:39:31 2021 -0500
+++ b/libinterp/corefcn/pager.cc	Thu Dec 17 20:18:51 2020 -0500
@@ -377,13 +377,17 @@
 
   bool output_system::sync (const char *buf, int len)
   {
-    if (! m_interpreter.interactive ()
+    // FIXME: The following seems to be a bit of a mess.
+
+    if (m_interpreter.server_mode ()
+        || ! m_interpreter.interactive ()
         || application::forced_interactive ()
         || m_really_flush_to_pager
         || (m_page_screen_output && m_page_output_immediately)
         || ! m_page_screen_output)
       {
-        bool bypass_pager = (! m_interpreter.interactive ()
+        bool bypass_pager = (m_interpreter.server_mode ()
+                             || ! m_interpreter.interactive ()
                              || application::forced_interactive ()
                              || ! m_page_screen_output
                              || (m_really_flush_to_pager
@@ -439,8 +443,17 @@
       {
         if (bypass_pager)
           {
-            std::cout.write (msg, len);
-            std::cout.flush ();
+            if (m_interpreter.server_mode ())
+              {
+                event_manager& evmgr = m_interpreter.get_event_manager ();
+
+                evmgr.interpreter_output (std::string (msg, len));
+              }
+            else
+              {
+                std::cout.write (msg, len);
+                std::cout.flush ();
+              }
           }
         else
           {
--- a/libinterp/octave.cc	Sun Jan 03 14:39:31 2021 -0500
+++ b/libinterp/octave.cc	Thu Dec 17 20:18:51 2020 -0500
@@ -212,6 +212,10 @@
             m_persist = true;
             break;
 
+          case SERVER_OPTION:
+            m_server = true;
+            break;
+
           case TEXI_MACROS_FILE_OPTION:
             if (octave_optarg_wrapper ())
               m_texi_macros_file = octave_optarg_wrapper ();
@@ -255,6 +259,7 @@
     m.assign ("read_history_file", read_history_file ());
     m.assign ("read_init_files", read_init_files ());
     m.assign ("read_site_files", read_site_files ());
+    m.assign ("server", server ());
     m.assign ("set_initial_path", set_initial_path ());
     m.assign ("traditional", traditional ());
     m.assign ("verbose_flag", verbose_flag ());
@@ -398,8 +403,21 @@
             std::cerr << "error: --gui and --no-line-editing are mutually exclusive options" << std::endl;
             octave_print_terse_usage_and_exit ();
           }
+        if (m_options.server ())
+          {
+            std::cerr << "error: --gui and --server are mutually exclusive options" << std::endl;
+            octave_print_terse_usage_and_exit ();
+          }
       }
 
+    if (m_options.server ())
+      {
+        if (m_options.forced_interactive ())
+          {
+            std::cerr << "error: --server and --forced-interactive are mutually exclusive options" << std::endl;
+            octave_print_terse_usage_and_exit ();
+          }
+      }
 
     m_is_octave_program = ((m_have_script_file || m_have_eval_option_code)
                            && ! m_options.persist ()
--- a/libinterp/octave.h	Sun Jan 03 14:39:31 2021 -0500
+++ b/libinterp/octave.h	Thu Dec 17 20:18:51 2020 -0500
@@ -69,6 +69,7 @@
     bool read_history_file (void) const { return m_read_history_file; }
     bool read_init_files (void) const { return m_read_init_files; }
     bool read_site_files (void) const { return m_read_site_files; }
+    bool server (void) const { return m_server; }
     bool set_initial_path (void) const { return m_set_initial_path; }
     bool traditional (void) const { return m_traditional; }
     bool verbose_flag (void) const { return m_verbose_flag; }
@@ -99,6 +100,7 @@
     void read_history_file (bool arg) { m_read_history_file = arg; }
     void read_init_files (bool arg) { m_read_init_files = arg; }
     void read_site_files (bool arg) { m_read_site_files = arg; }
+    void server (bool arg) { m_server = arg; }
     void set_initial_path (bool arg) { m_set_initial_path = arg; }
     void traditional (bool arg) { m_traditional = arg; }
     void verbose_flag (bool arg) { m_verbose_flag = arg; }
@@ -170,6 +172,10 @@
     // (--norc; --no-site-file; -f)
     bool m_read_site_files = true;
 
+    // If TRUE, start the command server.
+    // (--server)
+    bool m_server = false;
+
     // TRUE means we set the initial path to configured defaults.
     // (--no-init-path)
     bool m_set_initial_path = true;
--- a/libinterp/options-usage.h	Sun Jan 03 14:39:31 2021 -0500
+++ b/libinterp/options-usage.h	Thu Dec 17 20:18:51 2020 -0500
@@ -41,7 +41,7 @@
        [--jit-compiler] [--line-editing] [--no-gui] [--no-history]\n\
        [--no-init-file] [--no-init-path] [--no-line-editing]\n\
        [--no-site-file] [--no-window-system] [--norc] [-p path]\n\
-       [--path path] [--persist] [--silent] [--traditional]\n\
+       [--path path] [--persist] [--server] [--silent] [--traditional]\n\
        [--verbose] [--version] [file]";
 
 // This is here so that it's more likely that the usage message and
@@ -75,8 +75,9 @@
 #define NO_LINE_EDITING_OPTION 15
 #define NO_SITE_FILE_OPTION 16
 #define PERSIST_OPTION 17
-#define TEXI_MACROS_FILE_OPTION 18
-#define TRADITIONAL_OPTION 19
+#define SERVER_OPTION 18
+#define TEXI_MACROS_FILE_OPTION 19
+#define TRADITIONAL_OPTION 20
 struct octave_getopt_options long_opts[] =
 {
   { "braindead",                octave_no_arg,       0, TRADITIONAL_OPTION },
@@ -106,6 +107,7 @@
   { "path",                     octave_required_arg, 0, 'p' },
   { "persist",                  octave_no_arg,       0, PERSIST_OPTION },
   { "quiet",                    octave_no_arg,       0, 'q' },
+  { "server",                   octave_no_arg,       0, SERVER_OPTION },
   { "silent",                   octave_no_arg,       0, 'q' },
   { "texi-macros-file",         octave_required_arg, 0, TEXI_MACROS_FILE_OPTION },
   { "traditional",              octave_no_arg,       0, TRADITIONAL_OPTION },
@@ -156,6 +158,7 @@
   --norc, -f              Don't read any initialization files.\n\
   --path PATH, -p PATH    Add PATH to head of function search path.\n\
   --persist               Go interactive after --eval or reading from FILE.\n\
+  --server                Enter server mode at startup.\n\
   --silent, --quiet, -q   Don't print message at startup.\n\
   --texi-macros-file FILE Use Texinfo macros in FILE for makeinfo command.\n\
   --traditional           Set variables for closer MATLAB compatibility.\n\
--- a/src/main.in.cc	Sun Jan 03 14:39:31 2021 -0500
+++ b/src/main.in.cc	Thu Dec 17 20:18:51 2020 -0500
@@ -213,6 +213,7 @@
   int retval = 0;
 
   int idx_gui = -1;
+  bool server = false;
   bool start_gui = false;
   bool gui_libs = true;
 
@@ -291,6 +292,11 @@
           persist_octave = true;
           new_argv[k++] = argv[i];
         }
+      else if (! strcmp (argv[i], "--server"))
+        {
+          server = true;
+          new_argv[k++] = argv[i];
+        }
       else if (! strcmp (argv[i], "--eval") ||
                (strlen (argv[i]) > 0 && argv[i][0] != '-'))
         {
@@ -350,6 +356,13 @@
           return 1;
         }
 
+      if (server)
+        {
+          std::cerr << "octave: conflicting options: --server and --gui"
+                    << std::endl;
+          return 1;
+        }
+
 #if ! defined (HAVE_OCTAVE_QT_GUI)
       std::cerr << "octave: GUI features missing or disabled in this build"
                 << std::endl;