view libinterp/octave-value/ov-fcn-handle.cc @ 30564:796f54d4ddbf stable

update Octave Project Developers copyright for the new year In files that have the "Octave Project Developers" copyright notice, update for 2021. In all .txi and .texi files except gpl.txi and gpl.texi in the doc/liboctave and doc/interpreter directories, change the copyright to "Octave Project Developers", the same as used for other source files. Update copyright notices for 2022 (not done since 2019). For gpl.txi and gpl.texi, change the copyright notice to be "Free Software Foundation, Inc." and leave the date at 2007 only because this file only contains the text of the GPL, not anything created by the Octave Project Developers. Add Paul Thomas to contributors.in.
author John W. Eaton <jwe@octave.org>
date Tue, 28 Dec 2021 18:22:40 -0500
parents 75dff8f2de2e
children 83f9f8bda883 490e8a6107d0
line wrap: on
line source

////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2003-2022 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 <istream>
#include <list>
#include <ostream>
#include <sstream>
#include <string>
#include <vector>

#include "file-ops.h"
#include "oct-locbuf.h"

#include "defaults.h"
#include "defun.h"
#include "error.h"
#include "errwarn.h"
#include "file-stat.h"
#include "input.h"
#include "interpreter-private.h"
#include "interpreter.h"
#include "load-path.h"
#include "oct-env.h"
#include "oct-hdf5.h"
#include "oct-map.h"
#include "ov-base.h"
#include "ov-cell.h"
#include "ov-fcn-handle.h"
#include "ov-usr-fcn.h"
#include "parse.h"
#include "pr-output.h"
#include "pt-arg-list.h"
#include "pt-assign.h"
#include "pt-cmd.h"
#include "pt-eval.h"
#include "pt-exp.h"
#include "pt-idx.h"
#include "pt-misc.h"
#include "pt-pr-code.h"
#include "pt-stmt.h"
#include "stack-frame.h"
#include "syminfo.h"
#include "symscope.h"
#include "unwind-prot.h"
#include "variables.h"

#include "byte-swap.h"
#include "ls-ascii-helper.h"
#include "ls-hdf5.h"
#include "ls-oct-text.h"
#include "ls-oct-binary.h"
#include "ls-utils.h"


DEFINE_OV_TYPEID_FUNCTIONS_AND_DATA (octave_fcn_handle,
                                     "function handle",
                                     "function_handle");

const std::string octave_fcn_handle::anonymous ("@<anonymous>");

OCTAVE_NAMESPACE_BEGIN

  class invalid_fcn_handle : public base_fcn_handle
  {
  public:

    invalid_fcn_handle (void) : base_fcn_handle ("<invalid>") { }

    invalid_fcn_handle (const invalid_fcn_handle&) = default;

    ~invalid_fcn_handle (void) = default;

    invalid_fcn_handle * clone (void) const
    {
      return new invalid_fcn_handle (*this);
    }

    std::string type (void) const { return "<invalid>"; }

    octave_value_list call (int nargout, const octave_value_list& args);
  };

  // Create a handle to an unnamed internal function.  There will be no
  // way to save and reload it.  See, for example, the F__fltk_check__
  // function in __init_fltk__.cc.

  class internal_fcn_handle : public base_fcn_handle
  {
  public:

    internal_fcn_handle (const octave_value& fcn)
      : base_fcn_handle ("<internal>"), m_fcn (fcn)
    { }

    internal_fcn_handle (const internal_fcn_handle&) = default;

    ~internal_fcn_handle (void) = default;

    internal_fcn_handle * clone (void) const
    {
      return new internal_fcn_handle (*this);
    }

    std::string type (void) const { return "<internal>"; }

    bool is_internal (void) const { return true; }

    octave_value_list call (int nargout, const octave_value_list& args);

    // FIXME: These must go away.  They don't do the right thing for
    // scoping or overloads.
    octave_function * function_value (bool = false)
    {
      return m_fcn.function_value ();
    }

    octave_user_function * user_function_value (bool = false)
    {
      return m_fcn.user_function_value ();
    }

    octave_value fcn_val (void) { return m_fcn; }

    // Should be const.
    octave_scalar_map info (void);

    friend bool is_equal_to (const internal_fcn_handle& fh1,
                             const internal_fcn_handle& fh2);

  private:

    octave_value m_fcn;
  };

  class simple_fcn_handle : public base_fcn_handle
  {
  public:

    // FIXME: octaveroot is temporary information used when loading
    // handles.  Can we avoid using it in the constructor?

    simple_fcn_handle (const std::string& name = "",
                       const std::string& file = "",
                       const std::string& /*octaveroot*/ = "")
      : base_fcn_handle (name, file), m_fcn ()
    { }

    simple_fcn_handle (const octave_value& fcn, const std::string& name)
      : base_fcn_handle (name), m_fcn (fcn)
    {
      if (m_fcn.is_defined ())
        {
          octave_function *oct_fcn = m_fcn.function_value ();

          if (oct_fcn)
            m_file = oct_fcn->fcn_file_name ();
        }
    }

    simple_fcn_handle (const simple_fcn_handle&) = default;

    ~simple_fcn_handle (void) = default;

    simple_fcn_handle * clone (void) const
    {
      return new simple_fcn_handle (*this);
    }

    std::string type (void) const { return "simple"; }

    bool is_simple (void) const { return true; }

    octave_value_list call (int nargout, const octave_value_list& args);

    // FIXME: These must go away.  They don't do the right thing for
    // scoping or overloads.
    octave_function * function_value (bool);

    octave_user_function * user_function_value (bool);

    octave_value fcn_val (void);

    // Should be const.
    octave_scalar_map info (void);

    bool save_ascii (std::ostream& os);

    bool load_ascii (std::istream& is);

    bool save_binary (std::ostream& os, bool save_as_floats);

    bool load_binary (std::istream& is, bool swap, mach_info::float_format fmt);

    bool save_hdf5 (octave_hdf5_id loc_hid, const char *name, bool save_as_floats);

    bool load_hdf5 (octave_hdf5_id& group_hid, octave_hdf5_id& space_hid,
                    octave_hdf5_id& type_hid);

    void print_raw (std::ostream& os, bool pr_as_read_syntax,
                    int current_print_indent_level) const;

    friend bool is_equal_to (const simple_fcn_handle& fh1,
                             const simple_fcn_handle& fh2);

  private:

    octave_value m_fcn;
  };

  class scoped_fcn_handle : public base_fcn_handle
  {
  public:

    // FIXME: octaveroot is temporary information used when loading
    // handles.  Can we avoid using it in the constructor?

    scoped_fcn_handle (const std::string& name = "",
                       const std::string& file = "",
                       const std::string& /*octaveroot*/ = "")
      : base_fcn_handle (name, file)
    { }

    scoped_fcn_handle (const octave_value& fcn, const std::string& name,
                       const std::list<std::string>& parentage);

    scoped_fcn_handle (const scoped_fcn_handle&) = default;

    ~scoped_fcn_handle (void) = default;

    scoped_fcn_handle * clone (void) const
    {
      return new scoped_fcn_handle (*this);
    }

    std::string type (void) const { return "scopedfunction"; }

    bool is_scoped (void) const { return true; }

    octave_value_list call (int nargout, const octave_value_list& args);

    // FIXME: These must go away.  They don't do the right thing for
    // scoping or overloads.
    octave_function * function_value (bool = false)
    {
      return m_fcn.function_value ();
    }

    octave_user_function * user_function_value (bool = false)
    {
      return m_fcn.user_function_value ();
    }

    octave_value fcn_val (void) { return m_fcn; }

    // Should be const.
    octave_scalar_map info (void);

    bool save_ascii (std::ostream& os);

    bool load_ascii (std::istream& is);

    bool save_binary (std::ostream& os, bool save_as_floats);

    bool load_binary (std::istream& is, bool swap, mach_info::float_format fmt);

    bool save_hdf5 (octave_hdf5_id loc_id, const char *name, bool save_as_floats);

    bool load_hdf5 (octave_hdf5_id& group_hid, octave_hdf5_id& space_hid,
                    octave_hdf5_id& type_hid);

    void print_raw (std::ostream&, bool pr_as_read_syntax,
                    int current_print_indent_level) const;

    friend bool is_equal_to (const scoped_fcn_handle& fh1,
                             const scoped_fcn_handle& fh2);

  protected:

    void find_function (void);

    // The function we are handling.
    octave_value m_fcn;

    // List of parent function names.  The first element is the name of
    // m_fcn.
    std::list<std::string> m_parentage;
  };

  class base_nested_fcn_handle : public base_fcn_handle
  {
  public:

    // FIXME: octaveroot is temporary information used when loading
    // handles.  Can we avoid using it in the constructor?

    base_nested_fcn_handle (const std::string& name = "",
                            const std::string& file = "",
                            const std::string& /*octaveroot*/ = "")
      : base_fcn_handle (name, file)
    { }

    base_nested_fcn_handle (const octave_value& fcn, const std::string& name)
      : base_fcn_handle (name), m_fcn (fcn)
    { }

    std::string type (void) const { return "nested"; }

    using base_fcn_handle::is_nested;

    bool is_nested (void) const { return true; }

    // FIXME: These must go away.  They don't do the right thing for
    // scoping or overloads.
    octave_function * function_value (bool = false)
    {
      return m_fcn.function_value ();
    }

    octave_user_function * user_function_value (bool = false)
    {
      return m_fcn.user_function_value ();
    }

    octave_value fcn_val (void) { return m_fcn; }

    virtual octave_value workspace (void) const = 0;

    // Should be const.
    octave_scalar_map info (void);

    bool save_ascii (std::ostream& os);

    bool load_ascii (std::istream& is);

    bool save_binary (std::ostream& os, bool save_as_floats);

    bool load_binary (std::istream& is, bool swap, mach_info::float_format fmt);

    bool save_hdf5 (octave_hdf5_id loc_id, const char *name, bool save_as_floats);

    bool load_hdf5 (octave_hdf5_id& group_hid, octave_hdf5_id& space_hid,
                    octave_hdf5_id& type_hid);

    void print_raw (std::ostream&, bool pr_as_read_syntax,
                    int current_print_indent_level) const;

  protected:

    // The function we are handling.
    octave_value m_fcn;
  };

  class nested_fcn_handle : public base_nested_fcn_handle
  {
  public:

    // FIXME: octaveroot is temporary information used when loading
    // handles.  Can we avoid using it in the constructor?

    nested_fcn_handle (const std::string& name = "",
                       const std::string& file = "",
                       const std::string& octaveroot = "")
      : base_nested_fcn_handle (name, file, octaveroot)
    { }

    nested_fcn_handle (const octave_value& fcn, const std::string& name,
                       const std::shared_ptr<stack_frame>& stack_context)
      : base_nested_fcn_handle (fcn, name), m_stack_context (stack_context)
    {
      m_stack_context->mark_closure_context ();
    }

    nested_fcn_handle (const nested_fcn_handle&) = default;

    ~nested_fcn_handle (void) = default;

    using base_nested_fcn_handle::is_nested;

    bool is_nested (const std::shared_ptr<stack_frame>& frame) const
    {
      return frame == m_stack_context;
    }

    nested_fcn_handle * clone (void) const
    {
      return new nested_fcn_handle (*this);
    }

    octave_value make_weak_nested_handle (void) const;

    octave_value_list call (int nargout, const octave_value_list& args);

    octave_value workspace (void) const;

    friend bool is_equal_to (const nested_fcn_handle& fh1,
                             const nested_fcn_handle& fh2);

    std::shared_ptr<stack_frame> stack_context (void) const
    {
      return m_stack_context;
    }

  protected:

    // Pointer to closure stack frames.
    std::shared_ptr<stack_frame> m_stack_context;
  };

  class weak_nested_fcn_handle : public base_nested_fcn_handle
  {
  public:

    weak_nested_fcn_handle (const nested_fcn_handle& nfh)
      : base_nested_fcn_handle (nfh), m_stack_context (nfh.stack_context ())
    { }

    weak_nested_fcn_handle (const weak_nested_fcn_handle&) = default;

    ~weak_nested_fcn_handle (void) = default;

    weak_nested_fcn_handle * clone (void) const
    {
      return new weak_nested_fcn_handle (*this);
    }

    bool is_weak_nested (void) const { return true; }

    octave_value_list call (int nargout, const octave_value_list& args);

    octave_value workspace (void) const;

    friend bool is_equal_to (const weak_nested_fcn_handle& fh1,
                             const weak_nested_fcn_handle& fh2);

  protected:

    // Pointer to closure stack frames.
    std::weak_ptr<stack_frame> m_stack_context;
  };

  class class_simple_fcn_handle : public base_fcn_handle
  {
  public:

    // FIXME: octaveroot is temporary information used when loading
    // handles.  Can we avoid using it in the constructor?

    class_simple_fcn_handle (const std::string& name,
                             const std::string& file,
                             const std::string& /*octaveroot*/)
      : base_fcn_handle (name, file)
    { }

    // FIXME: is the method name supposed to be just the method name or
    // also contain the object name?

    class_simple_fcn_handle (const std::string& class_nm,
                             const std::string& meth_nm);

    class_simple_fcn_handle (const octave_value& fcn,
                             const std::string& class_nm,
                             const std::string& meth_nm);

    class_simple_fcn_handle (const octave_value& obj, const octave_value& fcn,
                             const std::string& class_nm,
                             const std::string& meth_nm);

    class_simple_fcn_handle (const class_simple_fcn_handle&) = default;

    ~class_simple_fcn_handle (void) = default;

    class_simple_fcn_handle * clone (void) const
    {
      return new class_simple_fcn_handle (*this);
    }

    std::string type (void) const { return "classsimple"; }

    bool is_class_simple (void) const { return true; }

    octave_value_list call (int nargout, const octave_value_list& args);

    // FIXME: These must go away.  They don't do the right thing for
    // scoping or overloads.
    octave_function * function_value (bool = false)
    {
      // FIXME: Shouldn't the lookup rules here match those used in the
      // call method?

      if (m_fcn.is_defined ())
        return m_fcn.function_value ();

      symbol_table& symtab
        = __get_symbol_table__ ("class_simple_fcn_handle::function_value");

      // FIXME: is caching the correct thing to do?
      // Cache this value so that the pointer will be valid as long as the
      // function handle object is valid.

      // FIXME: This should probably dispatch to the respective class method.
      // But that breaks if a function handle is used in a class method with
      // e.g. bsxfun with arguments of a different class (see bug #59661).
      // m_fcn = symtab.find_method (m_name, m_dispatch_class);
      m_fcn = symtab.find_function (m_name, octave_value_list ());

      return m_fcn.is_defined () ? m_fcn.function_value () : nullptr;
    }

    octave_user_function * user_function_value (bool = false)
    {
      return m_fcn.user_function_value ();
    }

    octave_value fcn_val (void) { return m_fcn; }

    // Should be const.
    octave_scalar_map info (void);

    std::string dispatch_class (void) const { return m_dispatch_class; }

    bool save_ascii (std::ostream& os);

    bool load_ascii (std::istream& is);

    bool save_binary (std::ostream& os, bool save_as_floats);

    bool load_binary (std::istream& is, bool swap, mach_info::float_format fmt);

    bool save_hdf5 (octave_hdf5_id loc_id, const char *name, bool save_as_floats);

    bool load_hdf5 (octave_hdf5_id& group_hid, octave_hdf5_id& space_hid,
                    octave_hdf5_id& type_hid);

    void print_raw (std::ostream&, bool pr_as_read_syntax,
                    int current_print_indent_level) const;

    friend bool is_equal_to (const class_simple_fcn_handle& fh1,
                             const class_simple_fcn_handle& fh2);

  protected:

    // The object containing the method we are handing.
    octave_value m_obj;

    // The method we are handling.
    octave_value m_fcn;

    // Name of the class that m_fcn belongs to.
    std::string m_dispatch_class;
  };

  // Handles to anonymous functions are similar to handles to nested
  // functions.  If they are created in a context that contains nested
  // functions, then they store a link to the parent call stack frames
  // that are active when they are created.  These call stack frames
  // (closure frames) provide access to variables needed by any nested
  // functions that are called from the anonymous function.  Anonymous
  // functions also store a list of values from their parent scope
  // corresponding to the symbols in the anonymous function.  This list
  // of values captures the variable values that are visible in the
  // scope where they are created.
  //
  // Note that because handles to anonymous and nested functions capture
  // call stack frames when they are created, they will cause deletion
  // of the values in those frames to be deferred until the handles to
  // the anonymous or nested functions are deleted.
  //
  // Would it be possible to avoid storing the closure frames for
  // handles to anonymous functions if we can determine that the
  // anonymous function has no unbound variables (or parameters, which
  // could be handles to nested functions?) or if it is not created in a
  // context that contains nested functions?
  //
  // Would it be possible to define anonymous functions as a special
  // type of nested function object that also has an variable
  // initialization list associated with it?

  class base_anonymous_fcn_handle : public base_fcn_handle
  {
  public:

    static const std::string anonymous;

    // Setting NAME here is a bit of a kluge to cope with a bad choice
    // made to append the number of local variables to the @<anonymous>
    // tag in the binary file format.  See also the save_binary and
    // load_binary functions.

    base_anonymous_fcn_handle (const std::string& name = "")
      : base_fcn_handle (name)
    { }

    base_anonymous_fcn_handle (const octave_value& fcn,
                               const stack_frame::local_vars_map& local_vars)
      : base_fcn_handle (anonymous), m_fcn (fcn), m_local_vars (local_vars)
    { }

    base_anonymous_fcn_handle (const base_anonymous_fcn_handle&) = default;

    ~base_anonymous_fcn_handle (void) = default;

    std::string type (void) const { return "anonymous"; }

    bool is_anonymous (void) const { return true; }

    // FIXME: These must go away.  They don't do the right thing for
    // scoping or overloads.
    octave_function * function_value (bool = false)
    {
      return m_fcn.function_value ();
    }

    octave_user_function * user_function_value (bool = false)
    {
      return m_fcn.user_function_value ();
    }

    octave_value fcn_val (void) { return m_fcn; }

    virtual octave_value workspace (void) const = 0;

    // Should be const.
    octave_scalar_map info (void);

    bool save_ascii (std::ostream& os);

    bool load_ascii (std::istream& is);

    bool save_binary (std::ostream& os, bool save_as_floats);

    bool load_binary (std::istream& is, bool swap, mach_info::float_format fmt);

    bool save_hdf5 (octave_hdf5_id loc_id, const char *name, bool save_as_floats);

    bool load_hdf5 (octave_hdf5_id& group_hid, octave_hdf5_id& space_hid,
                    octave_hdf5_id& type_hid);

    void print_raw (std::ostream&, bool pr_as_read_syntax,
                    int current_print_indent_level) const;

    // Anonymous function handles are printed without a newline.
    bool print_as_scalar (void) const { return false; }

    bool parse (const std::string& fcn_text);

  protected:

    // The function we are handling.
    octave_value m_fcn;

    // List of captured variable values for anonymous fucntions.
    stack_frame::local_vars_map m_local_vars;
  };

  class anonymous_fcn_handle : public base_anonymous_fcn_handle
  {
  public:

    using base_anonymous_fcn_handle::anonymous;

    // Setting NAME here is a bit of a kluge to cope with a bad choice
    // made to append the number of local variables to the @<anonymous>
    // tag in the binary file format.  See also the save_binary and
    // load_binary functions.

    anonymous_fcn_handle (const std::string& name = "")
      : base_anonymous_fcn_handle (name), m_stack_context ()
    { }

    anonymous_fcn_handle (const octave_value& fcn,
                          const stack_frame::local_vars_map& local_vars,
                          const std::shared_ptr<stack_frame>& stack_context = std::shared_ptr<stack_frame> ());

    anonymous_fcn_handle (const anonymous_fcn_handle&) = default;

    ~anonymous_fcn_handle (void) = default;

    anonymous_fcn_handle * clone (void) const
    {
      return new anonymous_fcn_handle (*this);
    }

    octave_value make_weak_anonymous_handle (void) const;

    octave_value_list call (int nargout, const octave_value_list& args);

    octave_value workspace (void) const;

    friend bool is_equal_to (const anonymous_fcn_handle& fh1,
                             const anonymous_fcn_handle& fh2);

    std::shared_ptr<stack_frame> stack_context (void) const
    {
      return m_stack_context;
    }

  protected:

    // Pointer to closure stack frames.
    std::shared_ptr<stack_frame> m_stack_context;
  };

  class weak_anonymous_fcn_handle : public base_anonymous_fcn_handle
  {
  public:

    using base_anonymous_fcn_handle::anonymous;

    weak_anonymous_fcn_handle (const anonymous_fcn_handle& afh)
      : base_anonymous_fcn_handle (afh), m_stack_context (afh.stack_context ())
    { }

    weak_anonymous_fcn_handle (const weak_anonymous_fcn_handle&) = default;

    ~weak_anonymous_fcn_handle (void) = default;

    weak_anonymous_fcn_handle * clone (void) const
    {
      return new weak_anonymous_fcn_handle (*this);
    }

    bool is_weak_anonymous (void) const { return true; }

    octave_value_list call (int nargout, const octave_value_list& args);

    octave_value workspace (void) const;

    friend bool is_equal_to (const weak_anonymous_fcn_handle& fh1,
                             const weak_anonymous_fcn_handle& fh2);

  protected:

    // Pointer to closure stack frames.
    std::weak_ptr<stack_frame> m_stack_context;
  };

  extern bool is_equal_to (const anonymous_fcn_handle& fh1,
                           const anonymous_fcn_handle& fh2);

  static void err_invalid_fcn_handle (const std::string& name)
  {
    error ("invalid function handle, unable to find function for @%s",
           name.c_str ());
  }

  octave_value base_fcn_handle::make_weak_nested_handle (void) const
  {
    std::string type_str = type ();
    error ("invalid conversion from %s handle to weak nestead handle",
           type_str.c_str ());
  }

  octave_value base_fcn_handle::make_weak_anonymous_handle (void) const
  {
    std::string type_str = type ();
    error ("invalid conversion from %s handle to weak anonymous handle",
           type_str.c_str ());
  }

  octave_value_list
  base_fcn_handle::subsref (const std::string& type,
                            const std::list<octave_value_list>& idx,
                            int nargout)
  {
    octave_value_list retval;

    switch (type[0])
      {
      case '(':
        {
          int tmp_nargout = (type.length () > 1 && nargout == 0) ? 1 : nargout;

          retval = call (tmp_nargout, idx.front ());
        }
        break;

      case '{':
      case '.':
        error ("function handle cannot be indexed with %c", type[0]);

      default:
        panic_impossible ();
      }

    // FIXME: perhaps there should be an
    // octave_value_list::next_subsref member function?  See also
    // octave_builtin::subsref.

    if (idx.size () > 1)
      retval = retval(0).next_subsref (nargout, type, idx);

    return retval;
  }

  octave_value
  base_fcn_handle::convert_to_str_internal (bool, bool, char type) const
  {
    std::ostringstream buf;
    print_raw (buf, true, 0);
    return octave_value (buf.str (), type);
  }

  bool
  base_fcn_handle::save_ascii (std::ostream&)
  {
    unimplemented ("save", "text");

    return true;
  }

  bool
  base_fcn_handle::load_ascii (std::istream&)
  {
    unimplemented ("load", "text");

    return true;
  }

  bool
  base_fcn_handle::save_binary (std::ostream&, bool)
  {
    unimplemented ("save", "binary");

    return true;
  }

  bool
  base_fcn_handle::load_binary (std::istream&, bool, mach_info::float_format)
  {
    unimplemented ("load", "binary");

    return true;
  }

  bool
  base_fcn_handle::save_hdf5 (octave_hdf5_id, const char *, bool)
  {
    unimplemented ("save", "hdf5");

    return true;
  }

  bool
  base_fcn_handle::load_hdf5 (octave_hdf5_id&, octave_hdf5_id&, octave_hdf5_id&)
  {
    unimplemented ("load", "hdf5");

    return true;
  }

  void base_fcn_handle::warn_load (const char *file_type) const
  {
    std::string obj_type = type ();

    warning_with_id
      ("Octave:load-save-unavailable",
       "%s: loading %s files not available in this version of Octave",
       obj_type.c_str (), file_type);
  }

  void base_fcn_handle::warn_save (const char *file_type) const
  {
    std::string obj_type = type ();

    warning_with_id
      ("Octave:load-save-unavailable",
       "%s: saving %s files not available in this version of Octave",
       obj_type.c_str (), file_type);
  }

  void base_fcn_handle::unimplemented (const char *op, const char *fmt) const
  {
    std::string htype = type ();

    warning ("%s for %s handles with %s format is not implemented",
             op, htype.c_str (), fmt);
  }

  octave_value_list
  invalid_fcn_handle::call (int, const octave_value_list&)
  {
    error ("invalid call to invalid function handle");
  }

  octave_value_list
  internal_fcn_handle::call (int nargout, const octave_value_list& args)
  {
    interpreter& interp = __get_interpreter__ ("internal_fcn_handle::call");

    return interp.feval (m_fcn, args, nargout);
  }

  octave_scalar_map internal_fcn_handle::info (void)
  {
    octave_scalar_map m;

    m.setfield ("function", fcn_name ());
    m.setfield ("type", type ());
    m.setfield ("file", "");

    return m;
  }

  bool is_equal_to (const internal_fcn_handle& fh1,
                    const internal_fcn_handle& fh2)
  {
    if (fh1.m_name == fh2.m_name
        && fh1.m_fcn.is_defined () && fh2.m_fcn.is_defined ())
      return fh1.m_fcn.is_copy_of (fh2.m_fcn);
    else
      return false;
  }

  octave_value_list
  simple_fcn_handle::call (int nargout, const octave_value_list& args)
  {
    // FIXME: if m_name has a '.' in the name, lookup first component.  If
    // it is a classdef meta object, then build TYPE and IDX arguments and
    // make a subsref call using them.

    interpreter& interp = __get_interpreter__ ("simple_fcn_handle::call");

    octave_value fcn_to_call;

    // The following code is similar to part of
    // tree_evaluator::visit_index_expression but simpler because it
    // handles a more restricted case.

    symbol_table& symtab = interp.get_symbol_table ();

    std::size_t pos = m_name.find ('.');

    if (pos != std::string::npos)
      {
        // FIXME: check to see which of these cases actually work in
        // Octave and Matlab.  For the last two, assume handle is
        // created before object is defined as an object.
        //
        // We can have one of
        //
        //   pkg-list . fcn  (args)
        //   pkg-list . cls . meth (args)
        //   class-name . method  (args)
        //   class-name . static-method  (args)
        //   object . method  (args)
        //   object . static-method  (args)

        // Evaluate package elements until we find a function,
        // classdef object, or classdef_meta object that is not a
        // package.  An object may only appear as the first element,
        // then it must be followed directly by a function name.

        std::size_t beg = 0;
        std::size_t end = pos;

        std::vector<std::string> idx_elts;

        while (true)
          {
            end = m_name.find ('.', beg);

            idx_elts.push_back (m_name.substr (beg, end-beg));

            if (end == std::string::npos)
              break;

            beg = end+1;
          }

        std::size_t n_elts = idx_elts.size ();

        bool have_object = false;
        octave_value partial_expr_val;

        // Lazy evaluation.  The first element was not known to be defined
        // as an object in the scope where the handle was created.  See if
        // there is a definition in the current scope.

        partial_expr_val = interp.varval (idx_elts[0]);

        if (partial_expr_val.is_defined ())
          {
            if (! partial_expr_val.is_classdef_object () || n_elts != 2)
              err_invalid_fcn_handle (m_name);

            have_object = true;
          }
        else
          partial_expr_val = symtab.find_function (idx_elts[0], ovl ());

        std::string type;
        std::list<octave_value_list> arg_list;

        for (std::size_t i = 1; i < n_elts; i++)
          {
            if (partial_expr_val.is_package ())
              {
                if (have_object)
                  err_invalid_fcn_handle (m_name);

                type = ".";
                arg_list.push_back (ovl (idx_elts[i]));

                try
                  {
                    // Silently ignore extra output values.

                    octave_value_list tmp_list
                      = partial_expr_val.subsref (type, arg_list, 0);

                    partial_expr_val
                      = tmp_list.length () ? tmp_list(0) : octave_value ();

                    if (partial_expr_val.is_cs_list ())
                      err_invalid_fcn_handle (m_name);

                    arg_list.clear ();
                  }
                catch (const index_exception&)
                  {
                    err_invalid_fcn_handle (m_name);
                  }
              }
            else if (have_object || partial_expr_val.is_classdef_meta ())
              {
                // Object or class name must be the next to the last
                // element (it was the previous one, so if this is the
                // final element, it should be a classdef method,
                // but we'll let the classdef or classdef_meta subsref
                // function sort that out.

                if (i != n_elts-1)
                  err_invalid_fcn_handle (m_name);

                type = ".(";
                arg_list.push_back (ovl (idx_elts[i]));
                arg_list.push_back (args);

                return partial_expr_val.subsref (type, arg_list, nargout);
              }
            else
              err_invalid_fcn_handle (m_name);
          }

        // If we get here, we must have a function to call.

        if (! partial_expr_val.is_function ())
          err_invalid_fcn_handle (m_name);

        fcn_to_call = partial_expr_val;
      }
    else
      {
        // No "." in the name.

        // Perform function lookup given current arguments.  We'll need
        // to do this regardless of whether a function was found when
        // the handle was created.

        octave_value ov_fcn = symtab.find_function (m_name, args);

        if (m_fcn.is_defined ())
          {
            // A simple function was found when the handle was created.
            // Use that unless we find a class method to override it.

            fcn_to_call = m_fcn;

            if (ov_fcn.is_defined ())
              {
                octave_function *fcn = ov_fcn.function_value ();

                std::string dispatch_class = fcn->dispatch_class ();

                if (fcn->is_class_method ())
                  {
                    // Function found through lookup is a class method
                    // so use it instead of the simple one found when
                    // the handle was created.

                    fcn_to_call = ov_fcn;
                  }
              }
          }
        else
          {
            // There was no simple function found when the handle was
            // created so use the one found here (if any).

            fcn_to_call = ov_fcn;
          }
      }

    if (! fcn_to_call.is_defined ())
      err_invalid_fcn_handle (m_name);

    return interp.feval (fcn_to_call, args, nargout);
  }

  octave_function * simple_fcn_handle::function_value (bool)
  {
    // FIXME: Shouldn't the lookup rules here match those used in the
    // call method?

    if (m_fcn.is_defined ())
      return m_fcn.function_value ();

    symbol_table& symtab
      = __get_symbol_table__ ("simple_fcn_handle::function_value");

    // FIXME: is caching the correct thing to do?
    // Cache this value so that the pointer will be valid as long as the
    // function handle object is valid.

    m_fcn = symtab.find_function (m_name, octave_value_list ());

    return m_fcn.is_defined () ? m_fcn.function_value () : nullptr;
  }

  octave_user_function * simple_fcn_handle::user_function_value (bool)
  {
    // FIXME: Shouldn't the lookup rules here match those used in the
    // call method?

    if (m_fcn.is_defined ())
      return m_fcn.user_function_value ();

    symbol_table& symtab
      = __get_symbol_table__ ("simple_fcn_handle::user_function_value");

    // FIXME: is caching the correct thing to do?
    // Cache this value so that the pointer will be valid as long as the
    // function handle object is valid.

    m_fcn = symtab.find_user_function (m_name);

    return m_fcn.is_defined () ? m_fcn.user_function_value () : nullptr;
  }

  octave_value simple_fcn_handle::fcn_val (void)
  {
    if (m_fcn.is_defined ())
      return m_fcn;

    symbol_table& symtab
      = __get_symbol_table__ ("simple_fcn_handle::user_function_value");

    // FIXME: is caching the correct thing to do?
    // Cache this value so that the pointer will be valid as long as the
    // function handle object is valid.

    m_fcn = symtab.find_user_function (m_name);

    return m_fcn;
  }

  octave_scalar_map simple_fcn_handle::info (void)
  {
    octave_scalar_map m;

    m.setfield ("function", fcn_name ());
    m.setfield ("type", type ());
    // When is FILE defined for simple function handles?
    m.setfield ("file", file ());

    return m;
  }

  bool simple_fcn_handle::save_ascii (std::ostream& os)
  {
    os << "# octaveroot: " << config::octave_exec_home () << "\n";

    std::string fnm = file ();
    if (! fnm.empty ())
      os << "# path: " << fnm << "\n";

    os << "# subtype: " << type () << "\n";

    os << m_name << "\n";

    return true;
  }

  bool simple_fcn_handle::load_ascii (std::istream& is)
  {
    // FIXME: If m_file is not empty, try to load the file and define
    // the function?  Is it an error if that fails?  Or should this job
    // always be deferred until the handle is used?

    return is.good ();
  }

  bool simple_fcn_handle::save_binary (std::ostream& os, bool)
  {
    std::ostringstream nmbuf;

    // When is FILE defined for simple function handles?
    std::string fnm;

    nmbuf << m_name << "@<simple>\n" << config::octave_exec_home ()
          << "\n" << fnm;

    std::string buf_str = nmbuf.str ();
    int32_t tmp = buf_str.length ();
    os.write (reinterpret_cast<char *> (&tmp), 4);
    os.write (buf_str.c_str (), buf_str.length ());

    return true;
  }

  bool simple_fcn_handle::load_binary (std::istream& is, bool,
                                       mach_info::float_format)
  {
    return is.good ();
  }

  bool simple_fcn_handle::save_hdf5 (octave_hdf5_id loc_id, const char *name,
                                     bool)
  {
#if defined (HAVE_HDF5)

    bool retval = true;

    octave_hdf5_id group_hid = -1;
#if defined (HAVE_HDF5_18)
    group_hid = H5Gcreate (loc_id, name, octave_H5P_DEFAULT, octave_H5P_DEFAULT,
                           octave_H5P_DEFAULT);
#else
    group_hid = H5Gcreate (loc_id, name, 0);
#endif
    if (group_hid < 0)
      return false;

    octave_hdf5_id space_hid, data_hid, type_hid;
    space_hid = data_hid = type_hid = -1;

    // attach the type of the variable
    type_hid = H5Tcopy (H5T_C_S1);
    H5Tset_size (type_hid, m_name.length () + 1);
    if (type_hid < 0)
      {
        H5Gclose (group_hid);
        return false;
      }

    OCTAVE_LOCAL_BUFFER (hsize_t, hdims, 2);
    hdims[0] = 0;
    hdims[1] = 0;
    space_hid = H5Screate_simple (0, hdims, nullptr);
    if (space_hid < 0)
      {
        H5Tclose (type_hid);
        H5Gclose (group_hid);
        return false;
      }
#if defined (HAVE_HDF5_18)
    data_hid = H5Dcreate (group_hid, "nm",  type_hid, space_hid,
                          octave_H5P_DEFAULT, octave_H5P_DEFAULT,
                          octave_H5P_DEFAULT);
#else
    data_hid = H5Dcreate (group_hid, "nm",  type_hid, space_hid,
                          octave_H5P_DEFAULT);
#endif
    if (data_hid < 0
        || H5Dwrite (data_hid, type_hid, octave_H5S_ALL, octave_H5S_ALL,
                     octave_H5P_DEFAULT, m_name.c_str ()) < 0)
      {
        H5Sclose (space_hid);
        H5Tclose (type_hid);
        H5Gclose (group_hid);
        return false;
      }
    H5Dclose (data_hid);

    std::string octaveroot = config::octave_exec_home ();

    // When is FILE defined for simple fucntion handles?
    std::string fpath;

    H5Sclose (space_hid);
    hdims[0] = 1;
    hdims[1] = octaveroot.length ();
    space_hid = H5Screate_simple (0, hdims, nullptr);
    if (space_hid < 0)
      {
        H5Tclose (type_hid);
        H5Gclose (group_hid);
        return false;
      }

    H5Tclose (type_hid);
    type_hid = H5Tcopy (H5T_C_S1);
    H5Tset_size (type_hid, octaveroot.length () + 1);
    octave_hdf5_id a_id;
#if defined (HAVE_HDF5_18)
    a_id = H5Acreate (group_hid, "OCTAVEROOT", type_hid, space_hid,
                      octave_H5P_DEFAULT, octave_H5P_DEFAULT);
#else
    a_id = H5Acreate (group_hid, "OCTAVEROOT", type_hid, space_hid,
                      octave_H5P_DEFAULT);
#endif

    if (a_id >= 0)
      {
        retval = (H5Awrite (a_id, type_hid, octaveroot.c_str ()) >= 0);

        H5Aclose (a_id);
      }
    else
      {
        H5Sclose (space_hid);
        H5Tclose (type_hid);
        H5Gclose (group_hid);
        return false;
      }

    H5Sclose (space_hid);
    hdims[0] = 1;
    hdims[1] = fpath.length ();
    space_hid = H5Screate_simple (0, hdims, nullptr);
    if (space_hid < 0)
      {
        H5Tclose (type_hid);
        H5Gclose (group_hid);
        return false;
      }

    H5Tclose (type_hid);
    type_hid = H5Tcopy (H5T_C_S1);
    H5Tset_size (type_hid, fpath.length () + 1);

#if defined (HAVE_HDF5_18)
    a_id = H5Acreate (group_hid, "FILE", type_hid, space_hid,
                      octave_H5P_DEFAULT, octave_H5P_DEFAULT);
#else
    a_id = H5Acreate (group_hid, "FILE", type_hid, space_hid, octave_H5P_DEFAULT);
#endif

    if (a_id >= 0)
      {
        retval = (H5Awrite (a_id, type_hid, fpath.c_str ()) >= 0);

        H5Aclose (a_id);
      }
    else
      retval = false;

    H5Sclose (space_hid);
    H5Tclose (type_hid);
    H5Gclose (group_hid);

    return retval;

#else

    octave_unused_parameter (loc_id);
    octave_unused_parameter (name);

    warn_save ("hdf5");

    return false;

#endif
  }

  bool simple_fcn_handle::load_hdf5 (octave_hdf5_id& group_hid,
                                     octave_hdf5_id& space_hid,
                                     octave_hdf5_id& type_hid)
  {
#if defined (HAVE_HDF5)

    unimplemented ("load", "hdf5");

    octave_unused_parameter (group_hid);
    octave_unused_parameter (space_hid);
    octave_unused_parameter (type_hid);

    return true;

#else

    octave_unused_parameter (group_hid);
    octave_unused_parameter (space_hid);
    octave_unused_parameter (type_hid);

    return false;

#endif
  }

  void simple_fcn_handle::print_raw (std::ostream& os, bool pr_as_read_syntax,
                                     int current_print_indent_level) const
  {
    octave_print_internal (os, '@' + m_name, pr_as_read_syntax,
                           current_print_indent_level);
  }

  bool is_equal_to (const simple_fcn_handle& fh1, const simple_fcn_handle& fh2)
  {
    if (fh1.m_name == fh2.m_name)
      {
        if (fh1.m_fcn.is_defined () && fh2.m_fcn.is_defined ())
          return fh1.m_fcn.is_copy_of (fh2.m_fcn);

        if (fh1.m_fcn.is_undefined () && fh2.m_fcn.is_undefined ())
          return true;
      }

    return false;
  }

  scoped_fcn_handle::scoped_fcn_handle (const octave_value& fcn,
                                        const std::string& name,
                                        const std::list<std::string>& parentage)
    : base_fcn_handle (name), m_fcn (fcn), m_parentage (parentage)
  {
    // FIXME: should it be an error if FCN is undefined?

    if (m_fcn.is_defined ())
      {
        octave_function *oct_fcn = m_fcn.function_value ();

        if (oct_fcn)
          m_file = oct_fcn->fcn_file_name ();
      }

    m_parentage.push_front (name);
  }

  octave_value_list
  scoped_fcn_handle::call (int nargout, const octave_value_list& args)
  {
    // FIXME: we aren't really using the scope yet.  Hmm.

    interpreter& interp = __get_interpreter__ ("simple_fcn_handle::call");

    if (! m_fcn.is_defined ())
      {
        // Try to find it?

        find_function ();
      }

    if (! m_fcn.is_defined ())
      err_invalid_fcn_handle (m_name);

    return interp.feval (m_fcn, args, nargout);
  }

  octave_scalar_map scoped_fcn_handle::info (void)
  {
    octave_scalar_map m;

    m.setfield ("function", fcn_name ());
    m.setfield ("type", type ());
    m.setfield ("file", file ());

    m.setfield ("parentage", Cell (m_parentage));

    return m;
  }

  bool scoped_fcn_handle::save_ascii (std::ostream& os)
  {
    os << "# octaveroot: " << config::octave_exec_home () << "\n";

    std::string fnm = file ();
    if (! fnm.empty ())
      os << "# path: " << fnm << "\n";

    os << "# subtype: " << type () << "\n";

    os << m_name << "\n";

    octave_value tmp = Cell (m_parentage);
    tmp.save_ascii (os);

    return os.good ();
  }

  bool scoped_fcn_handle::load_ascii (std::istream& is)
  {
    octave_cell ov_cell;
    ov_cell.load_ascii (is);

    if (ov_cell.iscellstr ())
      {
        Array<std::string> cellstr_val = ov_cell.cellstr_value ();

        for (octave_idx_type i = 0; i < cellstr_val.numel (); i++)
          m_parentage.push_back (cellstr_val(i));
      }

    return is.good ();
  }

  bool scoped_fcn_handle::save_binary (std::ostream& os, bool save_as_floats)
  {
    std::ostringstream nmbuf;

    std::string fnm = file ();

    nmbuf << m_name << "@<scopedfunction>\n" << config::octave_exec_home ()
          << "\n" << fnm;

    std::string buf_str = nmbuf.str ();
    int32_t len = buf_str.length ();
    os.write (reinterpret_cast<char *> (&len), 4);
    os.write (buf_str.c_str (), buf_str.length ());

    octave_value tmp = Cell (m_parentage);
    tmp.save_binary (os, save_as_floats);

    return os.good ();
  }

  bool scoped_fcn_handle::load_binary (std::istream& is, bool swap,
                                       mach_info::float_format fmt)
  {
    octave_cell ov_cell;
    ov_cell.load_binary (is, swap, fmt);

    if (ov_cell.iscellstr ())
      {
        Array<std::string> cellstr_val = ov_cell.cellstr_value ();

        for (octave_idx_type i = 0; i < cellstr_val.numel (); i++)
          m_parentage.push_back (cellstr_val(i));
      }

    return is.good ();
  }

  bool scoped_fcn_handle::save_hdf5 (octave_hdf5_id loc_id, const char *name,
                                     bool)
  {
#if defined (HAVE_HDF5)

    unimplemented ("save", "hdf5");

    // FIXME: save parentage.

    octave_unused_parameter (loc_id);
    octave_unused_parameter (name);

    return true;

#else

    octave_unused_parameter (loc_id);
    octave_unused_parameter (name);

    warn_save ("hdf5");

    return false;

#endif
  }

  bool scoped_fcn_handle::load_hdf5 (octave_hdf5_id& group_hid,
                                     octave_hdf5_id& space_hid,
                                     octave_hdf5_id& type_hid)
  {
#if defined (HAVE_HDF5)

    unimplemented ("load", "hdf5");

    // FIXME: load parentage.

    octave_unused_parameter (group_hid);
    octave_unused_parameter (space_hid);
    octave_unused_parameter (type_hid);

    return true;

#else

    octave_unused_parameter (group_hid);
    octave_unused_parameter (space_hid);
    octave_unused_parameter (type_hid);

    return false;

#endif
  }

  void scoped_fcn_handle::print_raw (std::ostream& os,
                                     bool pr_as_read_syntax,
                                     int current_print_indent_level) const
  {
    octave_print_internal (os, '@' + m_name, pr_as_read_syntax,
                           current_print_indent_level);
  }

 bool is_equal_to (const scoped_fcn_handle& fh1, const scoped_fcn_handle& fh2)
  {
    if (fh1.m_name == fh2.m_name
        && fh2.m_parentage == fh2.m_parentage
        && fh1.m_fcn.is_defined () && fh2.m_fcn.is_defined ())
      return fh1.m_fcn.is_copy_of (fh2.m_fcn);
    else
      return false;
  }

  void scoped_fcn_handle::find_function (void)
  {
    // Since a scoped function is not visible by itself, try to load the
    // file named in m_file then find and define the scoped function.
    // It is not an error if this fails.  We can report later that the
    // handle is invalid.

    symbol_table& symtab
      = __get_symbol_table__ ("scoped_fcn_handle::find_function");

    if (m_parentage.size () == 1)
      {
        std::string dir_name = sys::file_ops::dirname (m_file);

        std::size_t pos = dir_name.find_last_of (sys::file_ops::dir_sep_chars ());

        if (pos != std::string::npos)
          dir_name = dir_name.substr (0, pos);
        else if (dir_name == "private")
          dir_name = ".";

        std::string fcn_name = m_parentage.front ();

        // FIXME: Does dir_name need to be in the load path for this to work?

        m_fcn = symtab.find_private_function (dir_name, m_name);

        // FIXME: Verify that it is a private function?
      }
    else
      {
        std::string primary_parent_name = m_parentage.back ();

        octave_value ov_parent_fcn
          = symtab.find_user_function (primary_parent_name);

        if (ov_parent_fcn.is_defined ())
          {
            octave_user_function *fcn = ov_parent_fcn.user_function_value ();

            if (fcn)
              {
                std::string file_name = fcn->fcn_file_name ();

                std::string oct_home = config::octave_exec_home ();

                if (file_name.substr (0, oct_home.size ()) == oct_home)
                  file_name = file_name.substr (oct_home.size ());

                octave_value subfun = fcn->find_subfunction (m_name);

                if (subfun.is_defined ())
                  m_fcn = subfun;
              }
          }
      }
  }

  octave_scalar_map base_nested_fcn_handle::info (void)
  {
    octave_scalar_map m;

    m.setfield ("function", fcn_name ());
    m.setfield ("type", type ());
    m.setfield ("file", "");
    m.setfield ("workspace", workspace ());

    return m;
  }

  // FIXME: For save, we need a way to save the (possibly shared)
  // workspace.  For load, we need a way to load and link to the
  // (possibly shared) workspace that was saved.
  //
  // Since a nested function is not visible by itself, do we need to try
  // to load the file named in m_file then find and define the function?
  // Is it an error if that fails?  Or should this job always be
  // deferred until the handle is used?

  bool base_nested_fcn_handle::save_ascii (std::ostream& os)
  {
    unimplemented ("save", "text");

    octave_unused_parameter (os);

    return true;
  }

  bool base_nested_fcn_handle::load_ascii (std::istream& is)
  {
    unimplemented ("load", "text");

    octave_unused_parameter (is);

    return true;
  }

  bool base_nested_fcn_handle::save_binary (std::ostream& os,
                                            bool save_as_floats)
  {
    unimplemented ("save", "binary");

    octave_unused_parameter (os);
    octave_unused_parameter (save_as_floats);

    return true;
  }

  bool base_nested_fcn_handle::load_binary (std::istream& is, bool swap,
                                            mach_info::float_format fmt)
  {
    unimplemented ("load", "binary");

    octave_unused_parameter (is);
    octave_unused_parameter (swap);
    octave_unused_parameter (fmt);

    return true;
  }

  bool base_nested_fcn_handle::save_hdf5 (octave_hdf5_id loc_id,
                                          const char *name, bool)
  {
#if defined (HAVE_HDF5)

    unimplemented ("save", "hdf5");

    octave_unused_parameter (loc_id);
    octave_unused_parameter (name);

    return true;

#else

    octave_unused_parameter (loc_id);
    octave_unused_parameter (name);

    warn_save ("hdf5");

    return false;

#endif
  }

  bool base_nested_fcn_handle::load_hdf5 (octave_hdf5_id& group_hid,
                                          octave_hdf5_id& space_hid,
                                          octave_hdf5_id& type_hid)
  {
#if defined (HAVE_HDF5)

    unimplemented ("load", "hdf5");

    octave_unused_parameter (group_hid);
    octave_unused_parameter (space_hid);
    octave_unused_parameter (type_hid);

    return true;

#else

    octave_unused_parameter (group_hid);
    octave_unused_parameter (space_hid);
    octave_unused_parameter (type_hid);

    return false;

#endif
  }

  void base_nested_fcn_handle::print_raw (std::ostream& os,
                                          bool pr_as_read_syntax,
                                          int current_print_indent_level) const
  {
    octave_print_internal (os, '@' + m_name, pr_as_read_syntax,
                           current_print_indent_level);
  }

  octave_value nested_fcn_handle::make_weak_nested_handle (void) const
  {
    return octave_value (new octave_fcn_handle
                         (new weak_nested_fcn_handle (*this)));
  }

  octave_value_list
  nested_fcn_handle::call (int nargout, const octave_value_list& args)
  {
    tree_evaluator& tw = __get_evaluator__ ("nested_fcn_handle::call");

    octave_user_function *oct_usr_fcn = m_fcn.user_function_value ();

    tw.push_stack_frame (oct_usr_fcn, m_stack_context);

    unwind_action act ([&tw] () { tw.pop_stack_frame (); });

    return oct_usr_fcn->execute (tw, nargout, args);
  }

  octave_value nested_fcn_handle::workspace (void) const
  {
    return m_stack_context->workspace ();
  }

  bool is_equal_to (const nested_fcn_handle& fh1, const nested_fcn_handle& fh2)
  {
    if (fh1.m_name == fh2.m_name
        && fh1.m_fcn.is_defined () && fh2.m_fcn.is_defined ())
      return fh1.m_fcn.is_copy_of (fh2.m_fcn);
    else
      return false;
  }

  octave_value_list
  weak_nested_fcn_handle::call (int nargout, const octave_value_list& args)
  {
    tree_evaluator& tw = __get_evaluator__ ("weak_nested_fcn_handle::call");

    octave_user_function *oct_usr_fcn = m_fcn.user_function_value ();

    std::shared_ptr<stack_frame> frames = m_stack_context.lock ();

    tw.push_stack_frame (oct_usr_fcn, frames);

    unwind_action act ([&tw] () { tw.pop_stack_frame (); });

    return oct_usr_fcn->execute (tw, nargout, args);
  }

  octave_value weak_nested_fcn_handle::workspace (void) const
  {
    std::shared_ptr<stack_frame> frames = m_stack_context.lock ();

    return frames ? frames->workspace () : octave_value ();
  }

  bool is_equal_to (const weak_nested_fcn_handle& fh1,
                    const weak_nested_fcn_handle& fh2)
  {
    if (fh1.m_name == fh2.m_name
        && fh1.m_fcn.is_defined () && fh2.m_fcn.is_defined ())
      return fh1.m_fcn.is_copy_of (fh2.m_fcn);
    else
      return false;
  }

  class_simple_fcn_handle::class_simple_fcn_handle (const std::string& class_nm,
                                                    const std::string& meth_nm)
    : base_fcn_handle (meth_nm), m_obj (), m_fcn (),
      m_dispatch_class (class_nm)
  { }

  class_simple_fcn_handle::class_simple_fcn_handle (const octave_value& fcn,
                                                    const std::string& class_nm,
                                                    const std::string& meth_nm)
    : base_fcn_handle (meth_nm), m_obj (), m_fcn (fcn),
      m_dispatch_class (class_nm)
  { }

  class_simple_fcn_handle::class_simple_fcn_handle (const octave_value& obj,
                                                    const octave_value& fcn,
                                                    const std::string& class_nm,
                                                    const std::string& meth_nm)
    : base_fcn_handle (meth_nm), m_obj (obj), m_fcn (fcn),
      m_dispatch_class (class_nm)
  { }

  octave_value_list
  class_simple_fcn_handle::call (int nargout, const octave_value_list& args)
  {
    interpreter& interp = __get_interpreter__ ("class_simple_fcn_handle::call");

    if (m_obj.is_defined ())
      {
        octave_value_list tmp_args = args;
        tmp_args.prepend (m_obj);

        return interp.feval (m_fcn, tmp_args, nargout);
      }

    // FIXME: is this the best approach?  Should we be saving current
    // dispatch class and restoring that value instead of
    // unconditionally setting it to "" when we return from this
    // function?

    tree_evaluator& tw = interp.get_evaluator ();

    unwind_action act ([&tw] () { tw.set_dispatch_class (""); });

    tw.set_dispatch_class (m_dispatch_class);

    if (m_fcn.is_defined ())
      return interp.feval (m_fcn, args, nargout);

    return interp.feval (fcn_name (), args, nargout);
  }

  octave_scalar_map class_simple_fcn_handle::info (void)
  {
    octave_scalar_map m;

    m.setfield ("function", fcn_name ());
    m.setfield ("type", type ());
    m.setfield ("file", "");
    m.setfield ("class", dispatch_class ());

    return m;
  }

  // FIXME: Since a class method is not visible by itself, do we need to
  // try to load the file named in m_file then find and define the
  // function?  Is it an error if that fails?  Or should this job always
  // be deferred until the handle is used?

  bool class_simple_fcn_handle::save_ascii (std::ostream& os)
  {
    unimplemented ("save", "text");

    octave_unused_parameter (os);

    return true;
  }

  bool class_simple_fcn_handle::load_ascii (std::istream& is)
  {
    unimplemented ("load", "text");

    octave_unused_parameter (is);

    return true;
  }

  bool class_simple_fcn_handle::save_binary (std::ostream& os,
                                             bool save_as_floats)
  {
    unimplemented ("save", "binary");

    octave_unused_parameter (os);
    octave_unused_parameter (save_as_floats);

    return true;
  }

  bool class_simple_fcn_handle::load_binary (std::istream& is, bool swap,
                                             mach_info::float_format fmt)
  {
    unimplemented ("load", "binary");

    octave_unused_parameter (is);
    octave_unused_parameter (swap);
    octave_unused_parameter (fmt);

    return true;
  }

  bool class_simple_fcn_handle::save_hdf5 (octave_hdf5_id loc_id,
                                           const char *name, bool)
  {
#if defined (HAVE_HDF5)

    unimplemented ("save", "hdf5");

    octave_unused_parameter (loc_id);
    octave_unused_parameter (name);

    return true;

#else

    octave_unused_parameter (loc_id);
    octave_unused_parameter (name);

    warn_save ("hdf5");

    return false;

#endif
  }

  bool class_simple_fcn_handle::load_hdf5 (octave_hdf5_id& group_hid,
                                           octave_hdf5_id& space_hid,
                                           octave_hdf5_id& type_hid)
  {
#if defined (HAVE_HDF5)

    unimplemented ("load", "hdf5");

    octave_unused_parameter (group_hid);
    octave_unused_parameter (space_hid);
    octave_unused_parameter (type_hid);

    return true;

#else

    octave_unused_parameter (group_hid);
    octave_unused_parameter (space_hid);
    octave_unused_parameter (type_hid);

    return false;

#endif
  }

  void class_simple_fcn_handle::print_raw (std::ostream& os,
                                           bool pr_as_read_syntax,
                                           int current_print_indent_level) const
  {
    octave_print_internal (os, '@' + m_name, pr_as_read_syntax,
                           current_print_indent_level);
  }

  bool is_equal_to (const class_simple_fcn_handle& fh1,
                    const class_simple_fcn_handle& fh2)
  {
    // FIXME: Also need to check object values are equivalent?

    if (fh1.m_name == fh2.m_name
        && fh1.m_fcn.is_defined () && fh2.m_fcn.is_defined ())
      return fh1.m_fcn.is_copy_of (fh2.m_fcn);
    else
      return false;
  }

  const std::string base_anonymous_fcn_handle::anonymous ("@<anonymous>");

  octave_scalar_map base_anonymous_fcn_handle::info (void)
  {
    octave_scalar_map m;

    std::ostringstream buf;
    print_raw (buf, true, 0);
    m.setfield ("function", buf.str ());

    m.setfield ("type", type ());
    m.setfield ("file", "");
    m.setfield ("workspace", workspace ());
    m.setfield ("within_file_path", "");

    return m;
  }

  bool base_anonymous_fcn_handle::save_ascii (std::ostream& os)
  {
    // FIXME: can we ensure that m_fcn is always defined?

    if (m_fcn.is_undefined ())
      return false;

    os << m_name << "\n";

    print_raw (os, true, 0);
    os << "\n";

    std::size_t varlen = m_local_vars.size ();

    if (varlen > 0)
      {
        os << "# length: " << varlen << "\n";

        for (const auto& nm_val : m_local_vars)
          {
            if (! save_text_data (os, nm_val.second, nm_val.first, false, 0))
              return ! os.fail ();
          }
      }

    return true;
  }

  bool base_anonymous_fcn_handle::load_ascii (std::istream& is)
  {
    octave::skip_preceeding_newline (is);

    std::string buf;

    if (is)
      {
        // Get a line of text whitespace characters included, leaving
        // newline in the stream.

        buf = octave::read_until_newline (is, true);
      }

    std::streampos pos = is.tellg ();

    // Set up temporary scope to use for evaluating the text that
    // defines the anonymous function.

    interpreter& interp
      = __get_interpreter__ ("base_anonymous_fcn_handle::load_ascii");

    tree_evaluator& tw = interp.get_evaluator ();

    tw.push_dummy_scope (buf);
    unwind_action_safe restore_scope (&tree_evaluator::pop_scope, &tw);

    octave_idx_type len = 0;

    if (extract_keyword (is, "length", len, true) && len >= 0)
      {
        if (len > 0)
          {
            for (octave_idx_type i = 0; i < len; i++)
              {
                octave_value t2;
                bool dummy;

                std::string name = read_text_data (is, "", dummy, t2, i);

                if (! is)
                  error ("load: failed to load anonymous function handle");

                m_local_vars[name] = t2;
              }
          }
      }
    else
      {
        is.seekg (pos);
        is.clear ();
      }

    if (is)
      return parse (buf);

    return false;
  }

  bool base_anonymous_fcn_handle::save_binary (std::ostream& os,
                                               bool save_as_floats)
  {
    // FIXME: can we ensure that m_fcn is always defined?

    if (m_fcn.is_undefined ())
      return false;

    std::ostringstream nmbuf;

    std::size_t varlen = m_local_vars.size ();

    nmbuf << anonymous;
    if (varlen > 0)
      nmbuf << ' ' << varlen;

    std::string buf_str = nmbuf.str ();
    int32_t tmp = buf_str.length ();
    os.write (reinterpret_cast<char *> (&tmp), 4);
    os.write (buf_str.c_str (), buf_str.length ());

    std::ostringstream buf;
    print_raw (buf, true, 0);
    std::string stmp = buf.str ();
    tmp = stmp.length ();
    os.write (reinterpret_cast<char *> (&tmp), 4);
    os.write (stmp.c_str (), stmp.length ());

    if (varlen > 0)
      {
        for (const auto& nm_val : m_local_vars)
          {
            if (! save_binary_data (os, nm_val.second, nm_val.first,
                                    "", 0, save_as_floats))
              return ! os.fail ();
          }
      }

    return true;
  }

  bool base_anonymous_fcn_handle::load_binary (std::istream& is, bool swap,
                                               mach_info::float_format fmt)
  {
    // Read extra characters in m_name as the number of local variable
    // values in this anonymous function.

    octave_idx_type len = 0;
    std::size_t anl = anonymous.length ();
    if (m_name.length () > anl)
      {
        std::istringstream nm_is (m_name.substr (anl));
        nm_is >> len;

        // Anonymous functons don't have names.  We just used this
        // string as temporary storage to pass the number of local
        // variable values.

        m_name = "";
      }

    int32_t tmp;

    if (! is.read (reinterpret_cast<char *> (&tmp), 4))
      return false;
    if (swap)
      swap_bytes<4> (&tmp);

    OCTAVE_LOCAL_BUFFER (char, ctmp2, tmp+1);
    // is.get (ctmp2, tmp+1, 0); caused is.eof () to be true though
    // effectively not reading over file end
    is.read (ctmp2, tmp);
    ctmp2[tmp] = 0;

    // Set up temporary scope to use for evaluating the text that
    // defines the anonymous function.

    interpreter& interp
      = __get_interpreter__ ("base_anonymous_fcn_handle::load_binary");

    tree_evaluator& tw = interp.get_evaluator ();

    tw.push_dummy_scope (ctmp2);
    unwind_action_safe restore_scope (&tree_evaluator::pop_scope, &tw);

    if (len > 0)
      {
        for (octave_idx_type i = 0; i < len; i++)
          {
            octave_value t2;
            bool dummy;
            std::string doc;

            std::string name
              = read_binary_data (is, swap, fmt, "", dummy, t2, doc);

            if (! is)
              error ("load: failed to load anonymous function handle");

            m_local_vars[name] = t2;
          }
      }

    if (is)
      return parse (ctmp2);

    return false;
  }

  bool base_anonymous_fcn_handle::save_hdf5 (octave_hdf5_id loc_id,
                                             const char *name,
                                             bool save_as_floats)
  {
#if defined (HAVE_HDF5)

    bool retval = true;

    octave_hdf5_id group_hid = -1;
#if defined (HAVE_HDF5_18)
    group_hid = H5Gcreate (loc_id, name, octave_H5P_DEFAULT, octave_H5P_DEFAULT,
                           octave_H5P_DEFAULT);
#else
    group_hid = H5Gcreate (loc_id, name, 0);
#endif
    if (group_hid < 0)
      return false;

    octave_hdf5_id space_hid, data_hid, type_hid;
    space_hid = data_hid = type_hid = -1;

    // attach the type of the variable
    type_hid = H5Tcopy (H5T_C_S1);
    H5Tset_size (type_hid, m_name.length () + 1);
    if (type_hid < 0)
      {
        H5Gclose (group_hid);
        return false;
      }

    OCTAVE_LOCAL_BUFFER (hsize_t, hdims, 2);
    hdims[0] = 0;
    hdims[1] = 0;
    space_hid = H5Screate_simple (0, hdims, nullptr);
    if (space_hid < 0)
      {
        H5Tclose (type_hid);
        H5Gclose (group_hid);
        return false;
      }
#if defined (HAVE_HDF5_18)
    data_hid = H5Dcreate (group_hid, "nm",  type_hid, space_hid,
                          octave_H5P_DEFAULT, octave_H5P_DEFAULT,
                          octave_H5P_DEFAULT);
#else
    data_hid = H5Dcreate (group_hid, "nm",  type_hid, space_hid,
                          octave_H5P_DEFAULT);
#endif
    if (data_hid < 0
        || H5Dwrite (data_hid, type_hid, octave_H5S_ALL, octave_H5S_ALL,
                     octave_H5P_DEFAULT, m_name.c_str ()) < 0)
      {
        H5Sclose (space_hid);
        H5Tclose (type_hid);
        H5Gclose (group_hid);
        return false;
      }
    H5Dclose (data_hid);

    std::ostringstream buf;
    print_raw (buf, true, 0);
    std::string stmp = buf.str ();

    // attach the type of the variable
    H5Tset_size (type_hid, stmp.length () + 1);
    if (type_hid < 0)
      {
        H5Sclose (space_hid);
        H5Gclose (group_hid);
        return false;
      }

#if defined (HAVE_HDF5_18)
    data_hid = H5Dcreate (group_hid, "fcn",  type_hid, space_hid,
                          octave_H5P_DEFAULT, octave_H5P_DEFAULT,
                          octave_H5P_DEFAULT);
#else
    data_hid = H5Dcreate (group_hid, "fcn",  type_hid, space_hid,
                          octave_H5P_DEFAULT);
#endif
    if (data_hid < 0
        || H5Dwrite (data_hid, type_hid, octave_H5S_ALL, octave_H5S_ALL,
                     octave_H5P_DEFAULT, stmp.c_str ()) < 0)
      {
        H5Sclose (space_hid);
        H5Tclose (type_hid);
        H5Gclose (group_hid);
        return false;
      }

    H5Dclose (data_hid);

    std::size_t varlen = m_local_vars.size ();

    if (varlen > 0)
      {
        octave_hdf5_id as_id = H5Screate (H5S_SCALAR);

        if (as_id >= 0)
          {
            octave_hdf5_id a_id;
#if defined (HAVE_HDF5_18)
            a_id = H5Acreate (group_hid, "SYMBOL_TABLE", H5T_NATIVE_IDX, as_id,
                              octave_H5P_DEFAULT, octave_H5P_DEFAULT);

#else
            a_id = H5Acreate (group_hid, "SYMBOL_TABLE", H5T_NATIVE_IDX, as_id,
                              octave_H5P_DEFAULT);
#endif

            if (a_id >= 0)
              {
                retval = (H5Awrite (a_id, H5T_NATIVE_IDX, &varlen) >= 0);

                H5Aclose (a_id);
              }
            else
              retval = false;

            H5Sclose (as_id);
          }
        else
          retval = false;
#if defined (HAVE_HDF5_18)
        data_hid = H5Gcreate (group_hid, "symbol table",
                              octave_H5P_DEFAULT, octave_H5P_DEFAULT, octave_H5P_DEFAULT);
#else
        data_hid = H5Gcreate (group_hid, "symbol table", 0);
#endif
        if (data_hid < 0)
          {
            H5Sclose (space_hid);
            H5Tclose (type_hid);
            H5Gclose (group_hid);
            return false;
          }

        for (const auto& nm_val : m_local_vars)
          {
            if (! add_hdf5_data (data_hid, nm_val.second, nm_val.first,
                                 "", false, save_as_floats))
              break;
          }

        H5Gclose (data_hid);
      }

    H5Sclose (space_hid);
    H5Tclose (type_hid);
    H5Gclose (group_hid);

    return retval;

#else

    octave_unused_parameter (loc_id);
    octave_unused_parameter (name);
    octave_unused_parameter (save_as_floats);

    warn_save ("hdf5");

    return false;

#endif
  }

  bool base_anonymous_fcn_handle::load_hdf5 (octave_hdf5_id& group_hid,
                                             octave_hdf5_id& space_hid,
                                             octave_hdf5_id& type_hid)
  {
#if defined (HAVE_HDF5)

    bool success = true;

#if defined (HAVE_HDF5_18)
    octave_hdf5_id data_hid = H5Dopen (group_hid, "fcn", octave_H5P_DEFAULT);
#else
    octave_hdf5_id data_hid = H5Dopen (group_hid, "fcn");
#endif

    if (data_hid < 0)
      {
        H5Sclose (space_hid);
        H5Tclose (type_hid);
        H5Gclose (group_hid);
        return false;
      }

    H5Tclose (type_hid);
    type_hid = H5Dget_type (data_hid);
    octave_hdf5_id type_class_hid = H5Tget_class (type_hid);

    if (type_class_hid != H5T_STRING)
      {
        H5Sclose (space_hid);
        H5Tclose (type_hid);
        H5Dclose (data_hid);
        H5Gclose (group_hid);
        return false;
      }

    H5Sclose (space_hid);
    space_hid = H5Dget_space (data_hid);
    hsize_t rank = H5Sget_simple_extent_ndims (space_hid);

    if (rank != 0)
      {
        H5Sclose (space_hid);
        H5Tclose (type_hid);
        H5Dclose (data_hid);
        H5Gclose (group_hid);
        return false;
      }

    int slen = H5Tget_size (type_hid);
    if (slen < 0)
      {
        H5Sclose (space_hid);
        H5Tclose (type_hid);
        H5Dclose (data_hid);
        H5Gclose (group_hid);
        return false;
      }

    OCTAVE_LOCAL_BUFFER (char, fcn_tmp, slen);

    // create datatype for (null-terminated) string to read into:
    octave_hdf5_id st_id = H5Tcopy (H5T_C_S1);
    H5Tset_size (st_id, slen);

    if (H5Dread (data_hid, st_id, octave_H5S_ALL, octave_H5S_ALL,
                 octave_H5P_DEFAULT, fcn_tmp)
        < 0)
      {
        H5Tclose (st_id);
        H5Sclose (space_hid);
        H5Tclose (type_hid);
        H5Dclose (data_hid);
        H5Gclose (group_hid);
        return false;
      }
    H5Tclose (st_id);
    H5Dclose (data_hid);

    octave_idx_type len = 0;

    // we have to pull some shenanigans here to make sure
    // HDF5 doesn't print out all sorts of error messages if we
    // call H5Aopen for a non-existing attribute

    H5E_auto_t err_func;
    void *err_func_data;

    // turn off error reporting temporarily, but save the error
    // reporting function:
#if defined (HAVE_HDF5_18)
    H5Eget_auto (octave_H5E_DEFAULT, &err_func, &err_func_data);
    H5Eset_auto (octave_H5E_DEFAULT, nullptr, nullptr);
#else
    H5Eget_auto (&err_func, &err_func_data);
    H5Eset_auto (nullptr, nullptr);
#endif

    octave_hdf5_id attr_id = H5Aopen_name (group_hid, "SYMBOL_TABLE");

    if (attr_id >= 0)
      {
        if (H5Aread (attr_id, H5T_NATIVE_IDX, &len) < 0)
          success = false;

        H5Aclose (attr_id);
      }

    // restore error reporting:
#if defined (HAVE_HDF5_18)
    H5Eset_auto (octave_H5E_DEFAULT, err_func, err_func_data);
#else
    H5Eset_auto (err_func, err_func_data);
#endif

    // Set up temporary scope to use for evaluating the text that
    // defines the anonymous function.

    interpreter& interp
      = __get_interpreter__ ("base_anonymous_fcn_handle::load_hdf5");

    tree_evaluator& tw = interp.get_evaluator ();

    tw.push_dummy_scope (fcn_tmp);
    unwind_action_safe restore_scope (&tree_evaluator::pop_scope, &tw);

    if (len > 0 && success)
      {
        hsize_t num_obj = 0;
#if defined (HAVE_HDF5_18)
        data_hid = H5Gopen (group_hid, "symbol table", octave_H5P_DEFAULT);
#else
        data_hid = H5Gopen (group_hid, "symbol table");
#endif
        H5Gget_num_objs (data_hid, &num_obj);
        H5Gclose (data_hid);

        if (num_obj != static_cast<hsize_t> (len))
          error ("load: failed to load anonymous function handle");

        hdf5_callback_data dsub;
        int current_item = 0;
        for (octave_idx_type i = 0; i < len; i++)
          {
            if (hdf5_h5g_iterate (group_hid, "symbol table", &current_item,
                                  &dsub) <= 0)
              error ("load: failed to load anonymous function handle");

            m_local_vars[dsub.name] = dsub.tc;
          }
      }

    if (success)
      return parse (fcn_tmp);

    return false;

#else

    octave_unused_parameter (group_hid);
    octave_unused_parameter (space_hid);
    octave_unused_parameter (type_hid);

    return false;

#endif
  }

  void base_anonymous_fcn_handle::print_raw (std::ostream& os, bool, int) const
  {
    tree_print_code tpc (os);

    octave_user_function *f = m_fcn.user_function_value ();

    if (! f)
      error ("invalid anonymous function handle");

    os << "@";

    // The parameter list should always be valid for anonymous
    // functions, so we should always call accept for it, and it will
    // print the parens for us.

    tree_parameter_list *p = f->parameter_list ();

    if (p)
      p->accept (tpc);

    os << " ";

    tree_statement_list *b = f->body ();

    assert (b->length () == 1);

    tree_statement *s = b->front ();

    if (! s)
      error ("invalid anonymous function handle");

    assert (s->is_expression ());

    tree_expression *e = s->expression ();

    if (! e)
      error ("invalid anonymous function handle");

    tpc.print_fcn_handle_body (e);
  }

  bool base_anonymous_fcn_handle::parse (const std::string& fcn_text)
  {
    // FIXME: If evaluation of the string gives us an anonymous function
    // handle object, then why extract the function and create a new
    // anonymous function object?  Why not just attach the workspace
    // values to the object returned by eval_string?  This code is also is
    // duplicated in read_mat5_binary_element in ls-mat5.cc.

    interpreter& interp
      = __get_interpreter__ ("base_anonymous_fcn_handle::parse");

    // Set up temporary scope to use for evaluating the text that defines
    // the anonymous function so that we don't pick up values of random
    // variables that might be in the current scope.

    tree_evaluator& tw = interp.get_evaluator ();
    tw.push_dummy_scope ("read_mat5_binary_element");

    unwind_action act ([&tw] () { tw.pop_scope (); });

    int parse_status;
    octave_value anonymous_fcn_hdl
      = interp.eval_string (fcn_text, true, parse_status);

    if (parse_status != 0)
      return false;

    octave_fcn_handle *fh = anonymous_fcn_hdl.fcn_handle_value ();

    if (! fh)
      return false;

    m_fcn = fh->fcn_val ();

    octave_user_function *uf = m_fcn.user_function_value (true);

    if (uf)
      {
        symbol_scope uf_scope = uf->scope ();

        if (uf_scope)
          uf_scope.cache_name (m_name);
      }

    return true;
  }

  anonymous_fcn_handle::anonymous_fcn_handle (const octave_value& fcn,
                                              const stack_frame::local_vars_map& local_vars,
                                              const std::shared_ptr<stack_frame>& stack_context)
    : base_anonymous_fcn_handle (fcn, local_vars),
      m_stack_context (stack_context)
  { }

  octave_value anonymous_fcn_handle::make_weak_anonymous_handle (void) const
  {
    return octave_value (new octave_fcn_handle
                         (new weak_anonymous_fcn_handle (*this)));
  }

  octave_value_list
  anonymous_fcn_handle::call (int nargout, const octave_value_list& args)
  {
    tree_evaluator& tw = __get_evaluator__ ("anonymous_fcn_handle::call");

    octave_user_function *oct_usr_fcn = m_fcn.user_function_value ();

    tw.push_stack_frame (oct_usr_fcn, m_local_vars, m_stack_context);

    unwind_action act ([&tw] () { tw.pop_stack_frame (); });

    return oct_usr_fcn->execute (tw, nargout, args);
  }

  octave_value anonymous_fcn_handle::workspace (void) const
  {
    octave_scalar_map local_vars_map;

    for (const auto& nm_val : m_local_vars)
      local_vars_map.assign (nm_val.first, nm_val.second);

    // FIXME: it would be more convenient if stack_frame::workspace
    // returned a Cell object directly instead of a Cell in an
    // octave_value object.

    Cell cell_frames;

    if (m_stack_context)
      {
        octave_value ov_frames = m_stack_context->workspace ();
        cell_frames = ov_frames.cell_value ();
      }

    octave_idx_type num_frames = cell_frames.numel ();
    // FIXME: It seems there should be a simple way to concatenate cells...
    Cell retval = Cell (num_frames+1, 1);
    retval(0) = m_local_vars;
    for (octave_idx_type i = 0; i < num_frames; i++)
      retval(i+1) = cell_frames(i);

    return retval;
  }

  bool is_equal_to (const anonymous_fcn_handle& fh1,
                    const anonymous_fcn_handle& fh2)
  {
    if (fh1.m_fcn.is_defined () && fh2.m_fcn.is_defined ())
      return fh1.m_fcn.is_copy_of (fh2.m_fcn);
    else
      return false;
  }

  octave_value_list
  weak_anonymous_fcn_handle::call (int nargout, const octave_value_list& args)
  {
    tree_evaluator& tw = __get_evaluator__ ("anonymous_fcn_handle::call");

    octave_user_function *oct_usr_fcn = m_fcn.user_function_value ();

    std::shared_ptr<stack_frame> frames = m_stack_context.lock ();

    tw.push_stack_frame (oct_usr_fcn, m_local_vars, frames);

    unwind_action act ([&tw] () { tw.pop_stack_frame (); });

    return oct_usr_fcn->execute (tw, nargout, args);
  }

  octave_value weak_anonymous_fcn_handle::workspace (void) const
  {
    octave_scalar_map local_vars_map;

    for (const auto& nm_val : m_local_vars)
      local_vars_map.assign (nm_val.first, nm_val.second);

    // FIXME: it would be more convenient if stack_frame::workspace
    // returned a Cell object directly instead of a Cell in an
    // octave_value object.

    std::shared_ptr<stack_frame> frames = m_stack_context.lock ();

    Cell cell_frames;

    if (frames)
      {
        octave_value ov_frames = frames->workspace ();
        cell_frames = ov_frames.cell_value ();
      }

    octave_idx_type num_frames = cell_frames.numel ();

    // FIXME: It seems there should be a simple way to concatenate
    // cells...
    Cell retval = Cell (num_frames+1, 1);
    retval(0) = m_local_vars;
    for (octave_idx_type i = 0; i < num_frames; i++)
      retval(i+1) = cell_frames(i);

    return retval;
  }

  bool is_equal_to (const weak_anonymous_fcn_handle& fh1,
                    const weak_anonymous_fcn_handle& fh2)
  {
    if (fh1.m_name == fh2.m_name
        && fh1.m_fcn.is_defined () && fh2.m_fcn.is_defined ())
      return fh1.m_fcn.is_copy_of (fh2.m_fcn);
    else
      return false;
  }

OCTAVE_NAMESPACE_END

octave_fcn_handle::octave_fcn_handle (void)
  : octave_base_value (), m_rep (new octave::invalid_fcn_handle ())
{ }

octave_fcn_handle::octave_fcn_handle (const octave_value& fcn)
  : octave_base_value (), m_rep (new octave::internal_fcn_handle (fcn))
{ }

octave_fcn_handle::octave_fcn_handle (const std::string& name)
  : octave_base_value (), m_rep (new octave::simple_fcn_handle (name))
{ }

octave_fcn_handle::octave_fcn_handle (const octave_value& fcn,
                                      const std::string& name)
  : octave_base_value (), m_rep (new octave::simple_fcn_handle (fcn, name))
{ }

octave_fcn_handle::octave_fcn_handle (const std::string& class_nm,
                                      const std::string& meth_nm)
  : octave_base_value (),
    m_rep (new octave::class_simple_fcn_handle (class_nm, meth_nm))
{ }

octave_fcn_handle::octave_fcn_handle (const octave_value& fcn,
                                      const std::string& class_nm,
                                      const std::string& meth_nm)
  : octave_base_value (),
    m_rep (new octave::class_simple_fcn_handle (fcn, class_nm, meth_nm))
{ }

octave_fcn_handle::octave_fcn_handle (const octave_value& obj,
                                      const octave_value& fcn,
                                      const std::string& class_nm,
                                      const std::string& meth_nm)
  : octave_base_value (),
    m_rep (new octave::class_simple_fcn_handle (obj, fcn, class_nm, meth_nm))
{ }

octave_fcn_handle::octave_fcn_handle (const octave_value& fcn,
                                      const std::string& name,
                                      const std::list<std::string>& parentage)
  : octave_base_value (),
    m_rep (new octave::scoped_fcn_handle (fcn, name, parentage))
{ }

octave_fcn_handle::octave_fcn_handle (const octave_value& fcn,
                                      const std::string& name,
                                      const std::shared_ptr<octave::stack_frame>& stack_context)
  : octave_base_value (),
    m_rep (new octave::nested_fcn_handle (fcn, name, stack_context))
{ }

octave_fcn_handle::octave_fcn_handle (const octave_value& fcn,
                                      const octave::stack_frame::local_vars_map& local_vars,
                                      const std::shared_ptr<octave::stack_frame>& stack_context)
  : octave_base_value (),
    m_rep (new octave::anonymous_fcn_handle (fcn, local_vars, stack_context))
{ }

octave_fcn_handle::octave_fcn_handle (octave::base_fcn_handle *rep)
  : octave_base_value (), m_rep (rep)
{ }

octave_fcn_handle::octave_fcn_handle (const octave_fcn_handle& fh)
  : octave_base_value (fh)
{
  m_rep.reset (fh.m_rep->clone ());
}

dim_vector
octave_fcn_handle::dims (void) const
{
  static dim_vector dv (1, 1);
  return dv;
}

bool
octave_fcn_handle::save_ascii (std::ostream& os)
{
  return m_rep->save_ascii (os);
}

bool
octave_fcn_handle::load_ascii (std::istream& is)
{
  std::shared_ptr<octave::base_fcn_handle> new_rep;

  // Read enough to detect type then create new rep object and dispatch
  // to finish loading object.

  std::streampos pos = is.tellg ();

  std::string octaveroot = extract_keyword (is, "octaveroot", true);
  if (octaveroot.empty ())
    {
      is.seekg (pos);
      is.clear ();
    }

  pos = is.tellg ();

  std::string fpath = extract_keyword (is, "path", true);
  if (fpath.empty ())
    {
      is.seekg (pos);
      is.clear ();
    }

  if (! (octaveroot.empty () || fpath.empty ()))
    {
      std::size_t len = octaveroot.size ();
      if (octaveroot == fpath.substr (0, len))
        fpath = octave::config::octave_exec_home () + fpath.substr (len);
    }

  pos = is.tellg ();

  std::string subtype = extract_keyword (is, "subtype", true);
  if (subtype.empty ())
    {
      is.seekg (pos);
      is.clear ();

      // We have a legacy file that can contain either an anonymous
      // function or a simple function handle.

      std::string name;
      is >> name;

      if (name == anonymous)
        new_rep.reset (new octave::anonymous_fcn_handle ());
      else
        new_rep.reset (new octave::simple_fcn_handle (name, fpath, octaveroot));
    }
  else
    {
      // Load individual function handle types.

      if (subtype == "simple")
        {
          std::string name;
          is >> name;

          new_rep.reset (new octave::simple_fcn_handle (name, fpath, octaveroot));
        }
      else if (subtype == "scopedfunction")
        {
          std::string name;
          is >> name;

          new_rep.reset (new octave::scoped_fcn_handle (name, fpath, octaveroot));
        }
      else if (subtype == "anonymous")
        new_rep.reset (new octave::anonymous_fcn_handle ());
      else if (subtype == "nested")
        {
          std::string name;
          is >> name;

          new_rep.reset (new octave::nested_fcn_handle (name, fpath, octaveroot));
        }
      else if (subtype == "classsimple")
        {
          std::string name;
          is >> name;

          new_rep.reset (new octave::class_simple_fcn_handle (name, fpath, octaveroot));
        }
    }

  if (! new_rep)
    return false;

  if (! new_rep->load_ascii (is))
    return false;

  m_rep = new_rep;

  return true;
}

bool
octave_fcn_handle::save_binary (std::ostream& os, bool save_as_floats)
{
  return m_rep->save_binary (os, save_as_floats);
}

bool
octave_fcn_handle::load_binary (std::istream& is, bool swap,
                                octave::mach_info::float_format fmt)
{
  // Read enough to detect type then create new rep object and dispatch
  // to finish loading object.

  int32_t tmp;
  if (! is.read (reinterpret_cast<char *> (&tmp), 4))
    return false;
  if (swap)
    swap_bytes<4> (&tmp);

  OCTAVE_LOCAL_BUFFER (char, ctmp1, tmp+1);
  // is.get (ctmp1, tmp+1, 0); caused is.eof () to be true though
  // effectively not reading over file end
  is.read (ctmp1, tmp);
  ctmp1[tmp] = 0;
  std::string name (ctmp1);

  if (! is)
    return false;

  std::shared_ptr<octave::base_fcn_handle> new_rep;

  std::size_t anl = anonymous.length ();

  if (name.length () >= anl && name.substr (0, anl) == anonymous)
    {
      // Even with extra info stored in the function name, anonymous
      // functions look the same.  Note that NAME here may have the
      // number of local variables appended.  We decode that inside the
      // load_binary function.

      new_rep.reset (new octave::anonymous_fcn_handle (name));
    }
  else
    {
      // Unpack extra info stored with the function name and load
      // individual function handle types.
      // FIXME: is there a better way?

      std::string octaveroot;
      std::string fpath;
      std::string subtype = "simple";

      if (name.find_first_of ('\n') != std::string::npos)
        {
          std::size_t pos1 = name.find_first_of ('\n');
          std::size_t pos2 = name.find_first_of ('\n', pos1 + 1);
          octaveroot = name.substr (pos1 + 1, pos2 - pos1 - 1);
          fpath = name.substr (pos2 + 1);
          name = name.substr (0, pos1);
        }

      std::size_t pos1 = name.find ('@');
      if (pos1 != std::string::npos)
        {
          if (name[pos1+1] == '<')
            {
              std::size_t pos2 = name.find ('>', pos1 + 2);

              if (pos2 != std::string::npos)
                subtype = name.substr (pos1 + 2, pos2 - pos1 - 2);
            }

          name = name.substr (0, pos1);
        }

      // Anonymous should have been handled above so it is not in the
      // following list.

      if (subtype == "simple")
        new_rep.reset (new octave::simple_fcn_handle (name, fpath, octaveroot));
      else if (subtype == "scopedfunction")
        new_rep.reset (new octave::scoped_fcn_handle (name, fpath, octaveroot));
      else if (subtype == "nested")
        new_rep.reset (new octave::nested_fcn_handle (name, fpath, octaveroot));
      else if (subtype == "classsimple")
        new_rep.reset (new octave::class_simple_fcn_handle (name, fpath, octaveroot));
    }

  if (! new_rep)
    return false;

  if (! new_rep->load_binary (is, swap, fmt))
    return false;

  m_rep = new_rep;

  return true;
}

bool
octave_fcn_handle::save_hdf5 (octave_hdf5_id loc_id, const char *name,
                              bool save_as_floats)
{
  return m_rep->save_hdf5 (loc_id, name, save_as_floats);
}

bool
octave_fcn_handle::load_hdf5 (octave_hdf5_id loc_id, const char *name_arg)
{
#if defined (HAVE_HDF5)

#if defined (HAVE_HDF5_18)
  octave_hdf5_id group_hid = H5Gopen (loc_id, name_arg, octave_H5P_DEFAULT);
#else
  octave_hdf5_id group_hid = H5Gopen (loc_id, name_arg);
#endif
  if (group_hid < 0)
    return false;

#if defined (HAVE_HDF5_18)
  octave_hdf5_id data_hid = H5Dopen (group_hid, "nm", octave_H5P_DEFAULT);
#else
  octave_hdf5_id data_hid = H5Dopen (group_hid, "nm");
#endif

  if (data_hid < 0)
    {
      H5Gclose (group_hid);
      return false;
    }

  octave_hdf5_id type_hid = H5Dget_type (data_hid);
  octave_hdf5_id type_class_hid = H5Tget_class (type_hid);

  if (type_class_hid != H5T_STRING)
    {
      H5Tclose (type_hid);
      H5Dclose (data_hid);
      H5Gclose (group_hid);
      return false;
    }

  octave_hdf5_id space_hid = H5Dget_space (data_hid);
  hsize_t rank = H5Sget_simple_extent_ndims (space_hid);

  if (rank != 0)
    {
      H5Sclose (space_hid);
      H5Tclose (type_hid);
      H5Dclose (data_hid);
      H5Gclose (group_hid);
      return false;
    }

  int slen = H5Tget_size (type_hid);
  if (slen < 0)
    {
      H5Sclose (space_hid);
      H5Tclose (type_hid);
      H5Dclose (data_hid);
      H5Gclose (group_hid);
      return false;
    }

  OCTAVE_LOCAL_BUFFER (char, nm_tmp, slen);

  // create datatype for (null-terminated) string to read into:
  octave_hdf5_id st_id = H5Tcopy (H5T_C_S1);
  H5Tset_size (st_id, slen);

  if (H5Dread (data_hid, st_id, octave_H5S_ALL, octave_H5S_ALL,
               octave_H5P_DEFAULT, nm_tmp)
      < 0)
    {
      H5Tclose (st_id);
      H5Sclose (space_hid);
      H5Tclose (type_hid);
      H5Dclose (data_hid);
      H5Gclose (group_hid);
      return false;
    }
  H5Tclose (st_id);
  H5Dclose (data_hid);

  std::string name (nm_tmp);

  std::shared_ptr<octave::base_fcn_handle> new_rep;

  if (name == anonymous)
    {
      // Even with extra info stored in the function name, anonymous
      // functions look the same.

      new_rep.reset (new octave::anonymous_fcn_handle ());
    }
  else
    {
      // Unpack extra info stored with the function name and load
      // individual function handle types.
      // FIXME: is there a better way?

      std::string octaveroot;
      std::string fpath;
      std::string subtype = "simple";

      if (name.find_first_of ('\n') != std::string::npos)
        {
          std::size_t pos1 = name.find_first_of ('\n');
          std::size_t pos2 = name.find_first_of ('\n', pos1 + 1);
          octaveroot = name.substr (pos1 + 1, pos2 - pos1 - 1);
          fpath = name.substr (pos2 + 1);
          name = name.substr (0, pos1);
        }

      std::size_t pos1 = name.find ('@');
      if (pos1 != std::string::npos)
        {
          if (name[pos1+1] == '<')
            {
              std::size_t pos2 = name.find ('>', pos1 + 2);

              if (pos2 != std::string::npos)
                subtype = name.substr (pos1 + 2, pos2 - pos1 - 2);
            }

          name = name.substr (0, pos1);
        }

      // Anonymous should have been handled above so it is not in the
      // following list.

      if (subtype == "simple")
        new_rep.reset (new octave::simple_fcn_handle (name, fpath, octaveroot));
      else if (subtype == "scopedfunction")
        new_rep.reset (new octave::scoped_fcn_handle (name, fpath, octaveroot));
      else if (subtype == "nested")
        new_rep.reset (new octave::nested_fcn_handle (name, fpath, octaveroot));
      else if (subtype == "classsimple")
        new_rep.reset (new octave::class_simple_fcn_handle (name, fpath, octaveroot));
    }

  bool status = false;

  if (new_rep && new_rep->load_hdf5 (group_hid, space_hid, type_hid))
    {
      m_rep = new_rep;
      status = true;
    }

  // FIXME: manage these with an unwind_action object?

  H5Tclose (type_hid);
  H5Sclose (space_hid);
  H5Gclose (group_hid);

  return status;

#else

  octave_unused_parameter (loc_id);
  octave_unused_parameter (name_arg);

  warn_load ("hdf5");

  return false;

#endif
}

/*
%!test <*33857>
%! a = 2;
%! f = @(x) a + x;
%! g = @(x) 2 * x;
%! hm = @version;
%! hdld = @svd;
%! hbi = @log2;
%! f2 = f;
%! g2 = g;
%! hm2 = hm;
%! hdld2 = hdld;
%! hbi2 = hbi;
%! modes = {"-text", "-binary"};
%! if (isfield (__octave_config_info__, "HAVE_HDF5")
%!     && __octave_config_info__ ("HAVE_HDF5"))
%!   modes(end+1) = "-hdf5";
%! endif
%! for i = 1:numel (modes)
%!   mode = modes{i};
%!   nm = tempname ();
%!   unwind_protect
%!     f2 (1);
%!     save (mode, nm, "f2", "g2", "hm2", "hdld2", "hbi2");
%!     clear f2 g2 hm2 hdld2 hbi2
%!     load (nm);
%!     assert (f (2), f2 (2));
%!     assert (g (2), g2 (2));
%!     assert (g (3), g2 (3));
%!     unlink (nm);
%!     save (mode, nm, "f2", "g2", "hm2", "hdld2", "hbi2");
%!   unwind_protect_cleanup
%!     unlink (nm);
%!   end_unwind_protect
%! endfor
*/

/*
%!function fcn_handle_save_recurse (n, mode, nm, f2, g2, hm2, hdld2, hbi2)
%!  if (n == 0)
%!    save (mode, nm, "f2", "g2", "hm2", "hdld2", "hbi2");
%!  else
%!    fcn_handle_save_recurse (n - 1, mode, nm, f2, g2, hm2, hdld2, hbi2);
%!  endif
%!endfunction
%!function [f2, g2, hm2, hdld2, hbi2] = fcn_handle_load_recurse (n, nm)
%!  if (n == 0)
%!    load (nm);
%!  else
%!    [f2, g2, hm2, hdld2, hbi2] = fcn_handle_load_recurse (n - 1, nm);
%!  endif
%!endfunction

%!test <*35876>
%! a = 2;
%! f = @(x) a + x;
%! g = @(x) 2 * x;
%! hm = @version;
%! hdld = @svd;
%! hbi = @log2;
%! f2 = f;
%! g2 = g;
%! hm2 = hm;
%! hdld2 = hdld;
%! hbi2 = hbi;
%! modes = {"-text", "-binary"};
%! if (isfield (__octave_config_info__, "HAVE_HDF5")
%!     && __octave_config_info__ ("HAVE_HDF5"))
%!   modes(end+1) = "-hdf5";
%! endif
%! for i = 1:numel (modes)
%!   mode = modes{i};
%!   nm = tempname ();
%!   unwind_protect
%!     fcn_handle_save_recurse (2, mode, nm, f2, g2, hm2, hdld2, hbi2);
%!     clear f2 g2 hm2 hdld2 hbi2
%!     [f2, f2, hm2, hdld2, hbi2] = fcn_handle_load_recurse (2, nm);
%!     load (nm);
%!     assert (f (2), f2 (2));
%!     assert (g (2), g2 (2));
%!     assert (g (3), g2 (3));
%!     unlink (nm);
%!     fcn_handle_save_recurse (2, mode, nm, f2, g2, hm2, hdld2, hbi2);
%!   unwind_protect_cleanup
%!     unlink (nm);
%!   end_unwind_protect
%! endfor
*/

void
octave_fcn_handle::print (std::ostream& os, bool pr_as_read_syntax)
{
  print_raw (os, pr_as_read_syntax);
  newline (os);
}

void
octave_fcn_handle::print_raw (std::ostream& os, bool pr_as_read_syntax) const
{
  m_rep->print_raw (os, pr_as_read_syntax, current_print_indent_level ());
}

bool
is_equal_to (const octave_fcn_handle& fh1, const octave_fcn_handle& fh2)
{
  // FIXME: Maybe there is a better way?  Possibly by using typeid or
  // typeindex?

  // Don't include invalid_fcn_handle in the list of types to compare.
  // Consider them to be like NaN values so comparisons between any two
  // invalid handles are always false.

  if (fh1.is_internal () && fh2.is_internal ())
    return is_equal_to (*dynamic_cast<octave::internal_fcn_handle *> (fh1.get_rep ()),
                        *dynamic_cast<octave::internal_fcn_handle *> (fh2.get_rep ()));
  else if (fh1.is_simple () && fh2.is_simple ())
    return is_equal_to (*dynamic_cast<octave::simple_fcn_handle *> (fh1.get_rep ()),
                        *dynamic_cast<octave::simple_fcn_handle *> (fh2.get_rep ()));
  else if (fh1.is_scoped () && fh2.is_scoped ())
    return is_equal_to (*dynamic_cast<octave::scoped_fcn_handle *> (fh1.get_rep ()),
                        *dynamic_cast<octave::scoped_fcn_handle *> (fh2.get_rep ()));
  else if (fh1.is_nested () && fh2.is_nested ())
    return is_equal_to (*dynamic_cast<octave::nested_fcn_handle *> (fh1.get_rep ()),
                        *dynamic_cast<octave::nested_fcn_handle *> (fh2.get_rep ()));
  else if (fh1.is_class_simple () && fh2.is_class_simple ())
    return is_equal_to (*dynamic_cast<octave::class_simple_fcn_handle *> (fh1.get_rep ()),
                        *dynamic_cast<octave::class_simple_fcn_handle *> (fh2.get_rep ()));
  else if (fh1.is_anonymous () && fh2.is_anonymous ())
    return is_equal_to (*dynamic_cast<octave::anonymous_fcn_handle *> (fh1.get_rep ()),
                        *dynamic_cast<octave::anonymous_fcn_handle *> (fh2.get_rep ()));
  else
    return false;
}

OCTAVE_NAMESPACE_BEGIN

  // DEPRECATED in Octave 6.

  octave_value
  make_fcn_handle (interpreter& interp, const std::string& nm)
  {
    tree_evaluator& tw = interp.get_evaluator ();

    return tw.make_fcn_handle (nm);
  }

DEFUN (functions, args, ,
       doc: /* -*- texinfo -*-
@deftypefn {} {@var{s} =} functions (@var{fcn_handle})
Return a structure containing information about the function handle
@var{fcn_handle}.

The structure @var{s} always contains these three fields:

@table @asis
@item function
The function name.  For an anonymous function (no name) this will be the
actual function definition.

@item type
Type of the function.

@table @asis
@item anonymous
The function is anonymous.

@item private
The function is private.

@item overloaded
The function overloads an existing function.

@item simple
The function is a built-in or m-file function.

@item subfunction
The function is a subfunction within an m-file.
@end table

@item nested
The function is nested.

@item file
The m-file that will be called to perform the function.  This field is empty
for anonymous and built-in functions.
@end table

In addition, some function types may return more information in additional
fields.

@strong{Warning:} @code{functions} is provided for debugging purposes only.
Its behavior may change in the future and programs should not depend on any
particular output format.

@seealso{func2str, str2func}
@end deftypefn */)
{
  if (args.length () != 1)
    print_usage ();

  octave_fcn_handle *fh = args(0).xfcn_handle_value ("functions: FCN_HANDLE argument must be a function handle object");

  return ovl (fh->info ());
}

DEFUN (func2str, args, ,
       doc: /* -*- texinfo -*-
@deftypefn {} {} func2str (@var{fcn_handle})
Return a string containing the name of the function referenced by the
function handle @var{fcn_handle}.
@seealso{str2func, functions}
@end deftypefn */)
{
  if (args.length () != 1)
    print_usage ();

  octave_fcn_handle *fh = args(0).xfcn_handle_value ("func2str: FCN_HANDLE argument must be a function handle object");

  if (! fh)
    error ("func2str: FCN_HANDLE must be a valid function handle");

  octave_value retval;

  std::string fh_nm = fh->fcn_name ();

  if (fh->is_anonymous ())
    {
      std::ostringstream buf;

      fh->print_raw (buf);

      retval = buf.str ();
    }
  else
    retval = fh_nm;

  return retval;
}

DEFMETHOD (str2func, interp, args, ,
           doc: /* -*- texinfo -*-
@deftypefn {} {} str2func (@var{fcn_name})
Return a function handle constructed from the string @var{fcn_name}.

Previous versions of Octave accepted an optional second argument,
@qcode{"global"}, that caused str2func to ignore locally visible
functions.  This option is no longer supported.
@seealso{func2str, functions}
@end deftypefn */)
{
  int nargin = args.length ();

  if (nargin < 1 || nargin > 2)
    print_usage ();

  std::string nm
    = args(0).xstring_value ("str2func: FCN_NAME must be a string");

  if (nm.empty ())
    error ("str2func: invalid function name");

  if (nm[0] == '@')
    {
      // Unlike the anonymous_fcn_handle::parse method, don't set up
      // temporary scope to use for evaluating the text that defines
      // the anonymous function.  Here we want
      //
      //   str2fun ("@(args) expr")
      //
      // to behave the same as if
      //
      //   @(args) expr
      //
      // were evaluated in the current scope.

      int parse_status;
      octave_value afh = interp.eval_string (nm, true, parse_status);

      if (parse_status == 0)
        return afh;
    }
  else
    {
      if (nargin == 2)
        warning_with_id ("Octave:str2func-global-argument",
                         "str2func: second argument ignored");

      tree_evaluator& tw = interp.get_evaluator ();

      return tw.make_fcn_handle (nm);
    }

  return ovl ();
}

/*
%!test
%! f = str2func ("<");
%! assert (class (f), "function_handle");
%! assert (func2str (f), "lt");
%! assert (f (1, 2), true);
%! assert (f (2, 1), false);

%!test
%! f = str2func ("@(x) sin (x)");
%! assert (func2str (f), "@(x) sin (x)");
%! assert (f (0:3), sin (0:3));

%!error <FCN_NAME must be a string> str2func ({"sin"})
*/

/*
%!function y = __testrecursionfunc (f, x, n)
%!  if (nargin < 3)
%!    n = 0;
%!  endif
%!  if (n > 2)
%!    y = f (x);
%!  else
%!    n++;
%!    y = __testrecursionfunc (@(x) f (2*x), x, n);
%!  endif
%!endfunction
%!
%!assert (__testrecursionfunc (@(x) x, 1), 8)
*/

DEFUN (is_function_handle, args, ,
       doc: /* -*- texinfo -*-
@deftypefn {} {} is_function_handle (@var{x})
Return true if @var{x} is a function handle.
@seealso{isa, typeinfo, class, functions}
@end deftypefn */)
{
  if (args.length () != 1)
    print_usage ();

  return ovl (args(0).is_function_handle ());
}

/*
%!shared fh
%! fh = @(x) x;

%!assert (is_function_handle (fh))
%!assert (! is_function_handle ({fh}))
%!assert (! is_function_handle (1))

%!error is_function_handle ()
%!error is_function_handle (1, 2)
*/

/*
%!test
%! f = @(t) eval ('2*t');
%! assert (f (21), 42);
*/

/*
%!test <*58389>
%! s = "x";
%! a.(s) = [e, pi];
%! f = @(x) a.(s)(x);
%! assert (f(1), e);
%! assert (f(2), pi);
%! assert (f([2,1]), [pi, e]);
*/

/*
%!function r = __f (g, i)
%!  r = g(i);
%!endfunction
%!test
%! x = [1,2;3,4];
%! assert (__f (@(i) x(:,i), 1), [1;3]);
*/

OCTAVE_NAMESPACE_END