changeset 32894:9104b087dc26

maint: merge stable to default
author John W. Eaton <jwe@octave.org>
date Thu, 01 Feb 2024 00:34:24 -0500
parents 837450b0ee50 (current diff) 2faec354b977 (diff)
children dc3ff08bfd3f 212bb363f1fa
files libinterp/octave-value/cdef-class.h libinterp/octave-value/cdef-object.h libinterp/parse-tree/lex.h libinterp/parse-tree/oct-parse.yy libinterp/parse-tree/pt-eval.cc
diffstat 11 files changed, 358 insertions(+), 80 deletions(-) [+]
line wrap: on
line diff
--- a/libinterp/corefcn/help.cc	Wed Jan 31 00:26:04 2024 -0500
+++ b/libinterp/corefcn/help.cc	Thu Feb 01 00:34:24 2024 -0500
@@ -232,24 +232,15 @@
   return h;
 }
 
-std::string
-help_system::which (const std::string& name,
-                    std::string& type) const
+bool
+help_system::get_which_info_from_fcn (const std::string& name, const octave_value& ov_fcn, std::string& file, std::string& type) const
 {
-  std::string file;
-
-  if (name.empty ())
-    return file;
-
+  file = "";
   type = "";
 
-  symbol_table& symtab = m_interpreter.get_symbol_table ();
-
-  octave_value val = symtab.find_function (name);
-
-  if (val.is_defined ())
+  if (ov_fcn.is_function ())
     {
-      octave_function *fcn = val.function_value ();
+      octave_function *fcn = ov_fcn.function_value ();
 
       if (fcn)
         {
@@ -269,11 +260,10 @@
             }
           else
             {
-
               file = fcn->fcn_file_name ();
 
               if (! file.empty ())
-                type = val.is_user_script () ? "script" : "function";
+                type = ov_fcn.is_user_script () ? "script" : "function";
               else
                 {
                   if (fcn->is_user_function ())
@@ -285,6 +275,8 @@
                     }
                 }
             }
+
+          return true;
         }
       else
         {
@@ -293,24 +285,147 @@
           load_path& lp = m_interpreter.get_load_path ();
 
           file = lp.find_fcn_file (name);
+
+          if (file.empty ())
+            return false;
         }
     }
 
-  if (file.empty ())
+  return false;
+}
+
+// FIXME: There is a lot of duplication between the following function
+// and help_system::raw_help_from_symbol_table.  Some refactoring would
+// probably be useful.
+
+std::string
+help_system::which (const std::string& name,
+                    std::string& type) const
+{
+  std::string file;
+
+  if (name.empty ())
+    return file;
+
+  type = "";
+
+  symbol_table& symtab = m_interpreter.get_symbol_table ();
+
+  size_t pos = name.find ('.');
+
+  if (pos == std::string::npos)
     {
-      // File query.
+      // Simple name.  If not found, continue looking for packages and
+      // classes.
+
+      octave_value ov_fcn = symtab.find_function (name);
+
+      if (get_which_info_from_fcn (name, ov_fcn, file, type))
+        return file;
+    }
+
+  // NAME contains '.' and must match the following pattern:
+  //
+  //   (package.)*(package|classname).(property|method)*
+  //
+  // Start by looking up the full name.  It could be either a package or
+  // a class and we are done.  Otherwise, strip the final component and
+  // lookup that name.  If it is a package, look for a function.  If it
+  // is a class, look for a property or method.
+
+  cdef_manager& cdm = m_interpreter.get_cdef_manager ();
 
-      load_path& lp = m_interpreter.get_load_path ();
+  // FIXME: In the following search we may load classes.  Is that really
+  // what we want, or should we just search the loadpath for
+  // +pkga/+pkgb/classname/file.m, etc. and attempt to extract help text
+  // without actually installing packages and classes into the fcn_info
+  // table?
+
+  // Is NAME a class?
+
+  cdef_class cls = cdm.find_class (name, false, true);
+
+  if (cls.ok ())
+    {
+      // FIXME: Return documentation for the class or the class
+      // constructor?
+
+      file = cls.file_name ();
+      type = "classdef class";
+
+      return file;
+    }
+
+  cdef_package pkg = cdm.find_package (name, false, true);
 
-      // For compatibility: "file." queries "file".
-      if (name.size () > 1 && name[name.size () - 1] == '.')
-        file = lp.find_file (name.substr (0, name.size () - 1));
-      else
-        file = lp.find_file (name);
+  if (pkg.ok ())
+    {
+      // FIXME: How to get the fill name of a package?
+      file = pkg.get_name ();
+      type = "package";
+
+      return file;
+    }
+
+  // Strip final component (might be a property or method name).
+
+  pos = name.rfind ('.');
+  std::string prefix = name.substr (0, pos);
+  std::string nm = name.substr (pos+1);
+
+  // Is PREFIX the name of a class?
+
+  cls = cdm.find_class (prefix, false, true);
+
+  if (cls.ok ())
+    {
+      // FIXME: Should we only find public methods here?
+
+      octave_value ov_meth = cls.get_method (nm);
+
+      if (get_which_info_from_fcn (nm, ov_meth, file, type))
+        return file;
+
+      // FIXME: Should we only find public properties here?
+
+      cdef_property prop = cls.find_property (nm);
 
-      file = sys::env::make_absolute (file);
+      if (prop.ok ())
+        {
+          file = cls.file_name ();
+          type = "class property";
+
+          return file;
+        }
     }
 
+  // Or is PREFIX the name of a package?
+
+  pkg = cdm.find_package (prefix, false, true);
+
+  if (pkg.ok ())
+    {
+      octave_value ov_fcn = pkg.find (nm);
+
+      if (get_which_info_from_fcn (nm, ov_fcn, file, type))
+        return file;
+    }
+
+  // File query.
+
+  load_path& lp = m_interpreter.get_load_path ();
+
+  // For compatibility: "file." queries "file".
+  if (name.size () > 1 && name[name.size () - 1] == '.')
+    file = lp.find_file (name.substr (0, name.size () - 1));
+  else
+    file = lp.find_file (name);
+
+  file = sys::env::make_absolute (file);
+
+  if (! file.empty ())
+    type = "file";
+
   return file;
 }
 
@@ -545,51 +660,168 @@
   return retval;
 }
 
+static bool
+get_help_from_fcn (const std::string& fcn_nm, const octave_value& ov_fcn, std::string& help, std::string& what, bool& symbol_found)
+{
+  symbol_found = false;
+
+  help = "";
+  what = "";
+
+  if (ov_fcn.is_function ())
+    {
+      octave_function *fcn = ov_fcn.function_value ();
+
+      help = fcn->doc_string (fcn_nm);
+      what = fcn->fcn_file_name ();
+
+      if (what.empty ())
+        what = fcn->is_user_function () ? "command-line function" : "built-in function";
+
+      symbol_found = true;
+    }
+
+  return symbol_found;
+}
+
+// FIXME: There is a lot of duplication between the following function
+// and help_system::which.  Some refactoring would probably be useful.
+
 bool
-help_system::raw_help_from_symbol_table (const std::string& nm,
-    std::string& h, std::string& w,
-    bool& symbol_found) const
+help_system::raw_help_from_symbol_table (const std::string& name, std::string& help, std::string& what, bool& symbol_found) const
 {
-  std::string meth_nm;
-
   symbol_table& symtab = m_interpreter.get_symbol_table ();
 
-  octave_value val = symtab.find_function (nm);
+  size_t pos = name.find ('.');
+
+  if (pos == std::string::npos)
+    {
+      // Simple name.  If not found, continue looking for packages and
+      // classes.
+
+      octave_value ov_fcn = symtab.find_function (name);
+
+      // FIXME: it seems like there is a lot of potential for confusion
+      // because is_function can also return true for
+      // octave_classdef_meta objects.
 
-  if (! val.is_defined ())
-    {
-      std::size_t pos = nm.rfind ('.');
+      if (! ov_fcn.is_classdef_meta ()
+          && get_help_from_fcn (name, ov_fcn, help, what, symbol_found))
+        return true;
+    }
+
+  // If NAME does not contain '.', then it should be a package or a
+  // class name.
+  //
+  // If NAME contains '.' it should match the following pattern:
+  //
+  //   (package.)*(package|classname).(property|method)*
+  //
+  // Start by looking up the full name.  It could be either a package or
+  // a class and we are done.  Otherwise, strip the final component and
+  // lookup that name.  If it is a package, look for a function.  If it
+  // is a class, look for a property or method.
+
+  cdef_manager& cdm = m_interpreter.get_cdef_manager ();
 
-      if (pos != std::string::npos)
+  // FIXME: In the following search we may load classes.  Is that really
+  // what we want, or should we just search the loadpath for
+  // +pkga/+pkgb/classname/file.m, etc. and attempt to extract help text
+  // without actually installing packages and classes into the fcn_info
+  // table?
+
+  // Is NAME a class?
+
+  cdef_class cls = cdm.find_class (name, false, true);
+
+  if (cls.ok ())
+    {
+      // Is the class documented?
+
+      help = cls.doc_string ();
+
+      if (! help.empty ())
         {
-          meth_nm = nm.substr (pos+1);
+          what = "class";
+
+          symbol_found = true;
+          return true;
+        }
+
+      // Look for constructor.
 
-          val = symtab.find_function (nm.substr (0, pos));
+      pos = name.rfind ('.');
+      std::string nm = name.substr (pos+1);
+
+      octave_value ov_meth = cls.get_method (nm);
+
+      if (get_help_from_fcn (nm, ov_meth, help, what, symbol_found))
+        {
+          what = "constructor";
+
+          return true;
         }
     }
 
-  if (val.is_defined ())
+  cdef_package pkg = cdm.find_package (name, false, true);
+
+  if (pkg.ok ())
     {
-      octave_function *fcn = val.function_value ();
+      help = "package " + name;
+      what = "package";
+
+      symbol_found = true;
+      return true;
+    }
+
+  // Strip final component (might be a property or method name).
+
+  pos = name.rfind ('.');
+  std::string prefix = name.substr (0, pos);
+  std::string nm = name.substr (pos+1);
+
+  // Is PREFIX the name of a class?
+
+  cls = cdm.find_class (prefix, false, true);
 
-      if (fcn)
+  if (cls.ok ())
+    {
+      // FIXME: Should we only find public methods here?
+
+      octave_value ov_meth = cls.get_method (nm);
+
+      if (get_help_from_fcn (nm, ov_meth, help, what, symbol_found))
+        return true;
+
+      // FIXME: Should we only find public properties here?
+
+      cdef_property prop = cls.find_property (nm);
+
+      if (prop.ok ())
         {
-          // FCN may actually be a classdef_meta object.
+          // FIXME: is it supposed to be possible to document
+          // properties?
+
+          help = prop.doc_string ();
+          what = "class property";
 
           symbol_found = true;
-
-          h = fcn->doc_string (meth_nm);
-
-          w = fcn->fcn_file_name ();
-
-          if (w.empty ())
-            w = fcn->is_user_function () ? "command-line function"
-                : "built-in function";
-
           return true;
         }
     }
 
+  // Or is PREFIX the name of a package?
+
+  pkg = cdm.find_package (prefix, false, true);
+
+  if (pkg.ok ())
+    {
+      octave_value ov_fcn = pkg.find (nm);
+
+      if (get_help_from_fcn (nm, ov_fcn, help, what, symbol_found))
+        return true;
+    }
+
   return false;
 }
 
--- a/libinterp/corefcn/help.h	Wed Jan 31 00:26:04 2024 -0500
+++ b/libinterp/corefcn/help.h	Thu Feb 01 00:34:24 2024 -0500
@@ -187,6 +187,8 @@
     return old_val;
   }
 
+  bool get_which_info_from_fcn (const std::string& name, const octave_value& ov_fcn, std::string& file, std::string& type) const;
+
   string_vector local_functions () const;
 
   bool raw_help_from_symbol_table (const std::string& nm,
--- a/libinterp/octave-value/cdef-class.cc	Wed Jan 31 00:26:04 2024 -0500
+++ b/libinterp/octave-value/cdef-class.cc	Thu Feb 01 00:34:24 2024 -0500
@@ -1128,6 +1128,8 @@
 
                   cdef_property prop = cdm.make_property (retval, prop_name);
 
+                  prop.doc_string (prop_p->doc_string ());
+
 #if DEBUG_TRACE
                   std::cerr << "property: " << prop_p->ident ()->name ()
                             << std::endl;
--- a/libinterp/octave-value/cdef-class.h	Wed Jan 31 00:26:04 2024 -0500
+++ b/libinterp/octave-value/cdef-class.h	Thu Feb 01 00:34:24 2024 -0500
@@ -158,10 +158,6 @@
 
     bool is_meta_class () const { return m_meta; }
 
-    void doc_string (const std::string& txt) { m_doc_string = txt; }
-
-    std::string doc_string () const { return m_doc_string; }
-
     void file_name (const std::string& nm) { m_file_name = nm; }
 
     std::string file_name () const { return m_file_name; }
@@ -191,8 +187,6 @@
 
     std::string m_directory;
 
-    std::string m_doc_string;
-
     std::string m_file_name;
 
     // The methods defined by this class.
@@ -402,10 +396,6 @@
 
   bool is_meta_class () const { return get_rep ()->is_meta_class (); }
 
-  void doc_string (const std::string& txt) { get_rep ()->doc_string (txt); }
-
-  std::string doc_string () const { return get_rep ()->doc_string (); }
-
   void file_name (const std::string& nm) { get_rep ()->file_name (nm); }
 
   std::string file_name () const { return get_rep ()->file_name (); }
--- a/libinterp/octave-value/cdef-object.h	Wed Jan 31 00:26:04 2024 -0500
+++ b/libinterp/octave-value/cdef-object.h	Thu Feb 01 00:34:24 2024 -0500
@@ -619,6 +619,10 @@
 
   virtual bool is_package () const { return false; }
 
+  void doc_string (const std::string& txt) { m_doc_string = txt; }
+
+  std::string doc_string () const { return m_doc_string; }
+
   virtual octave_value_list
   meta_subsref (const std::string& /* type */,
                 const std::list<octave_value_list>& /* idx */,
@@ -636,6 +640,8 @@
 
 protected:
 
+  std::string m_doc_string;
+
   // Restricted copying!
   cdef_meta_object_rep (const cdef_meta_object_rep& obj)
     : handle_cdef_object (obj)
@@ -667,6 +673,10 @@
 
   bool is_package () const { return get_rep ()->is_package (); }
 
+  void doc_string (const std::string& txt) { get_rep ()->doc_string (txt); }
+
+  std::string doc_string () const { return get_rep ()->doc_string (); }
+
   octave_value_list
   meta_subsref (const std::string& type,
                 const std::list<octave_value_list>& idx, int nargout)
--- a/libinterp/parse-tree/lex.h	Wed Jan 31 00:26:04 2024 -0500
+++ b/libinterp/parse-tree/lex.h	Thu Feb 01 00:34:24 2024 -0500
@@ -302,6 +302,7 @@
       m_string_text (),
       m_current_input_line (),
       m_comment_text (),
+      m_classdef_help_text (),
       m_help_text (),
       m_function_text (),
       m_fcn_file_name (),
@@ -478,6 +479,9 @@
   // The current comment text.
   std::string m_comment_text;
 
+  // The current classdef help text.
+  std::string m_classdef_help_text;
+
   // The current help text.
   std::string m_help_text;
 
--- a/libinterp/parse-tree/lex.ll	Wed Jan 31 00:26:04 2024 -0500
+++ b/libinterp/parse-tree/lex.ll	Thu Feb 01 00:34:24 2024 -0500
@@ -2205,6 +2205,7 @@
     m_string_text = "";
     m_current_input_line = "";
     m_comment_text = "";
+    m_classdef_help_text = "";
     m_help_text = "";
     m_function_text = "";
     m_fcn_file_name = "";
--- a/libinterp/parse-tree/oct-parse.yy	Wed Jan 31 00:26:04 2024 -0500
+++ b/libinterp/parse-tree/oct-parse.yy	Thu Feb 01 00:34:24 2024 -0500
@@ -1652,19 +1652,17 @@
                   }
                 ;
 
-function        : function_beg stash_comment fcn_name
-                  opt_param_list opt_sep function_body function_end
+function        : function_beg stash_comment fcn_name opt_param_list opt_sep stash_comment function_body function_end
                   {
                     OCTAVE_YYUSE ($5);
 
-                    $$ = parser.make_function ($1, nullptr, $3, $4, $6, $7, $2);
-                  }
-                | function_beg stash_comment return_list '=' fcn_name
-                  opt_param_list opt_sep function_body function_end
+                    $$ = parser.make_function ($1, nullptr, $3, $4, $7, $8, $2, $6);
+                  }
+                | function_beg stash_comment return_list '=' fcn_name opt_param_list opt_sep stash_comment function_body function_end
                   {
                     OCTAVE_YYUSE ($4, $7);
 
-                    $$ = parser.make_function ($1, $3, $5, $6, $8, $9, $2);
+                    $$ = parser.make_function ($1, $3, $5, $6, $9, $10, $2, $8);
                   }
                 ;
 
@@ -1834,6 +1832,9 @@
                         YYABORT;
                       }
 
+                    lexer.m_classdef_help_text = lexer.m_help_text;
+                    lexer.m_help_text = "";
+
                     // Create invalid parent scope.
                     lexer.m_symtab_context.push (octave::symbol_scope::anonymous ());
                     lexer.m_parsing_classdef = true;
@@ -1844,19 +1845,25 @@
                   }
                 ;
 
-classdef        : classdef_beg stash_comment attr_list identifier opt_sep superclass_list class_body END
-                  {
-                    OCTAVE_YYUSE ($5);
-
-                    octave::comment_list *lc = $2;
+classdef        : classdef_beg attr_list identifier opt_sep stash_comment superclass_list stash_comment class_body END
+                  {
+                    OCTAVE_YYUSE ($4);
+
+                    octave::comment_list *lc = $5;
                     octave::comment_list *tc = lexer.get_comment ();
 
+                    if (lexer.m_classdef_help_text.empty () && $7 && ! $7->empty ())
+                      {
+                        const octave::comment_elt& elt = $7->front ();
+                        lexer.m_classdef_help_text = elt.text ();
+                      }
+
                     lexer.m_parsing_classdef = false;
 
-                    if (! ($$ = parser.make_classdef ($1, $3, $4, $6, $7, $8,
+                    if (! ($$ = parser.make_classdef ($1, $2, $3, $6, $8, $9,
                                                       lc, tc)))
                       {
-                        // make_classdef deleted $3, $4, $6, $7, LC, and
+                        // make_classdef deleted $2, $3, $6, $8, LC, and
                         // TC.
                         YYABORT;
                       }
@@ -2010,6 +2017,8 @@
 
 properties_beg  : PROPERTIES
                   {
+                    lexer.m_help_text = "";
+
                     lexer.m_classdef_element_names_are_keywords = false;
                     $$ = $1;
                   }
@@ -2086,6 +2095,8 @@
 
 methods_beg     : METHODS
                   {
+                    lexer.m_help_text = "";
+
                     lexer.m_classdef_element_names_are_keywords = false;
                     $$ = $1;
                   }
@@ -2170,6 +2181,8 @@
 
 events_beg      : EVENTS
                   {
+                    lexer.m_help_text = "";
+
                     lexer.m_classdef_element_names_are_keywords = false;
                     $$ = $1;
                   }
@@ -2222,6 +2235,8 @@
 
 enumeration_beg : ENUMERATION
                   {
+                    lexer.m_help_text = "";
+
                     lexer.m_classdef_element_names_are_keywords = false;
                     $$ = $1;
                   }
@@ -3948,8 +3963,19 @@
                               tree_parameter_list *param_list,
                               tree_statement_list *body,
                               tree_statement *end_fcn_stmt,
-                              comment_list *lc)
-  {
+                              comment_list *lc, comment_list *bc)
+  {
+    // If we are looking at a classdef method and there is a comment
+    // prior to the function keyword and another after, choose the one
+    // inside the function definition for compatibility with Matlab.
+
+    if (m_lexer.m_parsing_classdef && ! m_lexer.m_help_text.empty () && bc && ! bc->empty ())
+      {
+        const octave::comment_elt& elt = bc->front ();
+        m_lexer.m_help_text = elt.text ();
+      }
+
+
     int l = fcn_tok->line ();
     int c = fcn_tok->column ();
 
@@ -4378,8 +4404,11 @@
               body = new tree_classdef_body ();
 
             retval = new tree_classdef (m_lexer.m_symtab_context.curr_scope (),
+                                        m_lexer.m_classdef_help_text,
                                         a, id, sc, body, lc, tc,
                                         m_curr_package_name, full_name, l, c);
+
+            m_lexer.m_classdef_help_text = "";
           }
         else
           {
--- a/libinterp/parse-tree/parse.h	Wed Jan 31 00:26:04 2024 -0500
+++ b/libinterp/parse-tree/parse.h	Thu Feb 01 00:34:24 2024 -0500
@@ -389,7 +389,7 @@
   make_function (token *fcn_tok, tree_parameter_list *ret_list,
                  tree_identifier *id, tree_parameter_list *param_list,
                  tree_statement_list *body, tree_statement *end_fcn_stmt,
-                 comment_list *lc);
+                 comment_list *lc, comment_list *bc);
 
   // Begin defining a function.
   OCTINTERP_API octave_user_function *
--- a/libinterp/parse-tree/pt-classdef.h	Wed Jan 31 00:26:04 2024 -0500
+++ b/libinterp/parse-tree/pt-classdef.h	Thu Feb 01 00:34:24 2024 -0500
@@ -679,13 +679,14 @@
 {
 public:
 
-  tree_classdef (const symbol_scope& scope,
+  tree_classdef (const symbol_scope& scope, const std::string& help_text,
                  tree_classdef_attribute_list *a, tree_identifier *i,
                  tree_classdef_superclass_list *sc,
                  tree_classdef_body *b, comment_list *lc,
                  comment_list *tc, const std::string& pn = "",
                  const std::string& fn = "", int l = -1, int c = -1)
-    : tree_command (l, c), m_scope (scope), m_attr_list (a), m_id (i),
+    : tree_command (l, c), m_scope (scope), m_help_text (help_text),
+      m_attr_list (a), m_id (i),
       m_supclass_list (sc), m_element_list (b), m_lead_comm (lc),
       m_trail_comm (tc), m_pack_name (pn), m_file_name (fn)
   { }
@@ -726,7 +727,7 @@
 
   std::string doc_string () const
   {
-    return m_element_list ? m_element_list->doc_string () : "";
+    return m_help_text;
   }
 
   void accept (tree_walker& tw)
@@ -742,6 +743,8 @@
 
   symbol_scope m_scope;
 
+  std::string m_help_text;
+
   tree_classdef_attribute_list *m_attr_list;
 
   tree_identifier *m_id;
--- a/libinterp/parse-tree/pt-eval.cc	Wed Jan 31 00:26:04 2024 -0500
+++ b/libinterp/parse-tree/pt-eval.cc	Thu Feb 01 00:34:24 2024 -0500
@@ -3688,6 +3688,11 @@
               m_call_stack.set_location (stmt->line (), stmt->column ());
 
               retval = expr->evaluate_n (*this, nargout);
+
+              // Don't allow a comma-separated list to escape (see bug #64783).
+
+              if (nargout <= 1 && retval.length () == 1 && retval(0).is_cs_list ())
+                retval = retval(0).list_value ();
             }
         }
       else