changeset 21707:4c080cbc4ef9

Make source() not re-parse a file if it is already loaded (bug #33411). * oct-parse.cc (source_file): Check if file name is already in the symbol table or in the path. If so, and it is for the same full path, skip parsing. Throw an error if the file is not a valid script or function * oct-parse.cc (Fsource): Document the second "context" parameter.
author Lachlan Andrew <lachlanbis@gmail.com>
date Sat, 16 Apr 2016 00:02:57 +1000
parents cce4eb3f6f7c
children e316b1548d2d
files libinterp/parse-tree/oct-parse.in.yy
diffstat 1 files changed, 76 insertions(+), 26 deletions(-) [+]
line wrap: on
line diff
--- a/libinterp/parse-tree/oct-parse.in.yy	Tue Mar 29 21:27:59 2016 +1100
+++ b/libinterp/parse-tree/oct-parse.in.yy	Sat Apr 16 00:02:57 2016 +1000
@@ -4595,35 +4595,79 @@
     }
 
   octave_function *fcn = 0;
-
-  try
+  // Don't delete a function already in symbol_table
+  bool delete_fcn = false;
+
+  // Find symbol name that would be in symbol_table, if it were loaded.
+  size_t dir_end = file_name.find_last_of (file_ops::dir_sep_chars ());
+  dir_end = (dir_end == std::string::npos) ? 0 : dir_end + 1;
+
+  size_t extension = file_name.find_last_of ('.');
+  if (extension == std::string::npos)
+    extension = file_name.length ();
+
+  std::string symbol = file_name.substr (dir_end, extension - dir_end);
+  std::string full_name = octave_canonicalize_file_name (file_name);
+
+  // Check if this file is already loaded (or in the path)
+  octave_value loaded_sym = symbol_table::find (symbol);
+  if (loaded_sym.is_function ())
     {
-      fcn = parse_fcn_file (file_full_name, file_name, "", "",
-                            require_file, true, false, false, warn_for);
+      fcn = loaded_sym.function_value ();
+      if (fcn)
+        {
+          if (octave_canonicalize_file_name (fcn->fcn_file_name ())
+              == full_name)
+            delete_fcn = true;
+          else
+            fcn = 0;             // wrong file, so load it below
+        }
     }
-  catch (octave_execution_exception& e)
-    {
-      error (e, "source: error sourcing file '%s'", file_full_name.c_str ());
-    }
-
-  if (fcn && fcn->is_user_script ())
+
+  // If no symbol of this name, or the symbol is for a different file, load
+  if (! fcn)
     {
-      octave_value_list args;
-
-      if (verbose)
+      try
         {
-          std::cout << "executing commands from " << file_full_name << " ... ";
-          reading_startup_message_printed = true;
-          std::cout.flush ();
+          fcn = parse_fcn_file (file_full_name, file_name, "", "",
+                                require_file, true, false, false, warn_for);
+        }
+      catch (octave_execution_exception& e)
+        {
+          error (e, "source: error sourcing file '%s'",
+                 file_full_name.c_str ());
         }
-
-      fcn->do_multi_index_op (0, args);
-
-      if (verbose)
-        std::cout << "done." << std::endl;
-
-      delete fcn;
+    }
+
+  // Return or error if we don't have a valid script
+  if (! fcn)
+    return;
+
+  if (! fcn->is_user_code ())
+    {
+      if (delete_fcn)
+        delete fcn;
+      error ("source: %s is not a script", full_name.c_str ());
     }
+
+  // Parameter checking is over.  Now run.
+  octave_value_list args;
+
+  if (verbose)
+    {
+      std::cout << "executing commands from " << full_name << " ... ";
+      reading_startup_message_printed = true;
+      std::cout.flush ();
+    }
+
+  fcn->do_multi_index_op (0, args);
+
+  if (verbose)
+    std::cout << "done." << std::endl;
+
+  // Delete scripts not on the path, so they don't shadow ones that are.
+  if (delete_fcn)
+    delete fcn;
 }
 
 DEFUN (mfilename, args, ,
@@ -4689,11 +4733,17 @@
 
 DEFUN (source, args, ,
   "-*- texinfo -*-\n\
-@deftypefn {} {} source (@var{file})\n\
+@deftypefn  {} {} source (@var{file})\n\
+@deftypefnx {} {} source (@var{file}, @var{context})\n\
 Parse and execute the contents of @var{file}.\n\
 \n\
-This is equivalent to executing commands from a script file, but without\n\
-requiring the file to be named @file{@var{file}.m}.\n\
+Without specifying @var{context}, this is equivalent to executing commands\n\
+from a script file, but without requiring the file to be named\n\
+@file{@var{file}.m} or to be on the execution path.\n\
+\n\
+Instead of the current context, the script may be executed in either the\n\
+context of the function that called the present function\n\
+(@qcode{\"caller\"}), or the top-level context (@qcode{\"base\"}).\n\
 @seealso{run}\n\
 @end deftypefn")
 {