view libinterp/octave-value/cdef-class.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 a61e1a0f6024
children 83f9f8bda883
line wrap: on
line source

////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2012-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 <algorithm>
#include <iomanip>

#include "cdef-class.h"
#include "cdef-manager.h"
#include "cdef-method.h"
#include "cdef-package.h"
#include "cdef-property.h"
#include "cdef-utils.h"
#include "errwarn.h"
#include "interpreter-private.h"
#include "interpreter.h"
#include "load-path.h"
#include "ov-builtin.h"
#include "ov-classdef.h"
#include "ov-fcn-handle.h"
#include "ov-usr-fcn.h"
#include "parse.h"
#include "pt-assign.h"
#include "pt-classdef.h"
#include "pt-eval.h"
#include "pt-idx.h"
#include "pt-misc.h"
#include "pt-stmt.h"
#include "pt-walk.h"
#include "unwind-prot.h"

// Define to 1 to enable debugging statements.
#define DEBUG_TRACE 0
#if DEBUG_TRACE
#  include <iostream>
#endif

namespace octave
{
  static octave_value
  make_fcn_handle (const octave_value& fcn, const std::string& meth_name,
                   const std::string& class_name)
  {
    octave_value retval;

    if (fcn.is_defined ())
      {
        // FCN_HANDLE: METHOD
        octave_fcn_handle *fh
          = new octave_fcn_handle (fcn, class_name, meth_name);

        retval = octave_value (fh);
      }

    return retval;
  }

  cdef_class::cdef_class_rep::cdef_class_rep (const std::list<cdef_class>& superclasses)
    : cdef_meta_object_rep (), m_member_count (0), m_handle_class (false),
      m_meta (false)
  {
    put ("SuperClasses", to_ov (superclasses));
    m_implicit_ctor_list = superclasses;
  }

  cdef_method
  cdef_class::cdef_class_rep::find_method (const std::string& nm, bool local)
  {
    auto it = m_method_map.find (nm);

    if (it == m_method_map.end ())
      {
        // FIXME: look into class directory
      }
    else
      {
        cdef_method& meth = it->second;

        // FIXME: check if method reload needed

        if (meth.ok ())
          return meth;
      }

    if (! local)
      {
        // Look into superclasses

        Cell super_classes = get ("SuperClasses").cell_value ();

        for (int i = 0; i < super_classes.numel (); i++)
          {
            cdef_class cls = lookup_class (super_classes(i));

            cdef_method meth = cls.find_method (nm);

            if (meth.ok ())
              return meth;
          }
      }

    return cdef_method ();
  }

  class ctor_analyzer : public tree_walker
  {
  public:

    ctor_analyzer (void) = delete;

    ctor_analyzer (const std::string& ctor, const std::string& obj)
      : tree_walker (), m_who (ctor), m_obj_name (obj) { }

    ctor_analyzer (const ctor_analyzer&) = delete;

    ctor_analyzer& operator = (const ctor_analyzer&) = delete;

    ~ctor_analyzer (void) = default;

    void visit_statement (tree_statement& t)
    {
      if (t.is_expression ())
        t.expression ()->accept (*this);
    }

    void visit_simple_assignment (tree_simple_assignment& t)
    {
      t.right_hand_side ()->accept (*this);
    }

    void visit_multi_assignment (tree_multi_assignment& t)
    {
      t.right_hand_side ()->accept (*this);
    }

    void visit_index_expression (tree_index_expression& t)
    {
      t.expression ()->accept (*this);
    }

    std::list<cdef_class> get_constructor_list (void) const
    { return m_ctor_list; }

    // NO-OP

    void visit_anon_fcn_handle (tree_anon_fcn_handle&) { }
    void visit_argument_list (tree_argument_list&) { }
    void visit_binary_expression (tree_binary_expression&) { }
    void visit_break_command (tree_break_command&) { }
    void visit_colon_expression (tree_colon_expression&) { }
    void visit_continue_command (tree_continue_command&) { }
    void visit_decl_command (tree_decl_command&) { }
    void visit_decl_init_list (tree_decl_init_list&) { }
    void visit_decl_elt (tree_decl_elt&) { }
    void visit_simple_for_command (tree_simple_for_command&) { }
    void visit_complex_for_command (tree_complex_for_command&) { }
    void visit_octave_user_script (octave_user_script&) { }
    void visit_octave_user_function (octave_user_function&) { }
    void visit_function_def (tree_function_def&) { }
    void visit_identifier (tree_identifier&) { }
    void visit_if_clause (tree_if_clause&) { }
    void visit_if_command (tree_if_command&) { }
    void visit_if_command_list (tree_if_command_list&) { }
    void visit_switch_case (tree_switch_case&) { }
    void visit_switch_case_list (tree_switch_case_list&) { }
    void visit_switch_command (tree_switch_command&) { }
    void visit_matrix (tree_matrix&) { }
    void visit_cell (tree_cell&) { }
    void visit_no_op_command (tree_no_op_command&) { }
    void visit_constant (tree_constant&) { }
    void visit_fcn_handle (tree_fcn_handle&) { }
    void visit_parameter_list (tree_parameter_list&) { }
    void visit_postfix_expression (tree_postfix_expression&) { }
    void visit_prefix_expression (tree_prefix_expression&) { }
    void visit_return_command (tree_return_command&) { }
    void visit_try_catch_command (tree_try_catch_command&) { }
    void visit_unwind_protect_command (tree_unwind_protect_command&) { }
    void visit_while_command (tree_while_command&) { }
    void visit_do_until_command (tree_do_until_command&) { }

    void visit_superclass_ref (tree_superclass_ref& t)
    {
      if (t.method_name () == m_obj_name)
        {
          std::string class_name = t.class_name ();

          cdef_class cls = lookup_class (class_name, false);

          if (cls.ok ())
            m_ctor_list.push_back (cls);
        }
    }

  private:

    // The name of the constructor being analyzed.
    std::string m_who;

    // The name of the first output argument of the constructor.
    std::string m_obj_name;

    // The list of superclass constructors that are explicitly called.
    std::list<cdef_class> m_ctor_list;
  };

  void
  cdef_class::cdef_class_rep::install_method (const cdef_method& meth)
  {
    m_method_map[meth.get_name ()] = meth;

    m_member_count++;

    if (meth.is_constructor ())
      {
        // Analyze the constructor code to determine what superclass
        // constructors are called explicitly.

        octave_value ov_fcn = meth.get_function ();

        if (ov_fcn.is_defined ())
          {
            octave_user_function *uf = ov_fcn.user_function_value (true);

            if (uf)
              {
                tree_parameter_list *ret_list = uf->return_list ();
                tree_statement_list *body = uf->body ();

                if (! ret_list || ret_list->size () != 1)
                  error ("%s: invalid constructor output arguments",
                         meth.get_name ().c_str ());

                std::string m_obj_name = ret_list->front ()->name ();
                ctor_analyzer a (meth.get_name (), m_obj_name);

                body->accept (a);

                std::list<cdef_class> explicit_ctor_list
                  = a.get_constructor_list ();

                for (const auto& cdef_cls : explicit_ctor_list)
                  {
#if DEBUG_TRACE
                    std::cerr << "explicit superclass constructor: "
                              << cdef_cls.get_name () << std::endl;
#endif

                    m_implicit_ctor_list.remove (cdef_cls);
                  }
              }
          }
      }
  }

  void
  cdef_class::cdef_class_rep::load_all_methods (void)
  {
    // FIXME: re-scan class directory
  }

  Cell
  cdef_class::cdef_class_rep::get_methods (bool include_ctor)
  {
    std::map<std::string, cdef_method> meths;

    find_methods (meths, false, include_ctor);

    Cell c (meths.size (), 1);

    int idx = 0;

    for (const auto& nm_mthd : meths)
      c(idx++, 0) = to_ov (nm_mthd.second);

    return c;
  }

  std::map<std::string, cdef_method>
  cdef_class::cdef_class_rep::get_method_map (bool only_inherited,
                                              bool include_ctor)
  {
    std::map<std::string, cdef_method> methods;

    find_methods (methods, only_inherited, include_ctor);

    return methods;
  }

  void
  cdef_class::cdef_class_rep::find_methods (std::map<std::string, cdef_method>& meths,
                                            bool only_inherited,
                                            bool include_ctor)
  {
    load_all_methods ();

    method_const_iterator it;

    for (it = m_method_map.begin (); it != m_method_map.end (); ++it)
      {
        if (include_ctor || ! it->second.is_constructor ())
          {
            std::string nm = it->second.get_name ();

            if (meths.find (nm) == meths.end ())
              {
                if (only_inherited)
                  {
                    octave_value acc = it->second.get ("Access");

                    if (! acc.is_string ()
                        || acc.string_value () == "private")
                      continue;
                  }

                meths[nm] = it->second;
              }
          }
      }

    // Look into superclasses

    Cell super_classes = get ("SuperClasses").cell_value ();

    for (int i = 0; i < super_classes.numel (); i++)
      {
        cdef_class cls = lookup_class (super_classes(i));

        cls.get_rep ()->find_methods (meths, true, false);
      }
  }

  cdef_property
  cdef_class::cdef_class_rep::find_property (const std::string& nm)
  {
    auto it = m_property_map.find (nm);

    if (it != m_property_map.end ())
      {
        cdef_property& prop = it->second;

        if (prop.ok ())
          return prop;
      }

    // Look into superclasses

    Cell super_classes = get ("SuperClasses").cell_value ();

    for (int i = 0; i < super_classes.numel (); i++)
      {
        cdef_class cls = lookup_class (super_classes(i));

        cdef_property prop = cls.find_property (nm);

        if (prop.ok ())
          return prop;
      }

    return cdef_property ();
  }

  void
  cdef_class::cdef_class_rep::install_property (const cdef_property& prop)
  {
    m_property_map[prop.get_name ()] = prop;

    m_member_count++;
  }

  Cell
  cdef_class::cdef_class_rep::get_properties (int mode)
  {
    std::map<std::string, cdef_property> props;

    props = get_property_map (mode);

    Cell c (props.size (), 1);

    int idx = 0;

    for (const auto& pname_prop : props)
      c(idx++, 0) = to_ov (pname_prop.second);

    return c;
  }

  std::map<std::string, cdef_property>
  cdef_class::cdef_class_rep::get_property_map (int mode)
  {
    std::map<std::string, cdef_property> props;

    find_properties (props, mode);

    return props;
  }

  void
  cdef_class::cdef_class_rep::find_properties (std::map<std::string,
                                               cdef_property>& props,
                                               int mode)
  {
    property_const_iterator it;

    for (it = m_property_map.begin (); it != m_property_map.end (); ++it)
      {
        std::string nm = it->second.get_name ();

        if (props.find (nm) == props.end ())
          {
            if (mode == property_inherited)
              {
                octave_value acc = it->second.get ("GetAccess");

                if (! acc.is_string ()
                    || acc.string_value () == "private")
                  continue;
              }

            props[nm] = it->second;
          }
      }

    // Look into superclasses

    Cell super_classes = get ("SuperClasses").cell_value ();

    for (int i = 0; i < super_classes.numel (); i++)
      {
        cdef_class cls = lookup_class (super_classes(i));

        cls.get_rep ()->find_properties (props,
                                         (mode == property_all
                                          ? property_all
                                          : property_inherited));
      }
  }

  void
  cdef_class::cdef_class_rep::find_names (std::set<std::string>& names,
                                          bool all)
  {
    load_all_methods ();

    for (const auto& cls_fnmap : m_method_map)
      {
        if (! cls_fnmap.second.is_constructor ())
          {
            std::string nm = cls_fnmap.second.get_name ();

            if (! all)
              {
                octave_value acc = cls_fnmap.second.get ("Access");

                if (! acc.is_string()
                    || acc.string_value () != "public")
                  continue;
              }

            names.insert (nm);
          }
      }

    for (const auto& pname_prop : m_property_map)
      {
        std::string nm = pname_prop.second.get_name ();

        if (! all)
          {
            octave_value acc = pname_prop.second.get ("GetAccess");

            if (! acc.is_string()
                || acc.string_value () != "public")
              continue;
          }

        names.insert (nm);
      }

    // Look into superclasses

    Cell super_classes = get ("SuperClasses").cell_value ();

    for (int i = 0; i < super_classes.numel (); i++)
      {
        cdef_class cls = lookup_class (super_classes(i));

        cls.get_rep ()->find_names (names, all);
      }
  }

  string_vector
  cdef_class::cdef_class_rep::get_names (void)
  {
    std::set<std::string> names;

    find_names (names, false);

    string_vector v (names);

    return v.sort (true);
  }

  void
  cdef_class::cdef_class_rep::delete_object (const cdef_object& obj)
  {
    cdef_method dtor = find_method ("delete");

    // FIXME: would it be better to tell find_method above to not find
    // overloaded functions?

    if (dtor.ok () && dtor.is_defined_in_class (get_name ()))
      dtor.execute (obj, octave_value_list (), 0, true, "destructor");

    // FIXME: should we destroy corresponding properties here?

    // Call "delete" in super classes

    Cell super_classes = get ("SuperClasses").cell_value ();

    for (int i = 0; i < super_classes.numel (); i++)
      {
        cdef_class cls = lookup_class (super_classes(i));

        if (cls.get_name () != "handle")
          cls.delete_object (obj);
      }
  }

  octave_value_list
  cdef_class::cdef_class_rep::meta_subsref (const std::string& type,
                                            const std::list<octave_value_list>& idx,
                                            int nargout)
  {
    std::size_t skip = 1;

    octave_value_list retval;

    switch (type[0])
      {
      case '(':
        // Constructor call

#if DEBUG_TRACE
        std::cerr << "constructor" << std::endl;
#endif

        retval(0) = construct (idx.front ());
        break;

      case '.':
        {
          // Static method, constant (or property?)

#if DEBUG_TRACE
          std::cerr << "static method/property" << std::endl;
#endif

          if (idx.front ().length () != 1)
            error ("invalid meta.class indexing");

          std::string nm = idx.front ()(0).xstring_value ("invalid meta.class indexing, expected a method or property name");

          cdef_method meth = find_method (nm);

          if (meth.ok ())
            {
              if (! meth.is_static ())
                error ("method '%s' is not static", nm.c_str ());

              octave_value_list args;

              if (type.length () > 1 && idx.size () > 1 && type[1] == '(')
                {
                  args = *(++(idx.begin ()));
                  skip++;
                }

              retval = meth.execute (args, (type.length () > skip
                                            ? 1 : nargout), true,
                                     "meta.class");
            }
          else
            {
              cdef_property prop = find_property (nm);

              if (! prop.ok ())
                error ("no such method or property '%s'", nm.c_str ());

              if (! prop.is_constant ())
                error ("property '%s' is not constant", nm.c_str ());

              retval(0) = prop.get_value (true, "meta.class");
            }
        }
        break;

      default:
        error ("invalid meta.class indexing");
        break;
      }

    if (type.length () > skip && idx.size () > skip && ! retval.empty ())
      retval = retval(0).next_subsref (nargout, type, idx, skip);

    return retval;
  }

  void
  cdef_class::cdef_class_rep::meta_release (void)
  {
    cdef_manager& cdm
      = __get_cdef_manager__ ("cdef_class::cdef_class_rep::meta_release");

    cdm.unregister_class (wrap ());
  }

  void
  cdef_class::cdef_class_rep::initialize_object (cdef_object& obj)
  {
    // Populate the object with default property values

    std::list<cdef_class> super_classes
      = lookup_classes (get ("SuperClasses").cell_value ());

    for (auto& cls : super_classes)
      cls.initialize_object (obj);

    for (const auto& pname_prop : m_property_map)
      {
        if (! pname_prop.second.get ("Dependent").bool_value ())
          {
            octave_value pvalue = pname_prop.second.get ("DefaultValue");

            if (pvalue.is_defined ())
              obj.put (pname_prop.first, pvalue);
            else
              obj.put (pname_prop.first, octave_value (Matrix ()));
          }
      }

    m_count++;
    obj.mark_for_construction (cdef_class (this));
  }

  void
  cdef_class::cdef_class_rep::run_constructor (cdef_object& obj,
                                               const octave_value_list& args)
  {
    octave_value_list empty_args;

    for (const auto& cls : m_implicit_ctor_list)
      {
        cdef_class supcls = lookup_class (cls);

        supcls.run_constructor (obj, empty_args);
      }

    std::string cls_name = get_name ();
    std::string ctor_name = get_base_name (cls_name);

    cdef_method ctor = find_method (ctor_name);

    if (ctor.ok ())
      {
        octave_value_list ctor_args (args);
        octave_value_list ctor_retval;

        ctor_args.prepend (to_ov (obj));
        ctor_retval = ctor.execute (ctor_args, 1, true, "constructor");

        if (ctor_retval.length () != 1)
          error ("%s: invalid number of output arguments for classdef constructor",
                 ctor_name.c_str ());

        obj = to_cdef (ctor_retval(0));
      }

    obj.mark_as_constructed (wrap ());
  }

  octave_value
  cdef_class::cdef_class_rep::get_method (const std::string& name) const
  {
    auto p = m_method_map.find (name);

    if (p == m_method_map.end ())
      return octave_value ();

    return p->second.get_function ();
  }


  octave_value
  cdef_class::cdef_class_rep::construct (const octave_value_list& args)
  {
    cdef_object obj = construct_object (args);

    if (obj.ok ())
      return to_ov (obj);

    return octave_value ();
  }

  cdef_object
  cdef_class::cdef_class_rep::construct_object (const octave_value_list& args)
  {
    if (is_abstract ())
      error ("cannot instantiate object for abstract class '%s'",
             get_name ().c_str ());

    cdef_object obj;

    if (is_meta_class ())
      {
        // This code path is only used to create empty meta objects
        // as filler for the empty values within a meta object array.

        cdef_class this_cls = wrap ();

        static cdef_object empty_class;

        cdef_manager& cdm
          = __get_cdef_manager__ ("cdef_class::cdef_class_rep::construct_object");

        if (this_cls == cdm.meta_class ())
          {
            if (! empty_class.ok ())
              empty_class = cdm.make_class ("", std::list<cdef_class> ());
            obj = empty_class;
          }
        else if (this_cls == cdm.meta_property ())
          {
            static cdef_property empty_property;

            if (! empty_class.ok ())
              empty_class = cdm.make_class ("", std::list<cdef_class> ());
            if (! empty_property.ok ())
              empty_property = cdm.make_property (empty_class, "");
            obj = empty_property;
          }
        else if (this_cls == cdm.meta_method ())
          {
            static cdef_method empty_method;

            if (! empty_class.ok ())
              empty_class = cdm.make_class ("", std::list<cdef_class> ());
            if (! empty_method.ok ())
              empty_method = cdm.make_method (empty_class, "", octave_value ());
            obj = empty_method;
          }
        else if (this_cls == cdm.meta_package ())
          {
            static cdef_package empty_package;

            if (! empty_package.ok ())
              empty_package = cdm.make_package ("");
            obj = empty_package;
          }
        else
          panic_impossible ();

        return obj;
      }
    else
      {
        if (is_handle_class ())
          obj = cdef_object (new handle_cdef_object ());
        else
          obj = cdef_object (new value_cdef_object ());
        obj.set_class (wrap ());

        initialize_object (obj);

        run_constructor (obj, args);

        return obj;
      }

    return cdef_object ();
  }

  static octave_value
  compute_attribute_value (tree_evaluator& tw,
                           tree_classdef_attribute *t)
  {
    tree_expression *expr = t->expression ();

    if (expr)
      {
        if (expr->is_identifier ())
          {
            std::string s = expr->name ();

            if (s == "public")
              return std::string ("public");
            else if (s == "protected")
              return std::string ("protected");
            else if (s == "private")
              return std::string ("private");
          }

        return expr->evaluate (tw);
      }
    else
      return octave_value (true);
  }

  template <typename T>
  static std::string
  attribute_value_to_string (T *t, octave_value v)
  {
    if (v.is_string ())
      return v.string_value ();
    else if (t->expression ())
      return t->expression ()->original_text ();
    else
      return "true";
  }

  cdef_class
  cdef_class::make_meta_class (interpreter& interp,
                               tree_classdef *t, bool is_at_folder)
  {
    cdef_class retval;

    // Class creation

    std::string class_name = t->ident ()->name ();
    std::string full_class_name = class_name;
    if (! t->package_name ().empty ())
      full_class_name = t->package_name () + '.' + full_class_name;

#if DEBUG_TRACE
    std::cerr << "class: " << full_class_name << std::endl;
#endif

    // Push a dummy scope frame on the call stack that corresponds to
    // the scope that was used when parsing classdef object.  Without
    // this, we may pick up stray values from the current scope when
    // evaluating expressions found in things like attribute lists.

    tree_evaluator& tw = interp.get_evaluator ();

    tw.push_dummy_scope (full_class_name);
    unwind_action pop_scope (&tree_evaluator::pop_scope, &tw);

    std::list<cdef_class> slist;

    if (t->superclass_list ())
      {
        for (auto& scls : (*t->superclass_list ()))
          {
            std::string sclass_name = (scls)->class_name ();

#if DEBUG_TRACE
            std::cerr << "superclass: " << sclass_name << std::endl;
#endif

            cdef_class sclass = lookup_class (sclass_name);

            if (sclass.get ("Sealed").bool_value ())
              error ("'%s' cannot inherit from '%s', because it is sealed",
                     full_class_name.c_str (), sclass_name.c_str ());

            slist.push_back (sclass);
          }
      }

    cdef_manager& cdm = __get_cdef_manager__ ("cdef_class::make_meta_class");

    retval = cdm.make_class (full_class_name, slist);

    retval.doc_string (t->doc_string ());
    retval.file_name (t->file_name ());

    // Package owning this class

    if (! t->package_name ().empty ())
      {
        cdef_package pack = cdm.find_package (t->package_name ());

        if (pack.ok ())
          retval.put ("ContainingPackage", to_ov (pack));
      }

    // FIXME: instead of attaching attributes here, pass them to
    // cdef_manager::make_method.  The classdef manager contains a meta
    // object with a list of all valid properties that can be used to
    // validate the attribute list (see bug #60593).

    // Class attributes

    if (t->attribute_list ())
      {
        for (const auto& attr : (*t->attribute_list ()))
          {
            std::string aname = attr->ident ()->name ();
            octave_value avalue = compute_attribute_value (tw, attr);

#if DEBUG_TRACE
            std::cerr << "class attribute: " << aname << " = "
                      << attribute_value_to_string (attr, avalue) << std::endl;
#endif

            retval.put (aname, avalue);
          }
      }

    tree_classdef_body *b = t->body ();

    if (b)
      {
        // Keep track of the get/set accessor methods.  They will be used
        // later on when creating properties.

        std::map<std::string, octave_value> get_methods;
        std::map<std::string, octave_value> set_methods;

        // Method blocks

        std::list<tree_classdef_methods_block *> mb_list = b->methods_list ();

        load_path& lp = interp.get_load_path ();

        for (auto& mb_p : mb_list)
          {
            std::map<std::string, octave_value> amap;

#if DEBUG_TRACE
            std::cerr << "method block" << std::endl;
#endif

            // Method attributes

            if (mb_p->attribute_list ())
              {
                for (auto& attr_p : *mb_p->attribute_list ())
                  {
                    std::string aname = attr_p->ident ()->name ();
                    octave_value avalue = compute_attribute_value (tw, attr_p);

#if DEBUG_TRACE
                    std::cerr << "method attribute: " << aname << " = "
                              << attribute_value_to_string (attr_p, avalue)
                              << std::endl;
#endif

                    amap[aname] = avalue;
                  }
              }

            // Methods

            if (mb_p->element_list ())
              {
                for (auto& mtd : *mb_p->element_list ())
                  {
                    std::string mname = mtd.function_value ()->name ();
                    std::string mprefix = mname.substr (0, 4);

                    if (mprefix == "get.")
                      get_methods[mname.substr (4)]
                        = make_fcn_handle (mtd, mname, full_class_name);
                    else if (mprefix == "set.")
                      set_methods[mname.substr (4)]
                        = make_fcn_handle (mtd, mname, full_class_name);
                    else
                      {
                        cdef_method meth = cdm.make_method (retval, mname, mtd);

#if DEBUG_TRACE
                        std::cerr << (mname == class_name ? "constructor"
                                      : "method")
                                  << ": " << mname << std::endl;
#endif

                        // FIXME: instead of attaching attributes here,
                        // pass them to cdef_manager::make_method.  The
                        // classdef manager contains a meta object with
                        // a list of all valid properties that can be
                        // used to validate the attribute list (see bug
                        // #60593).

                        for (auto& attrnm_val : amap)
                          meth.put (attrnm_val.first, attrnm_val.second);

                        retval.install_method (meth);
                      }
                  }
              }
          }

        if (is_at_folder)
          {
            // Look for all external methods visible on octave path at the
            // time of loading of the class.
            //
            // FIXME: This is an "extension" to Matlab behavior, which only looks
            // in the @-folder containing the original classdef file.  However,
            // this is easier to implement it that way at the moment.

            std::list<std::string> external_methods
              = lp.methods (full_class_name);

            for (const auto& mtdnm : external_methods)
              {
                // FIXME: should we issue a warning if the method is already
                // defined in the classdef file?

                if (mtdnm != class_name
                    && ! retval.find_method (mtdnm, true).ok ())
                  {
                    // Create a dummy method that is used until the actual
                    // method is loaded.
                    octave_user_function *fcn = new octave_user_function ();

                    fcn->stash_function_name (mtdnm);

                    cdef_method meth
                      = cdm.make_method (retval, mtdnm, octave_value (fcn));

                    retval.install_method (meth);
                  }
              }
          }

        // Property blocks

        // FIXME: default property expression should be able to call static
        //        methods of the class being constructed.  A restricted CLASSNAME
        //        symbol should be added to the scope before evaluating default
        //        value expressions.

        std::list<tree_classdef_properties_block *> pb_list
          = b->properties_list ();

        for (auto& pb_p : pb_list)
          {
            std::map<std::string, octave_value> amap;

#if DEBUG_TRACE
            std::cerr << "property block" << std::endl;
#endif

            // Property attributes

            if (pb_p->attribute_list ())
              {
                for (auto& attr_p : *pb_p->attribute_list ())
                  {
                    std::string aname = attr_p->ident ()->name ();
                    octave_value avalue = compute_attribute_value (tw, attr_p);

#if DEBUG_TRACE
                    std::cerr << "property attribute: " << aname << " = "
                              << attribute_value_to_string (attr_p, avalue)
                              << std::endl;
#endif

                    if (aname == "Access")
                      {
                        amap["GetAccess"] = avalue;
                        amap["SetAccess"] = avalue;
                      }
                    else
                      amap[aname] = avalue;
                  }
              }

            // Properties

            if (pb_p->element_list ())
              {
                for (auto& prop_p : *pb_p->element_list ())
                  {
                    std::string prop_name = prop_p->ident ()->name ();

                    cdef_property prop = cdm.make_property (retval, prop_name);

#if DEBUG_TRACE
                    std::cerr << "property: " << prop_p->ident ()->name ()
                              << std::endl;
#endif

                    tree_expression *expr = prop_p->expression ();
                    if (expr)
                      {
                        octave_value pvalue = expr->evaluate (tw);

#if DEBUG_TRACE
                        std::cerr << "property default: "
                                  << attribute_value_to_string (prop_p, pvalue)
                                  << std::endl;
#endif

                        prop.put ("DefaultValue", pvalue);
                      }

                    // FIXME: instead of attaching attributes here, pass
                    // them to cdef_manager::make_property.  The
                    // classdef manager contains a meta object with a
                    // list of all valid properties that can be used to
                    // validate the attribute list (see bug #60593).

                    // Install property attributes.  This is done before assigning
                    // the property accessors so we can do validation by using
                    // cdef_property methods.

                    for (auto& attrnm_val : amap)
                      prop.put (attrnm_val.first, attrnm_val.second);

                    // Install property access methods, if any.  Remove the
                    // accessor methods from the temporary storage map, so we can
                    // detect which ones are invalid and do not correspond to a
                    // defined property.

                    auto git = get_methods.find (prop_name);

                    if (git != get_methods.end ())
                      {
                        make_function_of_class (retval, git->second);
                        prop.put ("GetMethod", git->second);
                        get_methods.erase (git);
                      }

                    auto sit = set_methods.find (prop_name);

                    if (sit != set_methods.end ())
                      {
                        make_function_of_class (retval, sit->second);
                        prop.put ("SetMethod", sit->second);
                        set_methods.erase (sit);
                      }

                    retval.install_property (prop);
                  }
              }
          }
      }

    return retval;
  }

  octave_value
  cdef_class::get_method_function (const std::string& /* nm */)
  {
    return octave_value (new octave_classdef_meta (*this));
  }
}