Mercurial > octave
view libinterp/corefcn/interpreter.cc @ 32090:7d663f770c5a
load-path: Avoid using file_stat on Windows (bug #59711).
* liboctave/system/oct-time.h, oct-time.cc (octave::sys::file_time): New class
to handle an efficient native file time type per platform.
* libinterp/corefcn/load-path.h (load_path::dir_info::dir_mtime,
load_path::dir_info::dir_time_last_checked): Change type to new class.
* libinterp/corefcn/load-path.cc (subdirs_modified, load_path::dir_info::update,
load_path::dir_info::initialize): Avoid using file_stat on Windows.
author | Markus Mützel <markus.muetzel@gmx.de> |
---|---|
date | Sat, 13 May 2023 13:33:57 +0200 |
parents | 3c608abd55f5 |
children | 0c32e838e522 |
line wrap: on
line source
//////////////////////////////////////////////////////////////////////// // // Copyright (C) 1993-2023 The Octave Project Developers // // See the file COPYRIGHT.md in the top-level directory of this // distribution or <https://octave.org/copyright/>. // // This file is part of Octave. // // Octave is free software: you can redistribute it and/or modify it // under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Octave is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Octave; see the file COPYING. If not, see // <https://www.gnu.org/licenses/>. // //////////////////////////////////////////////////////////////////////// #if defined (HAVE_CONFIG_H) # include "config.h" #endif #include <cstdio> #include <clocale> #include <iostream> #include <set> #include <string> #include <thread> #include "cmd-edit.h" #include "cmd-hist.h" #include "file-ops.h" #include "file-stat.h" #include "file-ops.h" #include "fpucw-wrappers.h" #include "lo-blas-proto.h" #include "lo-error.h" #include "lo-sysdep.h" #include "oct-env.h" #include "quit.h" #include "str-vec.h" #include "signal-wrappers.h" #include "unistd-wrappers.h" #include "builtin-defun-decls.h" #include "defaults.h" #include "Cell.h" #include "defun.h" #include "display.h" #include "error.h" #include "event-manager.h" #include "graphics.h" #include "help.h" #include "input.h" #include "interpreter-private.h" #include "interpreter.h" #include "load-path.h" #include "load-save.h" #include "octave.h" #include "oct-hist.h" #include "oct-map.h" #include "oct-mutex.h" #include "ovl.h" #include "ov.h" #include "ov-classdef.h" #include "parse.h" #include "pt-classdef.h" #include "pt-eval.h" #include "pt-jump.h" #include "pt-stmt.h" #include "settings.h" #include "sighandlers.h" #include "sysdep.h" #include "unwind-prot.h" #include "utils.h" #include "variables.h" #include "version.h" // TRUE means the quit() call is allowed. bool quit_allowed = true; // TRUE means we are ready to interpret commands, but not everything // is ready for interactive use. bool octave_interpreter_ready = false; // TRUE means we've processed all the init code and we are good to go. std::atomic<bool> octave_initialized{false}; OCTAVE_BEGIN_NAMESPACE(octave) DEFUN (__version_info__, args, , doc: /* -*- texinfo -*- @deftypefn {} {retval =} __version_info__ (@var{name}, @var{version}, @var{release}, @var{date}) Undocumented internal function. @end deftypefn */) { static octave_map vinfo; int nargin = args.length (); if (nargin != 0 && nargin != 4) print_usage (); octave_value retval; if (nargin == 0) retval = vinfo; else if (nargin == 4) { if (vinfo.nfields () == 0) { vinfo.assign ("Name", args(0)); vinfo.assign ("Version", args(1)); vinfo.assign ("Release", args(2)); vinfo.assign ("Date", args(3)); } else { octave_idx_type n = vinfo.numel () + 1; vinfo.resize (dim_vector (n, 1)); octave_value idx (n); vinfo.assign (idx, "Name", Cell (octave_value (args(0)))); vinfo.assign (idx, "Version", Cell (octave_value (args(1)))); vinfo.assign (idx, "Release", Cell (octave_value (args(2)))); vinfo.assign (idx, "Date", Cell (octave_value (args(3)))); } } return retval; } DEFMETHOD (quit, interp, args, , doc: /* -*- texinfo -*- @deftypefn {} {} quit @deftypefnx {} {} quit cancel @deftypefnx {} {} quit force @deftypefnx {} {} quit ("cancel") @deftypefnx {} {} quit ("force") @deftypefnx {} {} quit (@var{status}) @deftypefnx {} {} quit (@var{status}, "force") Quit the current Octave session. The @code{exit} function is an alias for @code{quit}. If the optional integer value @var{status} is supplied, pass that value to the operating system as Octave's exit status. The default value is zero. When exiting, Octave will attempt to run the m-file @file{finish.m} if it exists. User commands to save the workspace or clean up temporary files may be placed in that file. Alternatively, another m-file may be scheduled to run using @code{atexit}. If an error occurs while executing the @file{finish.m} file, Octave does not exit and control is returned to the command prompt. If the optional argument @qcode{"cancel"} is provided, Octave does not exit and control is returned to the command prompt. This feature allows the @code{finish.m} file to cancel the quit process. If the user preference to request confirmation before exiting, Octave will display a dialog and give the user an option to cancel the exit process. If the optional argument @qcode{"force"} is provided, no confirmation is requested, and the execution of the @file{finish.m} file is skipped. @seealso{atexit} @end deftypefn */) { int numel = args.length (); if (numel > 2) print_usage (); int exit_status = 0; bool force = false; bool cancel = false; if (numel == 2) { exit_status = args(0).xnint_value ("quit: STATUS must be an integer"); std::string frc = args(1).xstring_value ("quit: second argument must be a string"); if (frc == "force") force = true; else error (R"(quit: second argument must be string "force")"); } else if (numel == 1) { if (args(0).is_string ()) { const char *msg = R"(quit: option must be string "cancel" or "force")"; std::string opt = args(0).xstring_value (msg); if (opt == "cancel") cancel = true; else if (opt == "force") force = true; else error ("%s", msg); } else exit_status = args(0).xnint_value ("quit: STATUS must be an integer"); } if (cancel) { // No effect if "quit cancel" appears outside of finish script. if (interp.executing_finish_script ()) interp.cancel_quit (true); return ovl (); } interp.quit (exit_status, force); return ovl (); } DEFALIAS (exit, quit); DEFMETHOD (atexit, interp, args, nargout, doc: /* -*- texinfo -*- @deftypefn {} {} atexit (@var{fcn}) @deftypefnx {} {} atexit (@var{fcn}, true) @deftypefnx {} {} atexit (@var{fcn}, false) @deftypefnx {} {@var{status} =} atexit (@var{fcn}, false) Register a function to be called when Octave exits. For example, @example @group function last_words () disp ("Bye bye"); endfunction atexit ("last_words"); @end group @end example @noindent will print the message @qcode{"Bye bye"} when Octave exits. The additional argument @var{flag} will register or unregister @var{fcn} from the list of functions to be called when Octave exits. If @var{flag} is true, the function is registered, and if @var{flag} is false, it is unregistered. For example, after registering the function @code{last_words} above, @example atexit ("last_words", false); @end example @noindent will remove the function from the list and Octave will not call @code{last_words} when it exits. The optional output @var{status} is only available when unregistering a function. The value is true if the unregistering was successful and false otherwise. Programming Note: @code{atexit} only removes the first occurrence of a function from the list; if a function was placed in the list multiple times with @code{atexit}, it must also be removed from the list multiple times. @seealso{quit} @end deftypefn */) { int nargin = args.length (); if (nargin < 1 || nargin > 2) print_usage (); std::string arg = args(0).xstring_value ("atexit: FCN argument must be a string"); bool add_mode = (nargin == 2) ? args(1).xbool_value ("atexit: FLAG argument must be a logical value") : true; octave_value_list retval; if (add_mode) interp.add_atexit_fcn (arg); else { bool found = interp.remove_atexit_fcn (arg); if (nargout > 0) retval = ovl (found); } return retval; } DEFMETHOD (__traditional__, interp, , , doc: /* -*- texinfo -*- @deftypefn {} {@var{tf} =} __traditional__ () Return true if Octave was invoked with the @w{@env{--traditional}} option. @end deftypefn */) { return ovl (interp.traditional ()); } temporary_file_list::~temporary_file_list () { cleanup (); } void temporary_file_list::insert (const std::string& file) { m_files.insert (file); } void temporary_file_list::cleanup () { while (! m_files.empty ()) { auto it = m_files.begin (); octave_unlink_wrapper (it->c_str ()); m_files.erase (it); } } // The time we last time we changed directories. sys::time Vlast_chdir_time = 0.0; static void initialize_version_info () { octave_value_list args (4); args(0) = "GNU Octave"; args(1) = OCTAVE_VERSION; args(2) = config::release (); args(3) = OCTAVE_RELEASE_DATE; F__version_info__ (args); } static void xerbla_abort () { error ("Fortran procedure terminated by call to XERBLA"); } static void initialize_xerbla_error_handler () { // The idea here is to force xerbla to be referenced so that we will // link to our own version instead of the one provided by the BLAS // library. But numeric_limits<double>::NaN () should never be -1, so // we should never actually call xerbla. FIXME (again!): If this // becomes a constant expression the test might be optimized away and // then the reference to the function might also disappear. if (numeric_limits<double>::NaN () == -1) F77_FUNC (xerbla, XERBLA) ("octave", 13 F77_CHAR_ARG_LEN (6)); typedef void (*xerbla_handler_ptr) (); typedef void (*octave_set_xerbla_handler_ptr) (xerbla_handler_ptr); dynamic_library libs (""); if (libs) { octave_set_xerbla_handler_ptr octave_set_xerbla_handler = reinterpret_cast<octave_set_xerbla_handler_ptr> (libs.search ("octave_set_xerbla_handler")); if (octave_set_xerbla_handler) octave_set_xerbla_handler (xerbla_abort); } } OCTAVE_NORETURN static void lo_error_handler (const char *fmt, ...) { va_list args; va_start (args, fmt); verror_with_cfn (fmt, args); va_end (args); throw execution_exception (); } OCTAVE_NORETURN static void lo_error_with_id_handler (const char *id, const char *fmt, ...) { va_list args; va_start (args, fmt); verror_with_id_cfn (id, fmt, args); va_end (args); throw execution_exception (); } static void initialize_error_handlers () { set_liboctave_error_handler (lo_error_handler); set_liboctave_error_with_id_handler (lo_error_with_id_handler); set_liboctave_warning_handler (warning); set_liboctave_warning_with_id_handler (warning_with_id); } // Create an interpreter object and perform initialization up to the // point of setting reading command history and setting the load // path. interpreter::interpreter (application *app_context) : m_app_context (app_context), m_tmp_files (), m_atexit_fcns (), m_display_info (), m_environment (), m_settings (), m_error_system (*this), m_evaluator (*this), m_help_system (*this), m_input_system (*this), m_output_system (*this), m_history_system (*this), m_dynamic_loader (*this), m_load_path (*this), m_load_save_system (*this), m_type_info (), m_symbol_table (*this), m_stream_list (*this), m_child_list (), m_url_handle_manager (), m_cdef_manager (*this), m_gtk_manager (*this), m_event_manager (*this), m_gh_manager (nullptr), m_interactive (false), m_read_site_files (true), m_read_init_files (m_app_context != nullptr), m_verbose (false), m_traditional (false), m_inhibit_startup_message (false), m_load_path_initialized (false), m_history_initialized (false), m_interrupt_all_in_process_group (true), m_cancel_quit (false), m_executing_finish_script (false), m_executing_atexit (false), m_initialized (false) { // FIXME: When thread_local storage is used by default, this message // should change to say something like // // only one Octave interpreter may be active in any given thread if (s_instance) throw std::runtime_error ("only one Octave interpreter may be active"); s_instance = this; #if defined (OCTAVE_HAVE_WINDOWS_UTF8_LOCALE) // Force a UTF-8 locale on Windows if possible std::setlocale (LC_ALL, ".UTF8"); #else std::setlocale (LC_ALL, ""); #endif // Matlab uses "C" locale for LC_NUMERIC class regardless of local setting std::setlocale (LC_NUMERIC, "C"); std::setlocale (LC_TIME, "C"); sys::env::putenv ("LC_NUMERIC", "C"); sys::env::putenv ("LC_TIME", "C"); // Initialize the default floating point unit control state. octave_set_default_fpucw (); thread::init (); octave_ieee_init (); initialize_xerbla_error_handler (); initialize_error_handlers (); if (m_app_context) { install_signal_handlers (); octave_unblock_signal_by_name ("SIGTSTP"); } else quit_allowed = false; if (! m_app_context) m_display_info.initialize (); bool line_editing = false; if (m_app_context) { // Embedded interpreters don't execute command line options. const cmdline_options& options = m_app_context->options (); // Make all command-line arguments available to startup files, // including PKG_ADD files. string_vector args = options.all_args (); m_app_context->intern_argv (args); intern_nargin (args.numel () - 1); bool is_octave_program = m_app_context->is_octave_program (); std::list<std::string> command_line_path = options.command_line_path (); for (const auto& pth : command_line_path) m_load_path.set_command_line_path (pth); std::string exec_path = options.exec_path (); if (! exec_path.empty ()) m_environment.exec_path (exec_path); std::string image_path = options.image_path (); if (! image_path.empty ()) m_environment.image_path (image_path); if (! options.no_window_system ()) m_display_info.initialize (); // Is input coming from a terminal? If so, we are probably // interactive. // If stdin is not a tty, then we are reading commands from a // pipe or a redirected file. bool stdin_is_tty = octave_isatty_wrapper (fileno (stdin)); m_interactive = (! is_octave_program && stdin_is_tty && octave_isatty_wrapper (fileno (stdout))); // Don't force interactive if we're already interactive (bug #60696). bool forced_interactive = options.forced_interactive (); if (m_interactive) { m_app_context->forced_interactive (false); forced_interactive = false; } // Check if the user forced an interactive session. if (forced_interactive) m_interactive = true; line_editing = options.line_editing (); if ((! m_interactive || forced_interactive) && ! options.forced_line_editing ()) line_editing = false; m_traditional = options.traditional (); // FIXME: if possible, perform the following actions directly // instead of using the interpreter-level functions. if (options.echo_commands ()) m_evaluator.echo (tree_evaluator::ECHO_SCRIPTS | tree_evaluator::ECHO_FUNCTIONS | tree_evaluator::ECHO_ALL); std::string docstrings_file = options.docstrings_file (); if (! docstrings_file.empty ()) Fbuilt_in_docstrings_file (*this, ovl (docstrings_file)); std::string doc_cache_file = options.doc_cache_file (); if (! doc_cache_file.empty ()) Fdoc_cache_file (*this, ovl (doc_cache_file)); std::string info_file = options.info_file (); if (! info_file.empty ()) Finfo_file (*this, ovl (info_file)); std::string info_program = options.info_program (); if (! info_program.empty ()) Finfo_program (*this, ovl (info_program)); std::string texi_macros_file = options.texi_macros_file (); if (! texi_macros_file.empty ()) Ftexi_macros_file (*this, ovl (texi_macros_file)); } // FIXME: we defer creation of the gh_manager object because it // creates a root_figure object that requires the display_info // object, but that is currently only accessible through the global // interpreter object and that is not available until after the // interpreter::instance pointer is set (above). It would be better // if m_gh_manager could be an object value instead of a pointer and // created as part of the interpreter initialization. To do that, // we should either make the display_info object independent of the // interpreter object (does it really need to cache any // information?) or defer creation of the root_figure object until // it is actually needed. m_gh_manager = new gh_manager (*this); m_input_system.initialize (line_editing); // These can come after command line args since none of them set any // defaults that might be changed by command line options. initialize_version_info (); // This should be done before initializing the load path because // some PKG_ADD files might need --traditional behavior. if (m_traditional) maximum_braindamage (); octave_interpreter_ready = true; } OCTAVE_THREAD_LOCAL interpreter *interpreter::s_instance = nullptr; interpreter::~interpreter () { if (! m_app_context) shutdown (); delete m_gh_manager; } void interpreter::intern_nargin (octave_idx_type nargs) { m_evaluator.set_auto_fcn_var (stack_frame::NARGIN, nargs); } // Read the history file unless a command-line option inhibits that. void interpreter::initialize_history (bool read_history_file) { if (! m_history_initialized) { // Allow command-line option to override. if (m_app_context) { const cmdline_options& options = m_app_context->options (); read_history_file = options.read_history_file (); if (! read_history_file) command_history::ignore_entries (); } m_history_system.initialize (read_history_file); if (! m_app_context) command_history::ignore_entries (); m_history_initialized = true; } } // Set the initial path to the system default unless command-line // option says to leave it empty. void interpreter::initialize_load_path (bool set_initial_path) { if (! m_load_path_initialized) { // Allow command-line option to override. if (m_app_context) { const cmdline_options& options = m_app_context->options (); set_initial_path = options.set_initial_path (); } // Temporarily set the execute_pkg_add function to one that // catches exceptions. This is better than wrapping // load_path::initialize in a try-catch block because it will // not stop executing PKG_ADD files at the first exception. // It's also better than changing the default execute_pkg_add // function to use safe_source file because that will normally // be evaluated from the normal interpreter loop where exceptions // are already handled. unwind_action restore_add_hook (&load_path::set_add_hook, &m_load_path, m_load_path.get_add_hook ()); m_load_path.set_add_hook ([=] (const std::string& dir) { this->execute_pkg_add (dir); }); m_load_path.initialize (set_initial_path); m_load_path_initialized = true; } } // This may be called separately from execute void interpreter::initialize () { if (m_initialized) return; if (m_app_context) { const cmdline_options& options = m_app_context->options (); if (options.experimental_terminal_widget ()) { if (! options.gui ()) display_startup_message (); } else display_startup_message (); } else display_startup_message (); // Wait to read the history file until the interpreter reads input // files and begins evaluating commands. initialize_history (); // Initializing the load path may execute PKG_ADD files, so can't be // done until the interpreter is ready to execute commands. // Deferring it to the execute step also allows the path to be // initialized between creating and execute the interpreter, for // example, to set a custom path for an embedded interpreter. initialize_load_path (); octave_save_signal_mask (); can_interrupt = true; octave_signal_hook = respond_to_pending_signals; octave_interrupt_hook = nullptr; catch_interrupts (); // FIXME: could we eliminate this variable or make it not be global? // Global used to communicate with signal handler. octave_initialized = true; m_initialized = true; } // Note: this function is currently only used with the new // experimental terminal widget. void interpreter::get_line_and_eval () { m_evaluator.get_line_and_eval (); } // Note: the following class is currently only used with the new // experimental terminal widget. class cli_input_reader { public: cli_input_reader (interpreter& interp) : m_interpreter (interp), m_thread () { } OCTAVE_DISABLE_CONSTRUCT_COPY_MOVE (cli_input_reader) ~cli_input_reader () { // FIXME: Would it be better to ensure that // interpreter::get_line_and_eval exits and then call // m_thread.join () here? m_thread.detach (); } void start () { m_thread = std::thread (&interpreter::get_line_and_eval, &m_interpreter); } private: interpreter& m_interpreter; std::thread m_thread; }; void interpreter::parse_and_execute (const std::string& input, bool& incomplete_parse) { m_evaluator.parse_and_execute (input, incomplete_parse); } // FIXME: this function is intended to be executed only once. Should // we enforce that restriction? int interpreter::execute () { int exit_status = 0; try { initialize (); execute_startup_files (); if (m_app_context) { const cmdline_options& options = m_app_context->options (); if (m_app_context->have_eval_option_code ()) { int status = execute_eval_option_code (); if (status ) exit_status = status; if (! options.persist ()) { shutdown (); return exit_status; } } // If there is an extra argument, see if it names a file to // read. Additional arguments are taken as command line options // for the script. if (m_app_context->have_script_file ()) { int status = execute_command_line_file (); if (status) exit_status = status; if (! options.persist ()) { shutdown (); return exit_status; } } if (options.forced_interactive ()) command_editor::blink_matching_paren (false); if (options.server ()) exit_status = server_loop (); else if (options.experimental_terminal_widget ()) { if (options.gui ()) { m_event_manager.start_gui (true); exit_status = server_loop (); } else { // Use an object so that the thread started for the // reader will be cleaned up no matter how we exit // this function. cli_input_reader reader (*this); reader.start (); exit_status = server_loop (); } } else exit_status = main_loop (); shutdown (); } } catch (const exit_exception& xe) { exit_status = xe.exit_status (); shutdown (); } return exit_status; } // Call a function with exceptions handled to avoid problems with // errors while shutting down. #define OCTAVE_IGNORE_EXCEPTION(E) \ catch (E) \ { \ recover_from_exception (); \ \ std::cerr << "error: ignoring " #E " while preparing to exit" \ << std::endl; \ } #define OCTAVE_SAFE_CALL(F, ARGS) \ do \ { \ try \ { \ unwind_action restore_debug_on_error \ (&error_system::set_debug_on_error, &m_error_system, \ m_error_system.debug_on_error ()); \ \ unwind_action restore_debug_on_warning \ (&error_system::set_debug_on_warning, &m_error_system, \ m_error_system.debug_on_warning ()); \ \ m_error_system.debug_on_error (false); \ m_error_system.debug_on_warning (false); \ \ F ARGS; \ } \ OCTAVE_IGNORE_EXCEPTION (const exit_exception&) \ OCTAVE_IGNORE_EXCEPTION (const interrupt_exception&) \ OCTAVE_IGNORE_EXCEPTION (const execution_exception&) \ OCTAVE_IGNORE_EXCEPTION (const std::bad_alloc&) \ } \ while (0) void interpreter::shutdown () { // Attempt to prevent more than one call to shutdown. if (! m_initialized) return; m_initialized = false; OCTAVE_SAFE_CALL (feval, ("close", ovl ("all"), 0)); // Any atexit functions added after this function call won't be // executed. Each atexit function is executed with // OCTAVE_SAFE_CALL, so we don't need that here. execute_atexit_fcns (); // Clear all functions and variables while the event manager is // still processing events and notify the event manager. This way, // the workspace model will be cleared before the GUI exits. // FIXME: This approach seems a bit fragile since there could be // other places in the GUI that have references to interpreter // objects. How can we reliably ensure that they are all removed // before the interpreter exits? Maybe the best solution is to // always start the GUI from the interpreter and close it when the // interpreter exits? However, the workspace model is owned by the // base_qobject object not the workspace viewer or the main window, // so simply closing the GUI window(s) is not sufficient. See also // bug #61994. // Note that we don't force symbols to be cleared, so we will // respect mlock at this point. Later, we'll force all variables // and functions to be cleared. OCTAVE_SAFE_CALL (clear_all, ()); OCTAVE_SAFE_CALL (m_event_manager.clear_workspace, ()); // If we are attached to a GUI, queue and event to close it (only // works with the new terminal widget), process pending events and // disable the link. OCTAVE_SAFE_CALL (m_event_manager.close_gui, ()); OCTAVE_SAFE_CALL (m_event_manager.process_events, (true)); OCTAVE_SAFE_CALL (m_event_manager.disable, ()); OCTAVE_SAFE_CALL (m_input_system.clear_input_event_hooks, ()); // We may still have some figures. Close them. OCTAVE_SAFE_CALL (feval, ("close", ovl ("all"), 0)); // What is supposed to happen if a figure has a closerequestfcn or // deletefcn callback registered that creates other figures or // variables? What if those variables are classdef objects with // destructors that can create figures? The possibilities are // endless. At some point, we have to give up and force execution // to end. // Note that we again don't force symbols to be cleared, so we // continue to respect mlock here. Later, we'll force all variables // and functions to be cleared. OCTAVE_SAFE_CALL (clear_all, ()); // Do this explicitly so that destructors for mex file objects // are called, so that functions registered with mexAtExit are // called. OCTAVE_SAFE_CALL (m_symbol_table.clear_mex_functions, ()); OCTAVE_SAFE_CALL (command_editor::restore_terminal_state, ()); OCTAVE_SAFE_CALL (m_history_system.write_timestamp, ()); if (! command_history::ignoring_entries ()) OCTAVE_SAFE_CALL (command_history::clean_up_and_save, ()); OCTAVE_SAFE_CALL (m_gtk_manager.unload_all_toolkits, ()); // Now that the graphics toolkits have been unloaded, force all // symbols to be cleared. OCTAVE_SAFE_CALL (clear_all, (true)); // FIXME: May still need something like this to ensure that // destructors for class objects will run properly. Should that be // done earlier? Before or after atexit functions are executed? // What will happen if the destructor for an obect attempts to // display a figure? OCTAVE_SAFE_CALL (m_symbol_table.cleanup, ()); OCTAVE_SAFE_CALL (sysdep_cleanup, ()); OCTAVE_SAFE_CALL (flush_stdout, ()); // Don't call singleton_cleanup_list::cleanup until we have the // problems with registering/unregistering types worked out. For // example, uncomment the following line, then use the make_int // function from the examples directory to create an integer // object and then exit Octave. Octave should crash with a // segfault when cleaning up the typinfo singleton. We need some // way to force new octave_value_X types that are created in // .oct files to be unregistered when the .oct file shared library // is unloaded. // // OCTAVE_SAFE_CALL (singleton_cleanup_list::cleanup, ()); } void interpreter::execute_atexit_fcns () { // Prevent atexit functions from adding new functions to the list. m_executing_atexit = true; while (! m_atexit_fcns.empty ()) { std::string fcn = m_atexit_fcns.front (); m_atexit_fcns.pop_front (); OCTAVE_SAFE_CALL (feval, (fcn, octave_value_list (), 0)); OCTAVE_SAFE_CALL (flush_stdout, ()); } } void interpreter::display_startup_message () const { bool inhibit_startup_message = false; if (m_app_context) { const cmdline_options& options = m_app_context->options (); inhibit_startup_message = options.inhibit_startup_message (); } if (m_interactive && ! inhibit_startup_message) std::cout << octave_startup_message () << "\n" << std::endl; } // Initialize by reading startup files. Return nonzero if an exception // occurs when reading any of them, but don't exit early because of an // exception. int interpreter::execute_startup_files () { bool read_site_files = m_read_site_files; bool read_init_files = m_read_init_files; bool verbose = m_verbose; bool inhibit_startup_message = m_inhibit_startup_message; if (m_app_context) { const cmdline_options& options = m_app_context->options (); read_site_files = options.read_site_files (); read_init_files = options.read_init_files (); verbose = options.verbose_flag (); inhibit_startup_message = options.inhibit_startup_message (); } verbose = (verbose && ! inhibit_startup_message); bool require_file = false; std::string context; int exit_status = 0; if (read_site_files) { // Execute commands from the site-wide configuration file. // First from the file $(prefix)/lib/octave/site/m/octaverc // (if it exists), then from the file // $(prefix)/share/octave/$(version)/m/octaverc (if it exists). int status = safe_source_file (config::local_site_defaults_file (), context, verbose, require_file); if (status) exit_status = status; status = safe_source_file (config::site_defaults_file (), context, verbose, require_file); if (status) exit_status = status; } if (read_init_files) { // Try to execute commands from the Matlab compatible startup.m file // if it exists anywhere in the load path when starting Octave. std::string ff_startup_m = file_in_path ("startup.m", ""); if (! ff_startup_m.empty ()) { int parse_status = 0; try { eval_string (std::string ("startup"), false, parse_status, 0); } catch (const interrupt_exception&) { recover_from_exception (); } catch (const execution_exception& ee) { handle_exception (ee); } } // Try to execute commands from $CONFIG/octave/octaverc, where // $CONFIG is the platform-dependent location for user local // configuration files. std::string user_config_dir = sys::env::get_user_config_directory (); std::string cfg_dir = user_config_dir + sys::file_ops::dir_sep_str () + "octave"; std::string cfg_rc = sys::env::make_absolute ("octaverc", cfg_dir); if (! cfg_rc.empty ()) { int status = safe_source_file (cfg_rc, context, verbose, require_file); if (status) exit_status = status; } // Try to execute commands from $HOME/$OCTAVE_INITFILE and // $OCTAVE_INITFILE. If $OCTAVE_INITFILE is not set, // .octaverc is assumed. bool home_rc_already_executed = false; std::string initfile = sys::env::getenv ("OCTAVE_INITFILE"); if (initfile.empty ()) initfile = ".octaverc"; std::string home_dir = sys::env::get_home_directory (); std::string home_rc = sys::env::make_absolute (initfile, home_dir); std::string local_rc; if (! home_rc.empty ()) { int status = safe_source_file (home_rc, context, verbose, require_file); if (status) exit_status = status; // Names alone are not enough. if (sys::file_exists (home_rc)) { // We want to check for curr_dir after executing home_rc // because doing that may change the working directory. local_rc = sys::env::make_absolute (initfile); home_rc_already_executed = sys::same_file (home_rc, local_rc); } } if (! home_rc_already_executed) { if (local_rc.empty ()) local_rc = sys::env::make_absolute (initfile); int status = safe_source_file (local_rc, context, verbose, require_file); if (status) exit_status = status; } } if (m_interactive && verbose) std::cout << std::endl; return exit_status; } // Execute any code specified with --eval 'CODE' int interpreter::execute_eval_option_code () { if (! m_app_context) return 0; const cmdline_options& options = m_app_context->options (); std::string code_to_eval = options.code_to_eval (); unwind_protect_var<bool> upv (m_interactive, false); int parse_status = 0; try { eval_string (code_to_eval, false, parse_status, 0); } catch (const interrupt_exception&) { recover_from_exception (); return 1; } catch (const execution_exception& ee) { handle_exception (ee); return 1; } return parse_status; } int interpreter::execute_command_line_file () { if (! m_app_context) return 0; const cmdline_options& options = m_app_context->options (); string_vector args = options.all_args (); void (interpreter::*interactive_fptr) (bool) = &interpreter::interactive; unwind_action restore_interactive (interactive_fptr, this, m_interactive); unwind_action restore_argv (&application::intern_argv, m_app_context, args); unwind_action restore_nargin (&interpreter::intern_nargin, this, args.numel () - 1); void (application::*program_invocation_name_fptr) (const std::string&) = &application::program_invocation_name; unwind_action restore_program_invocation_name (program_invocation_name_fptr, m_app_context, application::program_invocation_name ()); void (application::*program_name_fptr) (const std::string&) = &application::program_name; unwind_action restore_program_name (program_name_fptr, m_app_context, application::program_name ()); m_interactive = false; // If we are running an executable script (#! /bin/octave) then // we should only see the args passed to the script. string_vector script_args = options.remaining_args (); m_app_context->intern_argv (script_args); intern_nargin (script_args.numel () - 1); std::string fname = script_args[0]; m_app_context->set_program_names (fname); std::string context; bool verbose = false; bool require_file = true; return safe_source_file (fname, context, verbose, require_file); } int interpreter::main_loop () { command_editor::add_event_hook (release_unreferenced_dynamic_libraries); return m_evaluator.repl (); } int interpreter::server_loop () { return m_evaluator.server_loop (); } tree_evaluator& interpreter::get_evaluator () { return m_evaluator; } stream_list& interpreter::get_stream_list () { return m_stream_list; } url_handle_manager& interpreter::get_url_handle_manager () { return m_url_handle_manager; } symbol_scope interpreter::get_top_scope () const { return m_evaluator.get_top_scope (); } symbol_scope interpreter::get_current_scope () const { return m_evaluator.get_current_scope (); } symbol_scope interpreter::require_current_scope (const std::string& who) const { symbol_scope scope = get_current_scope (); if (! scope) error ("%s: symbol table scope missing", who.c_str ()); return scope; } profiler& interpreter::get_profiler () { return m_evaluator.get_profiler (); } int interpreter::chdir (const std::string& dir) { std::string xdir = sys::file_ops::tilde_expand (dir); int cd_ok = sys::env::chdir (xdir); if (! cd_ok) error ("%s: %s", dir.c_str (), std::strerror (errno)); Vlast_chdir_time.stamp (); // FIXME: should these actions be handled as a list of functions // to call so users can add their own chdir handlers? m_load_path.read_dir_config ("."); m_load_path.update (); m_event_manager.directory_changed (sys::env::get_current_directory ()); return cd_ok; } void interpreter::mlock (bool skip_first) const { m_evaluator.mlock (skip_first); } void interpreter::munlock (bool skip_first) const { m_evaluator.munlock (skip_first); } bool interpreter::mislocked (bool skip_first) const { return m_evaluator.mislocked (skip_first); } void interpreter::munlock (const char *nm) { if (! nm) error ("munlock: invalid value for NAME"); munlock (std::string (nm)); } void interpreter::munlock (const std::string& nm) { octave_value val = m_symbol_table.find_function (nm); if (val.is_defined ()) { octave_function *fcn = val.function_value (); if (fcn) fcn->unlock (); } } bool interpreter::mislocked (const char *nm) { if (! nm) error ("mislocked: invalid value for NAME"); return mislocked (std::string (nm)); } bool interpreter::mislocked (const std::string& nm) { bool retval = false; octave_value val = m_symbol_table.find_function (nm); if (val.is_defined ()) { octave_function *fcn = val.function_value (); if (fcn) retval = fcn->islocked (); } return retval; } std::string interpreter::mfilename (const std::string& opt) const { return m_evaluator.mfilename (opt); } octave_value_list interpreter::eval_string (const std::string& eval_str, bool silent, int& parse_status, int nargout) { return m_evaluator.eval_string (eval_str, silent, parse_status, nargout); } octave_value interpreter::eval_string (const std::string& eval_str, bool silent, int& parse_status) { return m_evaluator.eval_string (eval_str, silent, parse_status); } octave_value_list interpreter::eval_string (const octave_value& arg, bool silent, int& parse_status, int nargout) { return m_evaluator.eval_string (arg, silent, parse_status, nargout); } octave_value_list interpreter::eval (const std::string& try_code, int nargout) { return m_evaluator.eval (try_code, nargout); } octave_value_list interpreter::eval (const std::string& try_code, const std::string& catch_code, int nargout) { return m_evaluator.eval (try_code, catch_code, nargout); } octave_value_list interpreter::evalin (const std::string& context, const std::string& try_code, int nargout) { return m_evaluator.evalin (context, try_code, nargout); } octave_value_list interpreter::evalin (const std::string& context, const std::string& try_code, const std::string& catch_code, int nargout) { return m_evaluator.evalin (context, try_code, catch_code, nargout); } //! Evaluate an Octave function (built-in or interpreted) and return //! the list of result values. //! //! @param name The name of the function to call. //! @param args The arguments to the function. //! @param nargout The number of output arguments expected. //! @return A list of output values. The length of the list is not //! necessarily the same as @c nargout. octave_value_list interpreter::feval (const char *name, const octave_value_list& args, int nargout) { return feval (std::string (name), args, nargout); } octave_value_list interpreter::feval (const std::string& name, const octave_value_list& args, int nargout) { octave_value fcn = m_symbol_table.find_function (name, args); if (fcn.is_undefined ()) error ("feval: function '%s' not found", name.c_str ()); octave_function *of = fcn.function_value (); return of->call (m_evaluator, nargout, args); } octave_value_list interpreter::feval (octave_function *fcn, const octave_value_list& args, int nargout) { if (fcn) return fcn->call (m_evaluator, nargout, args); return octave_value_list (); } octave_value_list interpreter::feval (const octave_value& val, const octave_value_list& args, int nargout) { // FIXME: do we really want to silently return an empty ovl if // the function object is undefined? It's essentially what the // version above that accepts a pointer to an octave_function // object does and some code was apparently written to rely on it // (for example, __ode15__). if (val.is_undefined ()) return ovl (); if (val.is_function ()) { return feval (val.function_value (), args, nargout); } else if (val.is_function_handle () || val.is_inline_function ()) { // This covers function handles, inline functions, and anonymous // functions. std::list<octave_value_list> arg_list; arg_list.push_back (args); // FIXME: could we make octave_value::subsref a const method? // It would be difficult because there are instances of // incrementing the reference count inside subsref methods, // which means they can't be const with the current way of // handling reference counting. octave_value xval = val; return xval.subsref ("(", arg_list, nargout); } else if (val.is_string ()) { return feval (val.string_value (), args, nargout); } else error ("feval: first argument must be a string, inline function, or a function handle"); return ovl (); } //! Evaluate an Octave function (built-in or interpreted) and return //! the list of result values. //! //! @param args The first element of @c args is the function to call. //! It may be the name of the function as a string, a function //! handle, or an inline function. The remaining arguments are //! passed to the function. //! @param nargout The number of output arguments expected. //! @return A list of output values. The length of the list is not //! necessarily the same as @c nargout. octave_value_list interpreter::feval (const octave_value_list& args, int nargout) { if (args.length () == 0) error ("feval: first argument must be a string, inline function, or a function handle"); octave_value f_arg = args(0); octave_value_list tmp_args = args.slice (1, args.length () - 1, true); return feval (f_arg, tmp_args, nargout); } octave_value interpreter::make_function_handle (const std::string& name) { return m_evaluator.make_fcn_handle (name); } void interpreter::install_variable (const std::string& name, const octave_value& value, bool global) { m_evaluator.install_variable (name, value, global); } octave_value interpreter::global_varval (const std::string& name) const { return m_evaluator.global_varval (name); } void interpreter::global_assign (const std::string& name, const octave_value& val) { m_evaluator.global_assign (name, val); } octave_value interpreter::top_level_varval (const std::string& name) const { return m_evaluator.top_level_varval (name); } void interpreter::top_level_assign (const std::string& name, const octave_value& val) { m_evaluator.top_level_assign (name, val); } bool interpreter::is_variable (const std::string& name) const { return m_evaluator.is_variable (name); } bool interpreter::is_local_variable (const std::string& name) const { return m_evaluator.is_local_variable (name); } octave_value interpreter::varval (const std::string& name) const { return m_evaluator.varval (name); } void interpreter::assign (const std::string& name, const octave_value& val) { m_evaluator.assign (name, val); } void interpreter::assignin (const std::string& context, const std::string& name, const octave_value& val) { m_evaluator.assignin (context, name, val); } void interpreter::source_file (const std::string& file_name, const std::string& context, bool verbose, bool require_file) { m_evaluator.source_file (file_name, context, verbose, require_file); } bool interpreter::at_top_level () const { return m_evaluator.at_top_level (); } bool interpreter::isglobal (const std::string& name) const { return m_evaluator.is_global (name); } octave_value interpreter::find (const std::string& name) { return m_evaluator.find (name); } void interpreter::clear_all (bool force) { m_evaluator.clear_all (force); } void interpreter::clear_objects () { m_evaluator.clear_objects (); } void interpreter::clear_variable (const std::string& name) { m_evaluator.clear_variable (name); } void interpreter::clear_variable_pattern (const std::string& pattern) { m_evaluator.clear_variable_pattern (pattern); } void interpreter::clear_variable_regexp (const std::string& pattern) { m_evaluator.clear_variable_regexp (pattern); } void interpreter::clear_variables () { m_evaluator.clear_variables (); } void interpreter::clear_global_variable (const std::string& name) { m_evaluator.clear_global_variable (name); } void interpreter::clear_global_variable_pattern (const std::string& pattern) { m_evaluator.clear_global_variable_pattern (pattern); } void interpreter::clear_global_variable_regexp (const std::string& pattern) { m_evaluator.clear_global_variable_regexp (pattern); } void interpreter::clear_global_variables () { m_evaluator.clear_global_variables (); } void interpreter::clear_functions (bool force) { m_symbol_table.clear_functions (force); } void interpreter::clear_function (const std::string& name) { m_symbol_table.clear_function (name); } void interpreter::clear_symbol (const std::string& name) { m_evaluator.clear_symbol (name); } void interpreter::clear_function_pattern (const std::string& pat) { m_symbol_table.clear_function_pattern (pat); } void interpreter::clear_function_regexp (const std::string& pat) { m_symbol_table.clear_function_regexp (pat); } void interpreter::clear_symbol_pattern (const std::string& pat) { return m_evaluator.clear_symbol_pattern (pat); } void interpreter::clear_symbol_regexp (const std::string& pat) { return m_evaluator.clear_symbol_regexp (pat); } std::list<std::string> interpreter::global_variable_names () { return m_evaluator.global_variable_names (); } std::list<std::string> interpreter::top_level_variable_names () { return m_evaluator.top_level_variable_names (); } std::list<std::string> interpreter::variable_names () { return m_evaluator.variable_names (); } std::list<std::string> interpreter::user_function_names () { return m_symbol_table.user_function_names (); } std::list<std::string> interpreter::autoloaded_functions () const { return m_evaluator.autoloaded_functions (); } // May be used to send an interrupt signal to the the interpreter from // another thread (for example, the GUI). void interpreter::interrupt () { static int sigint = 0; static bool first = true; if (first) { octave_get_sig_number ("SIGINT", &sigint); first = false; } // Send SIGINT to Octave and (optionally) all other processes in its // process group. The signal handler for SIGINT will set a global // variable indicating an interrupt has happened. That variable is // checked in many places in the Octave interpreter and eventually // results in an interrupt_exception being thrown. Finally, that // exception is caught and returns control to one of the // read-eval-print loops or to the server loop. We use a signal // instead of just setting the global variables here so that we will // probably send interrupt signals to any subprocesses as well as // interrupt execution of the interpreter. pid_t pid = m_interrupt_all_in_process_group ? 0 : octave_getpid_wrapper (); octave_kill_wrapper (pid, sigint); } void interpreter::pause () { // FIXME: To be reliable, these tree_evaluator functions must be // made thread safe. m_evaluator.break_on_next_statement (true); m_evaluator.reset_debug_state (); } void interpreter::stop () { // FIXME: To be reliable, these tree_evaluator functions must be // made thread safe. if (m_evaluator.in_debug_repl ()) m_evaluator.dbquit (true); else interrupt (); } void interpreter::resume () { // FIXME: To be reliable, these tree_evaluator functions must be // made thread safe. // FIXME: Should there be any feeback about not doing anything if // not in debug mode? if (m_evaluator.in_debug_repl ()) m_evaluator.dbcont (); } octave_value interpreter::PS1 (const octave_value_list& args, int nargout) { return m_input_system.PS1 (args, nargout); } std::string interpreter::PS1 () const { return m_input_system.PS1 (); } std::string interpreter::PS1 (const std::string& s) { return m_input_system.PS1 (s); } void interpreter::set_PS1 (const std::string& s) { m_input_system.set_PS1 (s); } octave_value interpreter::PS2 (const octave_value_list& args, int nargout) { return m_input_system.PS2 (args, nargout); } std::string interpreter::PS2 () const { return m_input_system.PS2 (); } std::string interpreter::PS2 (const std::string& s) { return m_input_system.PS2 (s); } void interpreter::set_PS2 (const std::string& s) { m_input_system.set_PS2 (s); } octave_value interpreter::PS4 (const octave_value_list& args, int nargout) { return m_evaluator.PS4 (args, nargout); } std::string interpreter::PS4 () const { return m_evaluator.PS4 (); } std::string interpreter::PS4 (const std::string& s) { return m_evaluator.PS4 (s); } void interpreter::set_PS4 (const std::string& s) { m_evaluator.set_PS4 (s); } // Provided for convenience. Will be removed once we eliminate the // old terminal widget. bool interpreter::experimental_terminal_widget () const { if (! m_app_context) return false; // Embedded interpreters don't execute command line options. const cmdline_options& options = m_app_context->options (); return options.experimental_terminal_widget (); } void interpreter::add_debug_watch_expression (const std::string& expr) { m_evaluator.add_debug_watch_expression (expr); } void interpreter::remove_debug_watch_expression (const std::string& expr) { m_evaluator.remove_debug_watch_expression (expr); } void interpreter::clear_debug_watch_expressions () { m_evaluator.clear_debug_watch_expressions (); } std::set<std::string> interpreter::debug_watch_expressions () const { return m_evaluator.debug_watch_expressions (); } void interpreter::handle_exception (const execution_exception& ee) { m_error_system.save_exception (ee); // FIXME: use a separate stream instead of std::cerr directly so that // error messages can be redirected more easily? Pass the message // to an event manager function? m_error_system.display_exception (ee); recover_from_exception (); } void interpreter::recover_from_exception () { if (octave_interrupt_state) m_event_manager.interpreter_interrupted (); can_interrupt = true; octave_interrupt_state = 0; octave_signal_caught = false; octave_restore_signal_mask (); catch_interrupts (); } void interpreter::mark_for_deletion (const std::string& file) { m_tmp_files.insert (file); } void interpreter::cleanup_tmp_files () { m_tmp_files.cleanup (); } void interpreter::quit (int exit_status, bool force, bool confirm) { if (! force) { try { bool cancel = false; if (symbol_exist ("finish.m", "file")) { unwind_protect_var<bool> upv1 (m_executing_finish_script, true); unwind_protect_var<bool> upv2 (m_cancel_quit); evalin ("base", "finish", 0); cancel = m_cancel_quit; } if (cancel) return; // Check for confirmation. if (confirm && ! m_event_manager.confirm_shutdown ()) return; } catch (const execution_exception&) { // Catch execution_exceptions so we don't throw an // exit_exception if there is an in finish.m. But throw it // again so that will be handled as any other // execution_exception by the evaluator. This way, errors // will be ignored properly and we won't exit if quit is // called recursively from finish.m. throw; } } throw exit_exception (exit_status); } void interpreter::add_atexit_fcn (const std::string& fname) { if (m_executing_atexit) return; m_atexit_fcns.push_front (fname); } bool interpreter::remove_atexit_fcn (const std::string& fname) { bool found = false; for (auto it = m_atexit_fcns.begin (); it != m_atexit_fcns.end (); it++) { if (*it == fname) { m_atexit_fcns.erase (it); found = true; break; } } return found; } // What internal options get configured by --traditional. void interpreter::maximum_braindamage () { PS1 (">> "); PS2 (""); PS4 (""); m_load_save_system.crash_dumps_octave_core (false); m_load_save_system.save_default_options ("-mat-binary"); m_history_system.timestamp_format_string ("%%-- %D %I:%M %p --%%"); m_error_system.beep_on_error (true); Fconfirm_recursive_rmdir (ovl (false)); Foptimize_diagonal_matrix (ovl (false)); Foptimize_permutation_matrix (ovl (false)); Foptimize_range (ovl (false)); Ffixed_point_format (ovl (true)); Fprint_empty_dimensions (ovl (false)); Fprint_struct_array_contents (ovl (true)); Fstruct_levels_to_print (ovl (0)); m_error_system.disable_warning ("Octave:abbreviated-property-match"); m_error_system.disable_warning ("Octave:colon-nonscalar-argument"); m_error_system.disable_warning ("Octave:data-file-in-path"); m_error_system.disable_warning ("Octave:empty-index"); m_error_system.disable_warning ("Octave:function-name-clash"); m_error_system.disable_warning ("Octave:possible-matlab-short-circuit-operator"); } void interpreter::execute_pkg_add (const std::string& dir) { try { m_load_path.execute_pkg_add (dir); } catch (const interrupt_exception&) { recover_from_exception (); } catch (const execution_exception& ee) { handle_exception (ee); } } // Execute commands from a file and catch potential exceptions in a consistent // way. This function should be called anywhere we might parse and execute // commands from a file before we have entered the main loop in // toplev.cc. int interpreter::safe_source_file (const std::string& file_name, const std::string& context, bool verbose, bool require_file) { try { source_file (file_name, context, verbose, require_file); } catch (const interrupt_exception&) { recover_from_exception (); return 1; } catch (const execution_exception& ee) { handle_exception (ee); return 1; } return 0; } OCTAVE_END_NAMESPACE(octave)