changeset 33237:5565b3e6eb60 bytecode-interpreter

maint: Merge default to bytecode-interpreter.
author Markus Mützel <markus.muetzel@gmx.de>
date Fri, 22 Mar 2024 19:45:02 +0100
parents 3305b504bc2b (current diff) c0ee9e8e3117 (diff)
children 39b6d6ca3831
files .github/workflows/make.yaml libinterp/octave-value/ov-usr-fcn.cc libinterp/octave-value/ov-usr-fcn.h test/Makefile.am
diffstat 17 files changed, 653 insertions(+), 342 deletions(-) [+]
line wrap: on
line diff
--- a/.github/workflows/make.yaml	Wed Mar 20 08:13:12 2024 +0100
+++ b/.github/workflows/make.yaml	Fri Mar 22 19:45:02 2024 +0100
@@ -480,11 +480,9 @@
           - compiler: clang
             cc: "clang"
             cxx: "clang++"
-            # Qt6 doesn't install pkg-config files on macOS. See: https://bugreports.qt.io/browse/QTBUG-86080
-            # So, we need to provide the necessary flags manually.
-            qt-cppflags: "-I${HOMEBREW_PREFIX}/opt/qt@6/include/QtCore -I${HOMEBREW_PREFIX}/opt/qt@6/include/QtGui -I${HOMEBREW_PREFIX}/opt/qt@6/include/QtHelp -I${HOMEBREW_PREFIX}/opt/qt@6/include/QtNetwork -I${HOMEBREW_PREFIX}/opt/qt@6/include/QtOpenGL -I${HOMEBREW_PREFIX}/opt/qt@6/include/QtOpenGLWidgets -I${HOMEBREW_PREFIX}/opt/qt@6/include/QtPrintSupport -I${HOMEBREW_PREFIX}/opt/qt@6/include/QtSql -I${HOMEBREW_PREFIX}/opt/qt@6/include/QtWidgets -I${HOMEBREW_PREFIX}/opt/qt@6/include/QtXml -I${HOMEBREW_PREFIX}/opt/qt@6/include/QtCore5Compat"
-            qt-ldflags: "-F${HOMEBREW_PREFIX}/opt/qt@6/Frameworks"
-            qt-libs: "-framework QtCore -framework QtGui -framework QtHelp -framework QtNetwork -framework QtOpenGL -framework QtOpenGLWidgets -framework QtPrintSupport -framework QtSql -framework QtWidgets -framework QtXml -framework QtCore5Compat"
+            # Qt6 doesn't install pkg-config files on macOS by default. See: https://bugreports.qt.io/browse/QTBUG-86080
+            # Homebrew installs "unofficial" .pc files for it in a non-default location.
+            qt-pkg-config-path: opt/qt@6/libexec/lib/pkgconfig
 
     env:
       CC: ${{ matrix.cc }}
@@ -595,12 +593,9 @@
             CPPFLAGS="-I${HOMEBREW_PREFIX}/opt/gettext/include -I${HOMEBREW_PREFIX}/opt/icu4c/include -I${HOMEBREW_PREFIX}/opt/qt@${{ matrix.qt }}/include -I${HOMEBREW_PREFIX}/opt/readline/include -I${HOMEBREW_PREFIX}/opt/sqlite/include $MY_CPPFLAGS -I${HOMEBREW_PREFIX}/include" \
             CXXFLAGS="-O2 -g" \
             LDFLAGS="-L${HOMEBREW_PREFIX}/opt/bison/lib -L${HOMEBREW_PREFIX}/opt/gettext/lib -L${HOMEBREW_PREFIX}/opt/icu4c/lib -L${HOMEBREW_PREFIX}/opt/readline/lib -L${HOMEBREW_PREFIX}/opt/sqlite/lib $MY_LDFLAGS -L${HOMEBREW_PREFIX}/lib" \
-            PKG_CONFIG_PATH="${HOMEBREW_PREFIX}/opt/openblas/lib/pkgconfig:${HOMEBREW_PREFIX}/opt/icu4c/lib/pkgconfig:${HOMEBREW_PREFIX}/opt/qt@${{ matrix.qt }}/lib/pkgconfig" \
+            PKG_CONFIG_PATH="${HOMEBREW_PREFIX}/opt/openblas/lib/pkgconfig:${HOMEBREW_PREFIX}/opt/icu4c/lib/pkgconfig:${HOMEBREW_PREFIX}/${{ matrix.qt-pkg-config-path }}" \
             --with-qt=${{ matrix.qt }} \
             QCOLLECTIONGENERATOR="qhelpgenerator" \
-            QT_CPPFLAGS="${{ matrix.qt-cppflags }}" \
-            QT_LDFLAGS="${{ matrix.qt-ldflags }}" \
-            QT_LIBS="${{ matrix.qt-libs }}" \
             --with-x=no \
             --with-blas="-L${HOMEBREW_PREFIX}/opt/openblas/lib -lopenblas" \
             --with-java-homedir="${HOMEBREW_PREFIX}/opt/openjdk" \
--- a/libinterp/corefcn/help.cc	Wed Mar 20 08:13:12 2024 +0100
+++ b/libinterp/corefcn/help.cc	Fri Mar 22 19:45:02 2024 +0100
@@ -675,6 +675,20 @@
       help = fcn->doc_string (fcn_nm);
       what = fcn->fcn_file_name ();
 
+      if (help.empty () && ov_fcn.is_user_function ())
+        {
+          octave_user_function *ufcn = ov_fcn.user_function_value ();
+
+          if (ufcn->is_classdef_constructor ())
+            help = "undocumented constructor: ";
+          else if (ufcn->is_classdef_method ())
+            help = "undocumented method: ";
+          else
+            help = "undocumented function: ";
+
+          help += ufcn->signature ();
+        }
+
       if (what.empty ())
         what = fcn->is_user_function () ? "command-line function" : "built-in function";
 
@@ -719,10 +733,23 @@
             }
         }
 
-      // We found a class, but no docstring for it or its constructor.
-      // Create a generic doc string.
-      help = name + " is an undocumented class";
-      what = "class";
+      // No dot in name and class is undocumented.  Look for documented
+      // constructor.
+
+      octave_value ov_meth = cls.get_method (name);
+
+      if (get_help_from_fcn (name, ov_meth, help, what, symbol_found))
+        {
+          what = "constructor";
+          symbol_found = true;
+          return true;
+        }
+
+      // We found a class, but no docstring for it and there is no
+      // constructor explicitly defined.
+
+      help = "default constructor: obj = " + name + " ()";
+      what = "constructor";
       symbol_found = true;
       return true;
     }
@@ -813,7 +840,21 @@
       octave_value ov_meth = cls.get_method (nm);
 
       if (get_help_from_fcn (nm, ov_meth, help, what, symbol_found))
-        return true;
+        {
+          what = "class method";
+          return true;
+        }
+
+      // Found class but no method.  If the NM is the same as the name
+      // of the class, then we have a default constructor.
+
+      if (cls.get_name () == nm)
+        {
+          help = "default constructor: obj = " + nm + " ()";
+          what = "constructor";
+          symbol_found = true;
+          return true;
+        }
 
       // FIXME: Should we only find public properties here?
 
--- a/libinterp/corefcn/syscalls.cc	Wed Mar 20 08:13:12 2024 +0100
+++ b/libinterp/corefcn/syscalls.cc	Fri Mar 22 19:45:02 2024 +0100
@@ -124,25 +124,34 @@
   if (args.length () != 2)
     print_usage ();
 
-  stream_list& streams = interp.get_stream_list ();
-
-  stream old_stream = streams.lookup (args(0), "dup2");
+  int i_old, i_new;
+  try
+    {
+      // Look up FID in Octave's list of open streams.
+      stream_list& streams = interp.get_stream_list ();
 
-  stream new_stream = streams.lookup (args(1), "dup2");
-
-  int i_old = old_stream.file_number ();
-  int i_new = new_stream.file_number ();
+      stream old_stream = streams.lookup (args(0), "dup2");
+      stream new_stream = streams.lookup (args(1), "dup2");
 
-  if (i_old >= 0 && i_new >= 0)
+      i_old = old_stream.file_number ();
+      i_new = new_stream.file_number ();
+    }
+  catch (execution_exception& ee)
     {
-      std::string msg;
+      // If the FIDs are not known to Octave, try the provided FIDs directly.
+      i_old = args(0).int_value (true);
+      i_new = args(1).int_value (true);
+    }
 
-      int status = sys::dup2 (i_old, i_new, msg);
+  if (i_old < 0 || i_new < 0)
+    // Bad FIDs, return error immediately
+    return ovl (-1, "");
 
-      return ovl (status, msg);
-    }
-  else
-    return ovl (-1, "");
+  std::string msg;
+
+  int status = sys::dup2 (i_old, i_new, msg);
+
+  return ovl (status, msg);
 }
 
 DEFMETHODX ("exec", Fexec, interp, args, ,
@@ -439,21 +448,28 @@
   if (args.length () != 3)
     print_usage ();
 
-  stream_list& streams = interp.get_stream_list ();
+  int fid;
+  try
+    {
+      // Look up FID in Octave's list of open streams.
+      stream_list& streams = interp.get_stream_list ();
+      stream strm = streams.lookup (args(0), "fcntl");
+      fid = strm.file_number ();
+    }
+  catch (execution_exception& ee)
+    {
+      // If the file is not known to Octave, try the provided file ID directly.
+      fid = args(0).int_value (true);
+    }
 
-  stream strm = streams.lookup (args(0), "fcntl");
-
-  int fid = strm.file_number ();
+  if (fid < 0)
+    error ("fcntl: invalid file id FID");
 
   // FIXME: Do we want to use xint_value and throw a warning message
   //        if input validation fails?
   int req = args(1).int_value (true);
   int arg = args(2).int_value (true);
 
-  // FIXME: Need better checking here?
-  if (fid < 0)
-    error ("fcntl: invalid file id");
-
   octave_value_list retval;
   std::string msg;
 
@@ -908,9 +924,21 @@
 
   if (args(0).is_scalar_type ())
     {
-      stream_list& streams = interp.get_stream_list ();
+      int fid;
+      try
+        {
+          // Look up FID in Octave's list of open streams.
+          stream_list& streams = interp.get_stream_list ();
+          fid = streams.get_file_number (args(0));
+        }
+      catch (execution_exception& ee)
+        {
+          // If the file is not known to Octave, try provided file ID directly.
+          fid = args(0).int_value (true);
+        }
 
-      int fid = streams.get_file_number (args(0));
+      if (fid < 0)
+        error ("stat: invalid file id FID");
 
       sys::file_fstat fs (fid);
 
--- a/libinterp/octave-value/ov-usr-fcn.cc	Wed Mar 20 08:13:12 2024 +0100
+++ b/libinterp/octave-value/ov-usr-fcn.cc	Fri Mar 22 19:45:02 2024 +0100
@@ -283,6 +283,29 @@
   delete m_trail_comm;
 }
 
+std::string
+octave_user_function::signature () const
+{
+  std::ostringstream buf;
+
+  octave::tree_print_code tpc (buf);
+
+  if (m_ret_list)
+    {
+      m_ret_list->accept (tpc);
+      buf << " = ";
+    }
+
+  buf << m_name << " ";
+
+  if (m_param_list)
+    m_param_list->accept (tpc);
+  else
+    buf << " ()";
+
+  return buf.str ();
+}
+
 octave_user_function *
 octave_user_function::define_ret_list (octave::tree_parameter_list *t)
 {
--- a/libinterp/octave-value/ov-usr-fcn.h	Wed Mar 20 08:13:12 2024 +0100
+++ b/libinterp/octave-value/ov-usr-fcn.h	Fri Mar 22 19:45:02 2024 +0100
@@ -235,6 +235,9 @@
 
   ~octave_user_function ();
 
+  // Declared calling form, generated from the parse tree.
+  std::string signature () const;
+
   octave_function * function_value (bool = false) { return this; }
 
   octave_user_function * user_function_value (bool = false) { return this; }
--- a/test/Makefile.am	Wed Mar 20 08:13:12 2024 +0100
+++ b/test/Makefile.am	Fri Mar 22 19:45:02 2024 +0100
@@ -114,7 +114,6 @@
 include bug-61191/module.mk
 include bug-63841/module.mk
 include bug-65037/module.mk
-include bug-65220/module.mk
 include class-concat/module.mk
 include classdef/module.mk
 include classdef-debug/module.mk
@@ -126,6 +125,7 @@
 include ctor-vs-method/module.mk
 include fcn-handle/module.mk
 include file-encoding/module.mk
+include help/module.mk
 include json/module.mk
 include jupyter-notebook/module.mk
 include load-path/module.mk
--- a/test/bug-65220/bug-65220.tst	Wed Mar 20 08:13:12 2024 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,110 +0,0 @@
-########################################################################
-##
-## Copyright (C) 2024 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/>.
-##
-########################################################################
-
-%!test <*65220>
-%! unwind_protect
-%!   addpath ('cdefdir');
-%!
-%!   ## Check methods first (bug was disguised if class checked first)
-%!   s = help ('cdef_help1.meth1');
-%!   assert (regexp (s, 'meth1: method help text ABOVE function'));
-%!
-%!   s = help ('cdef_help1.meth2');
-%!   assert (regexp (s, 'meth2: method help text BELOW function'));
-%!
-%!   s = help ('cdef_help1.meth3');
-%!   assert (regexp (s, 'meth3: method help text BELOW function'));
-%!
-%!   s = '';
-%!   try
-%!     s = help ('cdef_help1.meth4');
-%!   catch
-%!     assert (regexp (lasterr (), "'cdef_help1.meth4' is not documented"));
-%!   end_try_catch
-%!   if (! isempty (s))
-%!     error ("Impossible state: Help text found for 'cdef_help1.meth4'");
-%!   endif
-%!
-%!   ## Check documentation for entire class
-%!   s = help ('cdef_help1');
-%!   assert (regexp (s, 'class cdef_help1 : class help text ABOVE classdef'));
-%!
-%!   ## Check documentation for constructor
-%!   s = help ('cdef_help1.cdef_help1');
-%!   assert (regexp (s, 'cdef_help1: constructor help text BELOW function'));
-%!
-%!   ## Check documentation for properties
-%!   s = help ('cdef_help1.prop1');
-%!   assert (regexp (s, 'prop1: property help text ABOVE property'));
-%!
-%!   s = help ('cdef_help1.prop2');
-%!   assert (regexp (s, 'prop2: property help text in EOL-comment'));
-%!
-%! unwind_protect_cleanup
-%!   rmpath ('cdefdir');
-%! end_unwind_protect
-
-%!test <*65220>
-%! unwind_protect
-%!   addpath ('cdefdir');
-%!
-%!   ## Check methods first (bug was disguised if class checked first)
-%!   s = help ('cdef_help2.meth1');
-%!   assert (regexp (s, 'meth1: method help text ABOVE function'));
-%!
-%!   s = help ('cdef_help2.meth2');
-%!   assert (regexp (s, 'meth2: method help text BELOW function'));
-%!
-%!   s = help ('cdef_help2.meth3');
-%!   assert (regexp (s, 'meth3: method help text ABOVE function'));
-%!
-%!   s = '';
-%!   try
-%!     s = help ('cdef_help2.meth4');
-%!   catch
-%!     assert (regexp (lasterr (), "'cdef_help2.meth4' is not documented"));
-%!   end_try_catch
-%!   if (! isempty (s))
-%!     error ("Impossible state: Help text found for 'cdef_help2.meth4'");
-%!   endif
-%!
-%!   ## Check documentation for entire class
-%!   s = help ('cdef_help2');
-%!   assert (regexp (s, 'class cdef_help2 : class help text BELOW classdef'));
-%!
-%!   ## Check documentation for constructor
-%!   s = help ('cdef_help2.cdef_help2');
-%!   assert (regexp (s, 'cdef_help2: constructor help text ABOVE function'));
-%!
-%!   ## Check documentation for properties
-%!   s = help ('cdef_help2.prop1');
-%!   assert (regexp (s, 'prop1: property help text ABOVE property'));
-%!
-%!   s = help ('cdef_help2.prop2');
-%!   assert (regexp (s, 'prop2: property help text in EOL-comment'));
-%!
-%! unwind_protect_cleanup
-%!   rmpath ('cdefdir');
-%! end_unwind_protect
--- a/test/bug-65220/cdefdir/cdef_help1.m	Wed Mar 20 08:13:12 2024 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,95 +0,0 @@
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%%
-%% Copyright (C) 2024 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/>.
-%%
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-% class cdef_help1 : class help text ABOVE classdef keyword.
-%
-% Type 'help cdef_help1'
-
-classdef cdef_help1
-  % -*- texinfo -*-
-  % class cdef_help1 : class help text BELOW classdef keyword.
-  %
-  % Type 'help cdef_help1'
-
-  properties
-    % prop1: property help text ABOVE property name.
-    % Type "help cdef_help1.prop1"
-    prop1   % prop1: EOL-comment text.  Should not be displayed
-    prop2   % prop2: property help text in EOL-comment for property PROP2.  Type "help cdef_help1.prop2"
-  end
-
-  methods
-
-    % cdef_help1: constructor help text ABOVE function keyword
-    % Type 'help cdef_help1.cdef_help1'.
-    function obj = cdef_help1 (p1, p2)
-      % cdef_help1: constructor help text BELOW function keyword
-      % Type 'help cdef_help1.cdef_help1'.
-      if (nargin ~= 2)
-        obj.prop1 = 'default';
-        obj.prop2 = 42;
-      else
-        obj.prop1 = p1;
-        obj.prop2 = p2;
-      end
-    end
-
-    % meth1: method help text ABOVE function
-    %
-    % Type 'help cdef_help1.meth1'.
-    function obj2 = meth1 (obj, n)
-      obj2 = n + obj;
-    end
-
-    function obj2 = meth2 (obj, n)
-
-      % meth2: method help text BELOW function
-      % The blank line between function and comment is intentional.
-      % Type 'help cdef_help1.meth2'.
-      obj2 = n - obj;
-    end
-
-    % meth3: method help text ABOVE function
-    % Type 'help cdef_help1.meth3'.
-    % This should not be shown.
-    function obj3 = meth3 (obj, n)
-      % meth3: method help text BELOW function
-      % Type 'help cdef_help1.meth3'.
-      % This should be displayed.
-      obj3 = obj + n;
-    end
-
-    function obj4 = meth4 (obj, n)
-
-      obj4 = n - obj;
-      if (n)
-        % meth4: pure comment text.  This should *never* be displayed.
-        % Type 'help cdef_help1.meth4'.
-      end
-    end
-
-  end
-
-end
--- a/test/bug-65220/cdefdir/cdef_help2.m	Wed Mar 20 08:13:12 2024 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,92 +0,0 @@
-########################################################################
-##
-## Copyright (C) 2024 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/>.
-##
-########################################################################
-
-classdef cdef_help2
-  # -*- texinfo -*-
-  # class cdef_help2 : class help text BELOW classdef keyword.
-  #
-  # Type 'help cdef_help2'
-
-  properties
-    # prop1: property help text ABOVE property name.
-    # Type "help cdef_help2.prop1"
-    prop1   # prop1: EOL-comment text.  Should not be displayed
-    prop2   # prop2: property help text in EOL-comment for property PROP2.  Type "help cdef_help2.prop2"
-  end
-
-  methods
-
-    # cdef_help2: constructor help text ABOVE function keyword
-    # Type 'help cdef_help2.cdef_help2'.
-    # This should be shown because Octave comment character '#' is used.
-    function obj = cdef_help2 (p1, p2)
-      # cdef_help2: constructor help text BELOW function keyword
-      # Type 'help cdef_help2.cdef_help2'.
-      if (nargin ~= 2)
-        obj.prop1 = 'default';
-        obj.prop2 = 42;
-      else
-        obj.prop1 = p1;
-        obj.prop2 = p2;
-      end
-    end
-
-    # meth1: method help text ABOVE function
-    #
-    # Type 'help cdef_help2.meth1'.
-    function obj2 = meth1 (obj, n)
-      obj2 = n + obj;
-    end
-
-    function obj2 = meth2 (obj, n)
-
-      # meth2: method help text BELOW function
-      # The blank line between function and comment is intentional.
-      # Type 'help cdef_help2.meth2'.
-      obj2 = n - obj;
-    end
-
-    # meth3: method help text ABOVE function
-    # Type 'help cdef_help2.meth3'.
-    # This should be shown because Octave comment character '#' is used.
-    function obj3 = meth3 (obj, n)
-      # meth3: method help text BELOW function
-      # Type 'help cdef_help2.meth3'.
-      # This should NOT be displayed.
-      obj3 = obj + n;
-    end
-
-    function obj4 = meth4 (obj, n)
-
-      obj4 = n - obj;
-      if (n)
-        # meth4: pure comment text.  This should *never* be displayed.
-        # Type 'help cdef_help2.meth4'.
-      end
-    end
-
-  end
-
-end
--- a/test/bug-65220/module.mk	Wed Mar 20 08:13:12 2024 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,6 +0,0 @@
-bug_65220_TEST_FILES = \
-  %reldir%/bug-65220.tst \
-  %reldir%/cdefdir/cdef_help1.m \
-  %reldir%/cdefdir/cdef_help2.m
-
-TEST_FILES += $(bug_65220_TEST_FILES)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/help/cdefdir/cdef_help1.m	Fri Mar 22 19:45:02 2024 +0100
@@ -0,0 +1,95 @@
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%
+%% Copyright (C) 2024 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/>.
+%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+% class cdef_help1 : class help text ABOVE classdef keyword.
+%
+% Type 'help cdef_help1'
+
+classdef cdef_help1
+  % -*- texinfo -*-
+  % class cdef_help1 : class help text BELOW classdef keyword.
+  %
+  % Type 'help cdef_help1'
+
+  properties
+    % prop1: property help text ABOVE property name.
+    % Type "help cdef_help1.prop1"
+    prop1   % prop1: EOL-comment text.  Should not be displayed
+    prop2   % prop2: property help text in EOL-comment for property PROP2.  Type "help cdef_help1.prop2"
+  end
+
+  methods
+
+    % cdef_help1: constructor help text ABOVE function keyword
+    % Type 'help cdef_help1.cdef_help1'.
+    function obj = cdef_help1 (p1, p2)
+      % cdef_help1: constructor help text BELOW function keyword
+      % Type 'help cdef_help1.cdef_help1'.
+      if (nargin ~= 2)
+        obj.prop1 = 'default';
+        obj.prop2 = 42;
+      else
+        obj.prop1 = p1;
+        obj.prop2 = p2;
+      end
+    end
+
+    % meth1: method help text ABOVE function
+    %
+    % Type 'help cdef_help1.meth1'.
+    function obj2 = meth1 (obj, n)
+      obj2 = n + obj;
+    end
+
+    function obj2 = meth2 (obj, n)
+
+      % meth2: method help text BELOW function
+      % The blank line between function and comment is intentional.
+      % Type 'help cdef_help1.meth2'.
+      obj2 = n - obj;
+    end
+
+    % meth3: method help text ABOVE function
+    % Type 'help cdef_help1.meth3'.
+    % This should not be shown.
+    function obj3 = meth3 (obj, n)
+      % meth3: method help text BELOW function
+      % Type 'help cdef_help1.meth3'.
+      % This should be displayed.
+      obj3 = obj + n;
+    end
+
+    function obj4 = meth4 (obj, n)
+
+      obj4 = n - obj;
+      if (n)
+        % meth4: pure comment text.  This should *never* be displayed.
+        % Type 'help cdef_help1.meth4'.
+      end
+    end
+
+  end
+
+end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/help/cdefdir/cdef_help2.m	Fri Mar 22 19:45:02 2024 +0100
@@ -0,0 +1,92 @@
+########################################################################
+##
+## Copyright (C) 2024 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/>.
+##
+########################################################################
+
+classdef cdef_help2
+  # -*- texinfo -*-
+  # class cdef_help2 : class help text BELOW classdef keyword.
+  #
+  # Type 'help cdef_help2'
+
+  properties
+    # prop1: property help text ABOVE property name.
+    # Type "help cdef_help2.prop1"
+    prop1   # prop1: EOL-comment text.  Should not be displayed
+    prop2   # prop2: property help text in EOL-comment for property PROP2.  Type "help cdef_help2.prop2"
+  end
+
+  methods
+
+    # cdef_help2: constructor help text ABOVE function keyword
+    # Type 'help cdef_help2.cdef_help2'.
+    # This should be shown because Octave comment character '#' is used.
+    function obj = cdef_help2 (p1, p2)
+      # cdef_help2: constructor help text BELOW function keyword
+      # Type 'help cdef_help2.cdef_help2'.
+      if (nargin ~= 2)
+        obj.prop1 = 'default';
+        obj.prop2 = 42;
+      else
+        obj.prop1 = p1;
+        obj.prop2 = p2;
+      end
+    end
+
+    # meth1: method help text ABOVE function
+    #
+    # Type 'help cdef_help2.meth1'.
+    function obj2 = meth1 (obj, n)
+      obj2 = n + obj;
+    end
+
+    function obj2 = meth2 (obj, n)
+
+      # meth2: method help text BELOW function
+      # The blank line between function and comment is intentional.
+      # Type 'help cdef_help2.meth2'.
+      obj2 = n - obj;
+    end
+
+    # meth3: method help text ABOVE function
+    # Type 'help cdef_help2.meth3'.
+    # This should be shown because Octave comment character '#' is used.
+    function obj3 = meth3 (obj, n)
+      # meth3: method help text BELOW function
+      # Type 'help cdef_help2.meth3'.
+      # This should NOT be displayed.
+      obj3 = obj + n;
+    end
+
+    function obj4 = meth4 (obj, n)
+
+      obj4 = n - obj;
+      if (n)
+        # meth4: pure comment text.  This should *never* be displayed.
+        # Type 'help cdef_help2.meth4'.
+      end
+    end
+
+  end
+
+end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/help/cdefdir/cdef_help3.m	Fri Mar 22 19:45:02 2024 +0100
@@ -0,0 +1,38 @@
+########################################################################
+##
+## Copyright (C) 2024 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/>.
+##
+########################################################################
+
+classdef cdef_help3
+  properties
+    prop1 = 13;
+    prop2 = 42;
+  end
+
+  methods
+    function obj = cdef_help3 (p1, p2)
+      obj.prop1 = p1;
+      obj.prop2 = p2;
+    end
+  end
+end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/help/cdefdir/cdef_help4.m	Fri Mar 22 19:45:02 2024 +0100
@@ -0,0 +1,31 @@
+########################################################################
+##
+## Copyright (C) 2024 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/>.
+##
+########################################################################
+
+classdef cdef_help4
+  properties
+    prop1 = 13;
+    prop2 = 42;
+  end
+end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/help/help.tst	Fri Mar 22 19:45:02 2024 +0100
@@ -0,0 +1,254 @@
+########################################################################
+##
+## Copyright (C) 2024 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/>.
+##
+########################################################################
+
+%!test <*65220>
+%! unwind_protect
+%!   addpath ('cdefdir');
+%!
+%!   ## Check methods first (bug was disguised if class checked first)
+%!   s = help ('cdef_help1.meth1');
+%!   assert (regexp (s, 'meth1: method help text ABOVE function'));
+%!
+%! unwind_protect_cleanup
+%!   rmpath ('cdefdir');
+%! end_unwind_protect
+
+%!test <*65220>
+%! unwind_protect
+%!   addpath ('cdefdir');
+%!
+%!   s = help ('cdef_help1.meth2');
+%!   assert (regexp (s, 'meth2: method help text BELOW function'));
+%!
+%! unwind_protect_cleanup
+%!   rmpath ('cdefdir');
+%! end_unwind_protect
+
+%!test <*65220>
+%! unwind_protect
+%!   addpath ('cdefdir');
+%!
+%!   s = help ('cdef_help1.meth3');
+%!   assert (regexp (s, 'meth3: method help text BELOW function'));
+%!
+%! unwind_protect_cleanup
+%!   rmpath ('cdefdir');
+%! end_unwind_protect
+
+%!test <*65258>
+%! unwind_protect
+%!   addpath ('cdefdir');
+%!
+%!   s = help ('cdef_help1.meth4');
+%!   assert (regexp (s, 'undocumented method: obj4 = meth4 \(obj, n\)'));
+%!
+%! unwind_protect_cleanup
+%!   rmpath ('cdefdir');
+%! end_unwind_protect
+
+%!test <*65220>
+%! unwind_protect
+%!   addpath ('cdefdir');
+%!
+%!   ## Check documentation for entire class
+%!   s = help ('cdef_help1');
+%!   assert (regexp (s, 'class cdef_help1 : class help text ABOVE classdef'));
+%!
+%! unwind_protect_cleanup
+%!   rmpath ('cdefdir');
+%! end_unwind_protect
+
+%!test <*65220>
+%! unwind_protect
+%!   addpath ('cdefdir');
+%!
+%!   ## Check documentation for constructor
+%!   s = help ('cdef_help1.cdef_help1');
+%!   assert (regexp (s, 'cdef_help1: constructor help text BELOW function'));
+%!
+%! unwind_protect_cleanup
+%!   rmpath ('cdefdir');
+%! end_unwind_protect
+
+%!test <*65220>
+%! unwind_protect
+%!   addpath ('cdefdir');
+%!
+%!   ## Check documentation for properties
+%!   s = help ('cdef_help1.prop1');
+%!   assert (regexp (s, 'prop1: property help text ABOVE property'));
+%!
+%! unwind_protect_cleanup
+%!   rmpath ('cdefdir');
+%! end_unwind_protect
+
+%!test <*65220>
+%! unwind_protect
+%!   addpath ('cdefdir');
+%!
+%!   s = help ('cdef_help1.prop2');
+%!   assert (regexp (s, 'prop2: property help text in EOL-comment'));
+%!
+%! unwind_protect_cleanup
+%!   rmpath ('cdefdir');
+%! end_unwind_protect
+
+%!test <*65220>
+%! unwind_protect
+%!   addpath ('cdefdir');
+%!
+%!   ## Check methods first (bug was disguised if class checked first)
+%!   s = help ('cdef_help2.meth1');
+%!   assert (regexp (s, 'meth1: method help text ABOVE function'));
+%! unwind_protect_cleanup
+%!   rmpath ('cdefdir');
+%! end_unwind_protect
+
+%!
+%!test <*65220>
+%! unwind_protect
+%!   addpath ('cdefdir');
+%!
+%!   s = help ('cdef_help2.meth2');
+%!   assert (regexp (s, 'meth2: method help text BELOW function'));
+%!
+%! unwind_protect_cleanup
+%!   rmpath ('cdefdir');
+%! end_unwind_protect
+
+%!test <*65220>
+%! unwind_protect
+%!   addpath ('cdefdir');
+%!
+%!   s = help ('cdef_help2.meth3');
+%!   assert (regexp (s, 'meth3: method help text ABOVE function'));
+%!
+%! unwind_protect_cleanup
+%!   rmpath ('cdefdir');
+%! end_unwind_protect
+
+%!test <*65220>
+%! unwind_protect
+%!   addpath ('cdefdir');
+%!
+%!   s = help ('cdef_help2.meth4');
+%!   assert (regexp (s, 'undocumented method: obj4 = meth4 \(obj, n\)'));
+%!
+%! unwind_protect_cleanup
+%!   rmpath ('cdefdir');
+%! end_unwind_protect
+
+%!test <*65220>
+%! unwind_protect
+%!   addpath ('cdefdir');
+%!
+%!   ## Check documentation for entire class
+%!   s = help ('cdef_help2');
+%!   assert (regexp (s, 'class cdef_help2 : class help text BELOW classdef'));
+%!
+%! unwind_protect_cleanup
+%!   rmpath ('cdefdir');
+%! end_unwind_protect
+
+%!test <*65220>
+%! unwind_protect
+%!   addpath ('cdefdir');
+%!
+%!   ## Check documentation for constructor
+%!   s = help ('cdef_help2.cdef_help2');
+%!   assert (regexp (s, 'cdef_help2: constructor help text ABOVE function'));
+%!
+%! unwind_protect_cleanup
+%!   rmpath ('cdefdir');
+%! end_unwind_protect
+
+%!test <*65220>
+%! unwind_protect
+%!   addpath ('cdefdir');
+%!
+%!   ## Check documentation for properties
+%!   s = help ('cdef_help2.prop1');
+%!   assert (regexp (s, 'prop1: property help text ABOVE property'));
+%!
+%! unwind_protect_cleanup
+%!   rmpath ('cdefdir');
+%! end_unwind_protect
+
+%!test <*65220>
+%! unwind_protect
+%!   addpath ('cdefdir');
+%!
+%!   s = help ('cdef_help2.prop2');
+%!   assert (regexp (s, 'prop2: property help text in EOL-comment'));
+%!
+%! unwind_protect_cleanup
+%!   rmpath ('cdefdir');
+%! end_unwind_protect
+
+%!test <*65258>
+%! unwind_protect
+%!   addpath ('cdefdir');
+%!
+%!   s = help ('cdef_help3');
+%!   assert (regexp (s, 'undocumented constructor: obj = cdef_help3 \(p1, p2\)'));
+%!
+%! unwind_protect_cleanup
+%!   rmpath ('cdefdir');
+%! end_unwind_protect
+
+%!test <*65258>
+%! unwind_protect
+%!   addpath ('cdefdir');
+%!
+%!   s = help ('cdef_help3.cdef_help3');
+%!   assert (regexp (s, 'undocumented constructor: obj = cdef_help3 \(p1, p2\)'));
+%!
+%! unwind_protect_cleanup
+%!   rmpath ('cdefdir');
+%! end_unwind_protect
+
+%!test <*65258>
+%! unwind_protect
+%!   addpath ('cdefdir');
+%!
+%!   s = help ('cdef_help4');
+%!   assert (regexp (s, 'default constructor: obj = cdef_help4 \(\)'));
+%!
+%! unwind_protect_cleanup
+%!   rmpath ('cdefdir');
+%! end_unwind_protect
+
+%!test <*65258>
+%! unwind_protect
+%!   addpath ('cdefdir');
+%!
+%!   s = help ('cdef_help4.cdef_help4');
+%!   assert (regexp (s, 'default constructor: obj = cdef_help4 \(\)'));
+%!
+%! unwind_protect_cleanup
+%!   rmpath ('cdefdir');
+%! end_unwind_protect
+
+%!assert <*65258> (regexp (help ('undoc_fcn'), 'undocumented function: \[x, y, z\] = undoc_fcn \(a, b, ~, c = 3\)'))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/help/module.mk	Fri Mar 22 19:45:02 2024 +0100
@@ -0,0 +1,9 @@
+help_TEST_FILES = \
+  %reldir%/help.tst \
+  %reldir%/undoc_fcn.m \
+  %reldir%/cdefdir/cdef_help1.m \
+  %reldir%/cdefdir/cdef_help2.m \
+  %reldir%/cdefdir/cdef_help3.m \
+  %reldir%/cdefdir/cdef_help4.m
+
+TEST_FILES += $(help_TEST_FILES)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/help/undoc_fcn.m	Fri Mar 22 19:45:02 2024 +0100
@@ -0,0 +1,5 @@
+function [x, y, z] = undoc_fcn (a, b, ~, c = 3)
+  x = a;
+  y = b;
+  z = c;
+end