view libinterp/octave-value/ov-classdef.cc @ 25221:22ece7843499

Allow a single metaclass for Access property of classdef objects (bug #53601) * ov-classdef.cc (check_access): Add an additional elseif to input parsing to look for an object (metaclass) as input. If found, verify that the metaclass is either the same as the current class or is a superclass of the object.
author Rik <rik@octave.org>
date Wed, 11 Apr 2018 16:45:53 -0700
parents 6652d3823428
children 2ad00275b79b
line wrap: on
line source

/*

Copyright (C) 2012-2018 Michael Goffioul

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 "call-stack.h"
#include "defun.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-typeinfo.h"
#include "ov-usr-fcn.h"
#include "pt-assign.h"
#include "pt-classdef.h"
#include "pt-eval.h"
#include "pt-funcall.h"
#include "pt-idx.h"
#include "pt-misc.h"
#include "pt-stmt.h"
#include "pt-walk.h"
#include "symtab.h"

// Define to 1 to enable debugging statements.
#define DEBUG_TRACE 0

OCTAVE_NORETURN static
void
err_method_access (const std::string& from, const cdef_method& meth)
{
  octave_value acc = meth.get ("Access");
  std::string acc_s;

  if (acc.is_string ())
    acc_s = acc.string_value ();
  else
    acc_s = "class-restricted";

  error ("%s: method `%s' has %s access and cannot be run in this context",
         from.c_str (), meth.get_name ().c_str (), acc_s.c_str ());
}

OCTAVE_NORETURN static
void
err_property_access (const std::string& from, const cdef_property& prop,
                     bool is_set = false)
{
  octave_value acc = (prop.get (is_set ? "SetAccess" : "GetAccess"));
  std::string acc_s;

  if (acc.is_string ())
    acc_s = acc.string_value ();
  else
    acc_s = "class-restricted";

  if (is_set)
    error ("%s: property `%s' has %s access and cannot be set in this context",
           from.c_str (), prop.get_name ().c_str (), acc_s.c_str ());
  else
    error ("%s: property `%s' has %s access and cannot be obtained in this context",
           from.c_str (), prop.get_name ().c_str (), acc_s.c_str ());
}

static std::string
get_base_name (const std::string& nm)
{
  std::string::size_type pos = nm.find_last_of ('.');

  if (pos != std::string::npos)
    return nm.substr (pos + 1);

  return nm;
}

static void
make_function_of_class (const std::string& class_name,
                        const octave_value& fcn)
{
  octave_function *of = fcn.function_value ();

  of->stash_dispatch_class (class_name);

  octave_user_function *uf = of->user_function_value (true);

  if (uf)
    {
      if (get_base_name (class_name) == uf->name ())
        {
          uf->mark_as_class_constructor ();
          uf->mark_as_classdef_constructor ();
        }
      else
        uf->mark_as_class_method ();
    }
}

static void
make_function_of_class (const cdef_class& cls, const octave_value& fcn)
{
  make_function_of_class (cls.get_name (), fcn);
}

static octave_value
make_fcn_handle (octave_builtin::fcn ff, const std::string& nm)
{
  octave_value fcn (new octave_builtin (ff, nm));

  octave_value fcn_handle (new octave_fcn_handle (fcn, nm));

  return fcn_handle;
}

static octave_value
make_fcn_handle (const octave_value& fcn, const std::string& nm)
{
  octave_value retval;

  if (fcn.is_defined ())
    retval = octave_value (new octave_fcn_handle (fcn, nm));

  return retval;
}

static cdef_class
lookup_class (const std::string& name, bool error_if_not_found = true,
              bool load_if_not_found = true)
{
  cdef_manager& cdm = octave::__get_cdef_manager__ ("lookup_class");

  return cdm.find_class (name, error_if_not_found, load_if_not_found);
}

static cdef_class
lookup_class (const cdef_class& cls)
{
  // FIXME: placeholder for the time being, the purpose
  //        is to centralized any class update activity here.

  return cls;
}

static cdef_class
lookup_class (const octave_value& ov)
{
  if (ov.is_string())
    return lookup_class (ov.string_value ());
  else
    {
      cdef_class cls (to_cdef (ov));

      return lookup_class (cls);
    }

  return cdef_class ();
}

static std::list<cdef_class>
lookup_classes (const Cell& cls_list)
{
  std::list<cdef_class> retval;

  for (int i = 0; i < cls_list.numel (); i++)
    {
      cdef_class c = lookup_class (cls_list(i));

      retval.push_back (c);
    }

  return retval;
}

static octave_value
to_ov (const std::list<cdef_class>& class_list)
{
  Cell cls (class_list.size (), 1);
  int i = 0;

  for (const auto& cdef_cls : class_list)
    cls(i++) = to_ov (cdef_cls);

  return octave_value (cls);
}

static bool
is_superclass (const cdef_class& clsa, const cdef_class& clsb,
               bool allow_equal = true, int max_depth = -1)
{
  bool retval = false;

  if (allow_equal && clsa == clsb)
    retval = true;
  else if (max_depth != 0)
    {
      Cell c = clsb.get ("SuperClasses").cell_value ();

      for (int i = 0; ! retval && i < c.numel (); i++)
        {
          cdef_class cls = lookup_class (c(i));

          retval = is_superclass (clsa, cls, true,
                                  max_depth < 0 ? max_depth : max_depth-1);
        }
    }

  return retval;
}

inline bool
is_strict_superclass (const cdef_class& clsa, const cdef_class& clsb)
{ return is_superclass (clsa, clsb, false); }

inline bool
is_direct_superclass (const cdef_class& clsa, const cdef_class& clsb)
{ return is_superclass (clsa, clsb, false, 1); }

static octave_value_list
class_get_properties (const octave_value_list& args, int /* nargout */)
{
  octave_value_list retval;

  if (args.length () == 1 && args(0).type_name () == "object")
    {
      cdef_class cls (to_cdef (args(0)));

      retval(0) = cls.get_properties ();
    }

  return retval;
}

static cdef_class
get_class_context (std::string& name, bool& in_constructor)
{
  cdef_class cls;

  octave::call_stack& cs = octave::__get_call_stack__ ("get_class_context");

  octave_function *fcn = cs.current ();

  in_constructor = false;

  if (fcn && (fcn->is_class_method ()
              || fcn->is_classdef_constructor ()
              || fcn->is_anonymous_function_of_class ()
              || (fcn->is_private_function ()
                  && ! fcn->dispatch_class ().empty ())))
    {
      cls = lookup_class (fcn->dispatch_class ());

      name = fcn->name ();
      in_constructor = fcn->is_classdef_constructor ();
    }

  return cls;
}

inline cdef_class
get_class_context (void)
{
  std::string dummy_string;
  bool dummy_bool;

  return get_class_context (dummy_string, dummy_bool);
}

static bool
in_class_method (const cdef_class& cls)
{
  cdef_class ctx = get_class_context ();

  return (ctx.ok () && is_superclass (ctx, cls));
}

static bool
check_access (const cdef_class& cls, const octave_value& acc,
              const std::string& meth_name = "",
              const std::string& prop_name = "",
              bool is_prop_set = false)
{
  if (acc.is_string ())
    {
      std::string acc_s = acc.string_value ();

      if (acc_s == "public")
        return true;

      cdef_class ctx = get_class_context ();

      // The access is private or protected, this requires a
      // valid class context.

      if (ctx.ok ())
        {
          if (acc_s == "private")
            return (ctx == cls);
          else if (acc_s == "protected")
            {
              if (is_superclass (cls, ctx))
                // Calling a protected method in a superclass.
                return true;
              else if (is_strict_superclass (ctx, cls))
                {
                  // Calling a protected method or property in a derived class.
                  // This is only allowed if the context class knows about it
                  // and has access to it.

                  if (! meth_name.empty ())
                    {
                      cdef_method m = ctx.find_method (meth_name);

                      if (m.ok ())
                        return check_access (ctx, m.get ("Access"), meth_name);

                      return false;
                    }
                  else if (! prop_name.empty ())
                    {
                      cdef_property p = ctx.find_property (prop_name);

                      if (p.ok ())
                        {
                          octave_value p_access = p.get (is_prop_set ?
                                                         "SetAccess" :
                                                         "GetAccess");

                          return check_access (ctx, p_access, meth_name,
                                               prop_name, is_prop_set);
                        }

                      return false;
                    }
                  else
                    panic_impossible ();
                }

              return false;
            }
          else
            panic_impossible ();
        }
    }
  else if (acc.isobject ())
    {
      cdef_class ctx = get_class_context ();

      // At this point, a class context is always required.
      if (ctx.ok ())
        {
          if (ctx == cls)
            return true;

          cdef_class acc_cls (to_cdef (acc));

          if (is_superclass (acc_cls, ctx))
            return true;
        }
    }
  else if (acc.iscell ())
    {
      Cell acc_c = acc.cell_value ();

      cdef_class ctx = get_class_context ();

      // At this point, a class context is always required.

      if (ctx.ok ())
        {
          if (ctx == cls)
            return true;

          for (int i = 0; i < acc.numel (); i++)
            {
              cdef_class acc_cls (to_cdef (acc_c(i)));

              if (is_superclass (acc_cls, ctx))
                return true;
            }
        }
    }
  else
    error ("invalid property/method access in class `%s'",
           cls.get_name ().c_str ());

  return false;
}

static bool
is_dummy_method (const octave_value& fcn)
{
  bool retval = false;

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

          if (! uf || ! uf->body ())
            retval = true;
        }
    }
  else
    retval = true;

  return retval;
}

static bool
is_method_executing (const octave_value& ov, const cdef_object& obj)
{
  octave::tree_evaluator& tw
    = octave::__get_evaluator__ ("is_method_executing");

  octave::call_stack& cs = octave::__get_call_stack__ ("is_method_executing");

  octave_function *stack_fcn = cs.current ();

  octave_function *method_fcn = ov.function_value (true);

  // Does the top of the call stack match our target function?

  if (stack_fcn && stack_fcn == method_fcn)
    {
      octave_user_function *uf = method_fcn->user_function_value (true);

      // We can only check the context object for user-function (not builtin),
      // where we have access to the parameters (arguments and return values).
      // That's ok as there's no need to call this function for builtin
      // methods.

      if (uf)
        {
          // At this point, the method is executing, but we still need to
          // check the context object for which the method is executing.  For
          // methods, it's the first argument of the function; for ctors, it
          // is the first return value.

          octave::tree_parameter_list *pl = uf->is_classdef_constructor ()
            ? uf->return_list () : uf->parameter_list ();

          if (pl && pl->size () > 0)
            {
              octave::tree_decl_elt *elt = pl->front ();

              octave_value arg0 = tw.evaluate (elt);

              if (arg0.is_defined () && arg0.type_name () == "object")
                {
                  cdef_object arg0_obj = to_cdef (arg0);

                  return obj.is (arg0_obj);
                }
            }
        }
    }

  return false;
}

static octave_value_list
class_get_methods (const octave_value_list& args, int /* nargout */)
{
  octave_value_list retval;

  if (args.length () == 1 && args(0).type_name () == "object")
    {
      cdef_class cls (to_cdef (args(0)));

      retval(0) = cls.get_methods ();
    }

  return retval;
}

static octave_value_list
class_get_superclasses (const octave_value_list& args, int /* nargout */)
{
  octave_value_list retval;

  if (args.length () == 1 && args(0).type_name () == "object"
      && args(0).class_name () == "meta.class")
    {
      cdef_class cls (to_cdef (args(0)));

      Cell classes = cls.get ("SuperClasses").cell_value ();

      retval(0) = to_ov (lookup_classes (classes));
    }

  return retval;
}

static octave_value_list
class_get_inferiorclasses (const octave_value_list& args, int /* nargout */)
{
  octave_value_list retval;

  if (args.length () == 1 && args(0).type_name () == "object"
      && args(0).class_name () == "meta.class")
    {
      cdef_class cls (to_cdef (args(0)));

      Cell classes = cls.get ("InferiorClasses").cell_value ();

      retval(0) = to_ov (lookup_classes (classes));
    }

  return retval;
}

static octave_value_list
class_fromName (const octave_value_list& args, int /* nargout */)
{
  octave_value_list retval;

  if (args.length () != 1)
    error ("fromName: invalid number of parameters");

  std::string name = args(0).xstring_value ("fromName: CLASS_NAME must be a string");

  retval(0) = to_ov (lookup_class (name, false));

  return retval;
}

static octave_value_list
class_fevalStatic (const octave_value_list& args, int nargout)
{
  if (args.length () <= 1 || args(0).type_name () != "object")
    error ("fevalStatic: first argument must be a meta.class object");

  cdef_class cls (to_cdef (args(0)));

  std::string meth_name = args(1).xstring_value ("fevalStatic: method name must be a string");

  cdef_method meth = cls.find_method (meth_name);

  if (! meth.ok ())
    error ("fevalStatic: method not found: %s", meth_name.c_str ());

  if (! meth.is_static ())
    error ("fevalStatic: method `%s' is not static", meth_name.c_str ());

  return meth.execute (args.splice (0, 2), nargout, true, "fevalStatic");
}

static octave_value_list
class_getConstant (const octave_value_list& args, int /* nargout */)
{
  octave_value_list retval;

  if (args.length () != 2 || args(0).type_name () != "object"
      || args(0).class_name () != "meta.class")
    error ("getConstant: first argument must be a meta.class object");

  cdef_class cls = to_cdef (args(0));

  std::string prop_name = args(1).xstring_value ("getConstant: property name must be a string");

  cdef_property prop = cls.find_property (prop_name);

  if (! prop.ok ())
    error ("getConstant: property not found: %s",
           prop_name.c_str ());

  if (! prop.is_constant ())
    error ("getConstant: property `%s' is not constant",
           prop_name.c_str ());

  retval(0) = prop.get_value (true, "getConstant");

  return retval;
}

#define META_CLASS_CMP(OP, CLSA, CLSB, FUN)                             \
  static octave_value_list                                              \
  class_ ## OP (const octave_value_list& args, int /* nargout */)       \
  {                                                                     \
    octave_value_list retval;                                           \
                                                                        \
    if (args.length () != 2                                             \
        || args(0).type_name () != "object"                             \
        || args(1).type_name () != "object"                             \
        || args(0).class_name () != "meta.class"                        \
        || args(1).class_name () != "meta.class")                       \
      error (#OP ": invalid arguments");                                \
                                                                        \
    cdef_class clsa = to_cdef (args(0));                                \
                                                                        \
    cdef_class clsb = to_cdef (args(1));                                \
                                                                        \
    retval(0) = FUN (CLSA, CLSB);                                       \
                                                                        \
    return retval;                                                      \
  }

META_CLASS_CMP (lt, clsb, clsa, is_strict_superclass)
META_CLASS_CMP (le, clsb, clsa, is_superclass)
META_CLASS_CMP (gt, clsa, clsb, is_strict_superclass)
META_CLASS_CMP (ge, clsa, clsb, is_superclass)
META_CLASS_CMP (eq, clsa, clsb, operator==)
META_CLASS_CMP (ne, clsa, clsb, operator!=)

octave_value_list
property_get_defaultvalue (const octave_value_list& args, int /* nargout */)
{
  octave_value_list retval;

  if (args.length () == 1 && args(0).type_name () == "object")
    {
      cdef_property prop (to_cdef (args(0)));

      retval(0) = prop.get ("DefaultValue");

      if (! retval(0).is_defined ())
        error_with_id ("Octave:class:NotDefaultDefined",
                       "no default value for property `%s'",
                       prop.get_name ().c_str ());
    }

  return retval;
}

static octave_value_list
handle_delete (const octave_value_list& /* args */, int /* nargout */)
{
  octave_value_list retval;

  // FIXME: implement this

  return retval;
}

cdef_class
cdef_manager::make_class (const std::string& name,
                          const std::list<cdef_class>& super_list)
{
  cdef_class cls (name, super_list);

  cls.set_class (meta_class ());

  cls.put ("Abstract", false);
  cls.put ("ConstructOnLoad", false);
  cls.put ("ContainingPackage", Matrix ());
  cls.put ("Description", "");
  cls.put ("DetailedDescription", "");
  cls.put ("Events", Cell ());
  cls.put ("Hidden", false);
  cls.put ("InferiorClasses", Cell ());
  cls.put ("Methods", Cell ());
  cls.put ("Properties", Cell ());
  cls.put ("Sealed", false);

  if (name == "handle")
    {
      cls.put ("HandleCompatible", true);
      cls.mark_as_handle_class ();
    }
  else if (super_list.empty ())
    {
      cls.put ("HandleCompatible", false);
    }
  else
    {
      bool all_handle_compatible = true;
      bool has_handle_class = false;

      for (const auto& cl : super_list)
        {
          all_handle_compatible = all_handle_compatible
                                  && cl.get ("HandleCompatible").bool_value ();
          has_handle_class = has_handle_class || cl.is_handle_class ();
        }

      if (has_handle_class && ! all_handle_compatible)
        error ("%s: cannot mix handle and non-HandleCompatible classes",
               name.c_str ());

      cls.put ("HandleCompatible", all_handle_compatible);
      if (has_handle_class)
        cls.mark_as_handle_class ();
    }

  if (! name.empty ())
    register_class (cls);

  return cls;
}

cdef_class
cdef_manager::make_class (const std::string& name,
                          const cdef_class& super)
{
  return make_class (name, std::list<cdef_class> (1, super));
}

cdef_class
cdef_manager::make_meta_class (const std::string& name,
                               const cdef_class& super)
{
  cdef_class cls = make_class (name, super);

  cls.put ("Sealed", true);
  cls.mark_as_meta_class ();

  return cls;
}

cdef_property
cdef_manager::make_property (const cdef_class& cls, const std::string& name,
                             const octave_value& get_method,
                             const std::string& get_access,
                             const octave_value& set_method,
                             const std::string& set_access)
{
  cdef_property prop (name);

  prop.set_class (meta_property ());

  prop.put ("Description", "");
  prop.put ("DetailedDescription", "");
  prop.put ("Abstract", false);
  prop.put ("Constant", false);
  prop.put ("GetAccess", get_access);
  prop.put ("SetAccess", set_access);
  prop.put ("Dependent", false);
  prop.put ("Transient", false);
  prop.put ("Hidden", false);
  prop.put ("GetObservable", false);
  prop.put ("SetObservable", false);
  prop.put ("GetMethod", get_method);
  prop.put ("SetMethod", set_method);
  prop.put ("DefiningClass", to_ov (cls));
  prop.put ("DefaultValue", octave_value ());
  prop.put ("HasDefault", false);

  std::string class_name = cls.get_name ();

  if (! get_method.isempty ())
    make_function_of_class (class_name, get_method);
  if (! set_method.isempty ())
    make_function_of_class (class_name, set_method);

  return prop;
}

cdef_property
cdef_manager::make_attribute (const cdef_class& cls, const std::string& name)
{
  return make_property (cls, name, Matrix (), "public", Matrix (), "private");
}

cdef_method
cdef_manager::make_method (const cdef_class& cls, const std::string& name,
                           const octave_value& fcn,
                           const std::string& m_access, bool is_static)
{
  cdef_method meth (name);

  meth.set_class (meta_method ());

  meth.put ("Abstract", false);
  meth.put ("Access", m_access);
  meth.put ("DefiningClass", to_ov (cls));
  meth.put ("Description", "");
  meth.put ("DetailedDescription", "");
  meth.put ("Hidden", false);
  meth.put ("Sealed", true);
  meth.put ("Static", is_static);

  if (fcn.is_defined ())
    make_function_of_class (cls, fcn);

  meth.set_function (fcn);

  if (is_dummy_method (fcn))
    meth.mark_as_external (cls.get_name ());

  return meth;
}

cdef_method
cdef_manager::make_method (const cdef_class& cls, const std::string& name,
                           octave_builtin::fcn ff,
                           const std::string& m_access, bool is_static)
{
  octave_value fcn (new octave_builtin (ff, name));

  return make_method (cls, name, fcn, m_access, is_static);
}

cdef_method
cdef_manager::make_method (const cdef_class& cls, const std::string& name,
                           octave_builtin::meth mm,
                           const std::string& m_access, bool is_static)
{
  octave_value fcn (new octave_builtin (mm, name));

  return make_method (cls, name, fcn, m_access, is_static);
}

cdef_package
cdef_manager::make_package (const std::string& nm, const std::string& parent)
{
  cdef_package pack (nm);

  pack.set_class (meta_package ());

  if (parent.empty ())
    pack.put ("ContainingPackage", Matrix ());
  else
    pack.put ("ContainingPackage", to_ov (find_package (parent)));

  if (! nm.empty ())
    register_package (pack);

  return pack;
}

//----------------------------------------------------------------------------

int octave_classdef::t_id (-1);

const std::string octave_classdef::t_name ("object");

void
octave_classdef::register_type (octave::type_info& ti)
{
  t_id = ti.register_type (octave_classdef::t_name, "<unknown>",
                           octave_value (new octave_classdef ()));
}

octave_value_list
octave_classdef::subsref (const std::string& type,
                          const std::list<octave_value_list>& idx,
                          int nargout)
{
  size_t skip = 0;
  octave_value_list retval;

  cdef_class cls = object.get_class ();

  if (! in_class_method (cls) && ! called_from_builtin ())
    {
      cdef_method meth = cls.find_method ("subsref");

      if (meth.ok ())
        {
          octave_value_list args;

          args(1) = make_idx_args (type, idx, "subsref");

          count++;
          args(0) = octave_value (this);

          retval = meth.execute (args, nargout, true, "subsref");

          return retval;
        }
    }

  // At this point, the default subsref mechanism must be used.

  retval = object.subsref (type, idx, nargout, skip, cdef_class ());

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

  return retval;
}

octave_value
octave_classdef::subsref (const std::string& type,
                          const std::list<octave_value_list>& idx,
                          bool auto_add)
{
  size_t skip = 0;
  octave_value_list retval;

  // This variant of subsref is used to create temporary values when doing
  // assignment with multi-level indexing.  AFAIK this is only used for internal
  // purpose (not sure we should even implement this) and any overload subsref
  // should not be called.

  retval = object.subsref (type, idx, 1, skip, cdef_class (), auto_add);

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

  return retval.length () > 0 ? retval(0) : octave_value ();
}

octave_value
octave_classdef::subsasgn (const std::string& type,
                           const std::list<octave_value_list>& idx,
                           const octave_value& rhs)
{
  octave_value retval;

  cdef_class cls = object.get_class ();

  if (! in_class_method (cls) && ! called_from_builtin ())
    {
      cdef_method meth = cls.find_method ("subsasgn");

      if (meth.ok ())
        {
          octave_value_list args;

          args(1) = make_idx_args (type, idx, "subsasgn");

          count++;
          args(0) = octave_value (this);
          args(2) = rhs;

          octave_value_list retlist;

          retlist = meth.execute (args, 1, true, "subsasgn");

          if (retlist.empty ())
            error ("overloaded method `subsasgn' did not return any value");

          retval = retlist(0);
        }
    }

  if (! retval.is_defined ())
    retval = object.subsasgn (type, idx, rhs);

  return retval;
}

octave_value
octave_classdef::undef_subsasgn (const std::string& type,
                                 const std::list<octave_value_list>& idx,
                                 const octave_value& rhs)
{
  if (type.length () == 1 && type[0] == '(')
    {
      object = object.make_array ();

      return subsasgn (type, idx, rhs);
    }
  else
    return octave_base_value::undef_subsasgn (type, idx, rhs);

  return octave_value ();
}

octave_idx_type
octave_classdef::numel (const octave_value_list& idx)
{
  octave_idx_type retval = -1;

  cdef_class cls = object.get_class ();

  if (! in_class_method (cls) && ! called_from_builtin ())
    {
      cdef_method meth = cls.find_method ("numel");

      if (meth.ok ())
        {
          octave_value_list args (idx.length () + 1, octave_value ());

          count++;
          args(0) = octave_value (this);

          for (octave_idx_type i = 0; i < idx.length (); i++)
            args(i+1) = idx(i);

          octave_value_list lv = meth.execute (args, 1, true, "numel");
          if (lv.length () != 1 || ! lv(0).is_scalar_type ())
            error ("@%s/numel: invalid return value", cls.get_name ().c_str ());

          retval = lv(0).idx_type_value (true);

          return retval;
        }
    }

  retval = octave_base_value::numel (idx);

  return retval;
}

void
octave_classdef::print (std::ostream& os, bool)
{
  print_raw (os);
}

void
octave_classdef::print_raw (std::ostream& os, bool) const
{
  indent (os);
  os << "<object ";
  if (object.is_array ())
    os << "array ";
  os << class_name () << '>';
  newline (os);
}

bool
octave_classdef::print_name_tag (std::ostream& os,
                                 const std::string& name) const
{
  return octave_base_value::print_name_tag (os, name);
}

void
octave_classdef::print_with_name (std::ostream& os, const std::string& name,
                                  bool print_padding)
{
  octave_base_value::print_with_name (os, name, print_padding);
}

bool
octave_classdef::is_instance_of (const std::string& cls_name) const
{
  cdef_class cls = lookup_class (cls_name, false, false);

  if (cls.ok ())
    return is_superclass (cls, object.get_class ());

  return false;
}

//----------------------------------------------------------------------------

class octave_classdef_meta : public octave_function
{
public:
  octave_classdef_meta (const cdef_meta_object& obj)
    : object (obj) { }

  ~octave_classdef_meta (void)
  { object.meta_release (); }

  bool is_classdef_meta (void) const { return true; }

  bool is_package (void) const { return object.is_package(); }

  octave_function * function_value (bool = false) { return this; }

  octave_value_list
  subsref (const std::string& type,
           const std::list<octave_value_list>& idx,
           int nargout)
  {
    return object.meta_subsref (type, idx, nargout);
  }

  octave_value_list call (octave::tree_evaluator&, int nargout,
                          const octave_value_list& args)
  {
    // Emulate ()-type meta subsref

    std::list<octave_value_list> idx (1, args);
    std::string type ("(");

    return subsref (type, idx, nargout);
  }

  bool accepts_postfix_index (char type) const
  { return object.meta_accepts_postfix_index (type); }

  bool
  is_classdef_constructor (const std::string& cname = "") const
  {
    bool retval = false;

    if (object.is_class ())
      {
        if (cname.empty ())
          retval = true;
        else
          {
            cdef_class cls (object);

            if (cls.get_name () == cname)
              retval = true;
          }
      }

    return retval;
  }

private:
  cdef_meta_object object;
};

//----------------------------------------------------------------------------

class octave_classdef_superclass_ref : public octave_function
{
public:
  octave_classdef_superclass_ref (const octave_value_list& a)
    : octave_function (), args (a) { }

  ~octave_classdef_superclass_ref (void) = default;

  bool is_classdef_superclass_ref (void) const { return true; }

  octave_function * function_value (bool = false) { return this; }

  octave_value_list
  call (octave::tree_evaluator&, int nargout, const octave_value_list& idx)
  {
    octave_value_list retval;

    std::string meth_name;
    bool in_constructor;
    cdef_class ctx;

    ctx = get_class_context (meth_name, in_constructor);

    if (! ctx.ok ())
      error ("superclass calls can only occur in methods or constructors");

    std::string mname = args(0).string_value ();
    std::string cname = args(1).string_value ();

    cdef_class cls = lookup_class (cname);

    if (in_constructor)
      {
        if (! is_direct_superclass (cls, ctx))
          error ("`%s' is not a direct superclass of `%s'",
                 cname.c_str (), ctx.get_name ().c_str ());

        if (! is_constructed_object (mname))
          error ("cannot call superclass constructor with variable `%s'",
                 mname.c_str ());

        octave::symbol_scope scope
          = octave::__require_current_scope__ ("octave_classdef_superclass_ref::call");

        octave_value sym = scope.varval (mname);

        cls.run_constructor (to_cdef_ref (sym), idx);

        retval(0) = sym;
      }
    else
      {
        if (mname != meth_name)
          error ("method name mismatch (`%s' != `%s')",
                 mname.c_str (), meth_name.c_str ());

        if (! is_strict_superclass (cls, ctx))
          error ("`%s' is not a superclass of `%s'",
                 cname.c_str (), ctx.get_name ().c_str ());

        // I see 2 possible implementations here:
        // 1) use cdef_object::subsref with a different class
        //    context; this avoids duplicating code, but
        //    assumes the object is always the first argument
        // 2) lookup the method manually and call
        //    cdef_method::execute; this duplicates part of
        //    logic in cdef_object::subsref, but avoid the
        //    assumption of 1)
        // Not being sure about the assumption of 1), I
        // go with option 2) for the time being.

        cdef_method meth = cls.find_method (meth_name, false);

        if (! meth.ok ())
          error ("no method `%s' found in superclass `%s'",
                 meth_name.c_str (), cname.c_str ());

        retval = meth.execute (idx, nargout, true,
                               meth_name);
      }

    return retval;
  }

private:
  bool is_constructed_object (const std::string nm)
  {
    octave::call_stack& cs
      = octave::__get_call_stack__ ("octave_classdef_superclass_ref::is_constructed_object");

    octave_function *of = cs.current ();

    if (of->is_classdef_constructor ())
      {
        octave_user_function *uf = of->user_function_value (true);

        if (uf)
          {
            octave::tree_parameter_list *ret_list = uf->return_list ();

            if (ret_list && ret_list->length () == 1)
              return (ret_list->front ()->name () == nm);
          }
      }

    return false;
  }

private:
  octave_value_list args;
};

//----------------------------------------------------------------------------

octave_map
cdef_object::map_value (void) const
{
  octave_map retval;

  warning_with_id ("Octave:classdef-to-struct",
                   "struct: converting a classdef object into a struct "
                   "overrides the access restrictions defined for properties. "
                   "All properties are returned, including private and "
                   "protected ones.");

  cdef_class cls = get_class ();

  if (cls.ok ())
    {
      std::map<std::string, cdef_property> props;

      props = cls.get_property_map (cdef_class::property_all);

      // FIXME: Why not const here?
      for (auto& prop_val : props)
        {
          if (is_array ())
            {
              Array<cdef_object> a_obj = array_value ();

              Cell cvalue (a_obj.dims ());

              for (octave_idx_type i = 0; i < a_obj.numel (); i++)
                cvalue (i) = prop_val.second.get_value (a_obj(i), false);

              retval.setfield (prop_val.first, cvalue);
            }
          else
            {
              Cell cvalue (dim_vector (1, 1),
                           prop_val.second.get_value (*this, false));

              retval.setfield (prop_val.first, cvalue);
            }
        }
    }

  return retval;
}

string_vector
cdef_object_rep::map_keys (void) const
{
  cdef_class cls = get_class ();

  if (cls.ok ())
    return cls.get_names ();

  return string_vector ();
}

octave_value_list
cdef_object_scalar::subsref (const std::string& type,
                             const std::list<octave_value_list>& idx,
                             int nargout, size_t& skip,
                             const cdef_class& context, bool auto_add)
{
  skip = 0;

  cdef_class cls = (context.ok () ? context : get_class ());

  octave_value_list retval;

  if (! cls.ok ())
    return retval;

  switch (type[0])
    {
    case '.':
      {
        std::string name = (idx.front ())(0).string_value ();

        cdef_method meth = cls.find_method (name);

        if (meth.ok ())
          {
            int _nargout = (type.length () > 2 ? 1 : nargout);

            octave_value_list args;

            skip = 1;

            if (type.length () > 1 && type[1] == '(')
              {
                std::list<octave_value_list>::const_iterator it = idx.begin ();

                args = *++it;

                skip++;
              }

            if (meth.is_static ())
              retval = meth.execute (args, _nargout, true, "subsref");
            else
              {
                refcount++;
                retval = meth.execute (cdef_object (this), args, _nargout,
                                       true, "subsref");
              }
          }

        if (skip == 0)
          {
            cdef_property prop = cls.find_property (name);

            if (! prop.ok ())
              error ("subsref: unknown method or property: %s", name.c_str ());

            if (prop.is_constant ())
              retval(0) = prop.get_value (true, "subsref");
            else
              {
                refcount++;
                retval(0) = prop.get_value (cdef_object (this),
                                            true, "subsref");
              }

            skip = 1;
          }
        break;
      }

    case '(':
      {
        const octave_value_list& ival = idx.front ();

        refcount++;
        cdef_object this_obj (this);

        if (ival.empty ())
          {
            skip++;
            retval(0) = to_ov (this_obj);
          }
        else
          {
            Array<cdef_object> arr (dim_vector (1, 1), this_obj);

            cdef_object new_obj = cdef_object (new cdef_object_array (arr));

            new_obj.set_class (get_class ());

            retval = new_obj.subsref (type, idx, nargout, skip, cls, auto_add);
          }
      }
      break;

    default:
      error ("object cannot be indexed with `%c'", type[0]);
      break;
    }

  return retval;
}

octave_value
cdef_object_scalar::subsasgn (const std::string& type,
                              const std::list<octave_value_list>& idx,
                              const octave_value& rhs)
{
  octave_value retval;

  cdef_class cls = get_class ();

  switch (type[0])
    {
    case '.':
      {
        std::string name = (idx.front ())(0).string_value ();

        cdef_property prop = cls.find_property (name);

        if (! prop.ok ())
          error ("subsasgn: unknown property: %s", name.c_str ());

        if (prop.is_constant ())
          error ("subsasgn: cannot assign constant property: %s",
                 name.c_str ());

        refcount++;

        cdef_object obj (this);

        if (type.length () == 1)
          {
            prop.set_value (obj, rhs, true, "subsasgn");

            retval = to_ov (obj);
          }
        else
          {
            octave_value val =
              prop.get_value (obj, true, "subsasgn");

            std::list<octave_value_list> args (idx);

            args.erase (args.begin ());

            val = val.assign (octave_value::op_asn_eq,
                              type.substr (1), args, rhs);

            if (val.class_name () != "object"
                || ! to_cdef (val).is_handle_object ())
              prop.set_value (obj, val, true, "subsasgn");

            retval = to_ov (obj);
          }
      }
      break;

    case '(':
      {
        refcount++;

        cdef_object this_obj (this);

        Array<cdef_object> arr (dim_vector (1, 1), this_obj);

        cdef_object new_obj = cdef_object (new cdef_object_array (arr));

        new_obj.set_class (get_class ());

        octave_value tmp = new_obj.subsasgn (type, idx, rhs);

        retval = tmp;
      }
      break;

    default:
      error ("subsasgn: object cannot be index with `%c'", type[0]);
      break;
    }

  return retval;
}

void
cdef_object_scalar::mark_for_construction (const cdef_class& cls)
{
  std::string cls_name = cls.get_name ();

  Cell supcls = cls.get ("SuperClasses").cell_value ();

  std::list<cdef_class> supcls_list = lookup_classes (supcls);

  ctor_list[cls] = supcls_list;
}

octave_value_list
cdef_object_array::subsref (const std::string& type,
                            const std::list<octave_value_list>& idx,
                            int /* nargout */, size_t& skip,
                            const cdef_class& /* context */, bool auto_add)
{
  octave_value_list retval;

  skip = 1;

  switch (type[0])
    {
    case '(':
      {
        const octave_value_list& ival = idx.front ();

        if (ival.empty ())
          {
            refcount++;
            retval(0) = to_ov (cdef_object (this));
            break;
          }

        bool is_scalar = true;
        Array<idx_vector> iv (dim_vector (1, ival.length ()));

        for (int i = 0; i < ival.length (); i++)
          {
            try
              {
                iv(i) = ival(i).index_vector ();
              }
            catch (octave::index_exception& e)
              {
                // Rethrow to allow more info to be reported later.
                e.set_pos_if_unset (ival.length (), i+1);
                throw;
              }

            is_scalar = is_scalar && iv(i).is_scalar ();
          }

        Array<cdef_object> ires = array.index (iv, auto_add);

        // If resizing is enabled (auto_add = true), it's possible
        // indexing was out-of-bound and the result array contains
        // invalid cdef_objects.

        if (auto_add)
          fill_empty_values (ires);

        if (is_scalar)
          retval(0) = to_ov (ires(0));
        else
          {
            cdef_object array_obj (new cdef_object_array (ires));

            array_obj.set_class (get_class ());

            retval(0) = to_ov (array_obj);
          }
      }
      break;

    case '.':
      if (type.size () == 1 && idx.size () == 1)
        {
          Cell c (dims ());

          octave_idx_type n = array.numel ();

          // dummy variables
          size_t dummy_skip;
          cdef_class dummy_cls;

          for (octave_idx_type i = 0; i < n; i++)
            {
              octave_value_list r = array(i).subsref (type, idx, 1, dummy_skip,
                                                      dummy_cls);

              if (r.length () > 0)
                c(i) = r(0);
            }

          retval(0) = octave_value (c, true);

          break;
        }
      OCTAVE_FALLTHROUGH;

    default:
      error ("can't perform indexing operation on array of %s objects",
             class_name ().c_str ());
      break;
    }

  return retval;
}

octave_value
cdef_object_array::subsasgn (const std::string& type,
                             const std::list<octave_value_list>& idx,
                             const octave_value& rhs)
{
  octave_value retval;

  switch (type[0])
    {
    case '(':
      if (type.length () == 1)
        {
          cdef_object rhs_obj = to_cdef (rhs);

          if (rhs_obj.get_class () != get_class ())
            error ("can't assign %s object into array of %s objects.",
                   rhs_obj.class_name ().c_str (),
                   class_name ().c_str ());

          const octave_value_list& ival = idx.front ();
          bool is_scalar = true;
          Array<idx_vector> iv (dim_vector (1, ival.length ()));

          for (int i = 0; i < ival.length (); i++)
            {
              try
                {
                  iv(i) = ival(i).index_vector ();
                }
              catch (octave::index_exception& e)
                {
                  e.set_pos_if_unset (ival.length (), i+1);
                  throw;   // var name set in pt-idx.cc / pt-assign.cc
                }

              is_scalar = is_scalar && iv(i).is_scalar ();
            }

          Array<cdef_object> rhs_mat;

          if (! rhs_obj.is_array ())
            {
              rhs_mat = Array<cdef_object> (dim_vector (1, 1));
              rhs_mat(0) = rhs_obj;
            }
          else
            rhs_mat = rhs_obj.array_value ();

          octave_idx_type n = array.numel ();

          array.assign (iv, rhs_mat, cdef_object ());

          if (array.numel () > n)
            fill_empty_values ();

          refcount++;
          retval = to_ov (cdef_object (this));
        }
      else
        {
          const octave_value_list& ivl = idx.front ();

          // Fill in trailing singleton dimensions so that
          // array.index doesn't create a new blank entry (bug #46660).
          const octave_idx_type one = static_cast<octave_idx_type> (1);
          const octave_value_list& ival = ivl.length () >= 2
                                            ? ivl : ((array.dims ()(0) == 1)
                                                      ? ovl (one, ivl(0))
                                                      : ovl (ivl(0), one));

          bool is_scalar = true;

          Array<idx_vector> iv (dim_vector (1, ival.length ()));

          for (int i = 0; i < ival.length (); i++)
            {
              try
                {
                  iv(i) = ival(i).index_vector ();
                }
              catch (octave::index_exception& e)
                {
                  // Rethrow to allow more info to be reported later.
                  e.set_pos_if_unset (ival.length (), i+1);
                  throw;
                }

              is_scalar = is_scalar && iv(i).is_scalar ();

              if (! is_scalar)
                error ("subsasgn: invalid indexing for object array assignment"
                       ", the index must reference a single object in the "
                       "array.");
            }

          Array<cdef_object> a = array.index (iv, true);

          if (a.numel () != 1)
            error ("subsasgn: invalid indexing for object array assignment");

          cdef_object obj = a(0);

          int ignore_copies = 0;

          // If the object in 'a' is not valid, this means the index
          // was out-of-bound and we need to create a new object.

          if (! obj.ok ())
            obj = get_class ().construct_object (octave_value_list ());
          else
            // Optimize the subsasgn call to come.  There are 2 copies
            // that we can safely ignore:
            // - 1 in "array"
            // - 1 in "a"
            ignore_copies = 2;

          std::list<octave_value_list> next_idx (idx);

          next_idx.erase (next_idx.begin ());

          octave_value tmp = obj.subsasgn (type.substr (1), next_idx,
                                           rhs, ignore_copies);

          cdef_object robj = to_cdef (tmp);

          if (! robj.ok ()
              || robj.is_array ()
              || robj.get_class () != get_class ())
            error ("subasgn: invalid assignment into array of %s objects",
                   class_name ().c_str ());

          // Small optimization, when dealing with handle
          // objects, we don't need to re-assign the result
          // of subsasgn back into the array.

          if (! robj.is (a(0)))
            {
              Array<cdef_object> rhs_a (dim_vector (1, 1),
                                        robj);

              octave_idx_type n = array.numel ();

              array.assign (iv, rhs_a);

              if (array.numel () > n)
                fill_empty_values ();
            }

          refcount++;

          retval = to_ov (cdef_object (this));
        }
      break;

    default:
      error ("can't perform indexing operation on array of %s objects",
             class_name ().c_str ());
      break;
    }

  return retval;
}

void
cdef_object_array::fill_empty_values (Array<cdef_object>& arr)
{
  cdef_class cls = get_class ();

  cdef_object obj;

  int n = arr.numel ();

  for (int i = 0; i < n; i++)
    {
      if (! arr.xelem (i).ok ())
        {
          if (! obj.ok ())
            {
              obj = cls.construct_object (octave_value_list ());

              arr.xelem (i) = obj;
            }
          else
            arr.xelem (i) = obj.copy ();
        }
    }
}

bool
cdef_object_scalar::is_constructed_for (const cdef_class& cls) const
{
  return (is_constructed ()
          || ctor_list.find (cls) == ctor_list.end ());
}

bool
cdef_object_scalar::is_partially_constructed_for (const cdef_class& cls) const
{
  std::map< cdef_class, std::list<cdef_class>>::const_iterator it;

  if (is_constructed ())
    return true;
  else if ((it = ctor_list.find (cls)) == ctor_list.end ()
           || it->second.empty ())
    return true;

  for (const auto& cdef_cls : it->second)
    if (! is_constructed_for (cdef_cls))
      return false;

  return true;
}

inline void
cdef_object_scalar::mark_as_constructed (const cdef_class& cls)
{
  ctor_list.erase (cls);
}

handle_cdef_object::~handle_cdef_object (void)
{
#if DEBUG_TRACE
  std::cerr << "deleting " << get_class ().get_name ()
            << " object (handle)" << std::endl;
#endif
}

value_cdef_object::~value_cdef_object (void)
{
#if DEBUG_TRACE
  std::cerr << "deleting " << get_class ().get_name ()
            << " object (value)" << std::endl;
#endif
}

cdef_class::cdef_class_rep::cdef_class_rep (const std::list<cdef_class>& superclasses)
  : cdef_meta_object_rep (), member_count (0), handle_class (false),
    object_count (0), meta (false)
{
  put ("SuperClasses", to_ov (superclasses));
  implicit_ctor_list = superclasses;
}

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

  if (it == 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 octave::tree_walker
{
public:
  ctor_analyzer (const std::string& ctor, const std::string& obj)
    : octave::tree_walker (), who (ctor), obj_name (obj) { }

  void visit_statement_list (octave::tree_statement_list& t)
  {
    for (const auto& stmt_p : t)
      stmt_p->accept (*this);
  }

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

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

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

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

  void visit_funcall (octave::tree_funcall& t)
  {
    octave_value fcn = t.function ();

    if (fcn.is_function ())
      {
        octave_function *of = fcn.function_value (true);

        if (of)
          {
            if (of->name () == "__superclass_reference__")
              {
                octave_value_list args = t.arguments ();

                if (args(0).string_value () == obj_name)
                  {
                    std::string class_name = args(1).string_value ();

                    cdef_class cls = lookup_class (class_name, false);

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

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

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

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

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

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

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

  member_count++;

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

      octave_function *of = meth.get_function ().function_value (true);

      if (of)
        {
          octave_user_function *uf = of->user_function_value (true);

          if (uf)
            {
              octave::tree_parameter_list *ret_list = uf->return_list ();
              octave::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 obj_name = ret_list->front ()->name ();
              ctor_analyzer a (meth.get_name (), 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

                  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 (void)
{
  std::map<std::string,cdef_method> meths;

  find_methods (meths, false);

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

  int idx = 0;

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

  return c;
}

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

  method_const_iterator it;

  for (it = method_map.begin (); it != method_map.end (); ++it)
    {
      if (! 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);
    }
}

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

  if (it != 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)
{
  property_map[prop.get_name ()] = prop;

  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 = property_map.begin (); it != 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 : 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 : 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 (cdef_object obj)
{
  method_iterator it = method_map.find ("delete");

  if (it != method_map.end ())
    {
      cdef_class cls = obj.get_class ();

      obj.set_class (wrap ());

      it->second.execute (obj, octave_value_list (), 0, false);

      obj.set_class (cls);
    }

  // 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));

      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)
{
  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
    = octave::__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 : 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 ()));
        }
    }

  refcount++;
  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 : 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::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
        = octave::__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 (octave::tree_evaluator& tw,
                         octave::tree_classdef_attribute *t)
{
  octave::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 tw.evaluate (expr);
    }
  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 (octave::interpreter& interp,
                             octave::tree_classdef *t, bool is_at_folder)
{
  cdef_class retval;
  std::string class_name, full_class_name;

  // Class creation

  class_name = full_class_name = t->ident ()->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

  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
    = octave::__get_cdef_manager__ ("cdef_class::make_meta_class");

  retval = cdm.make_class (full_class_name, slist);

  // 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));
    }

  // Class attributes

  octave::tree_evaluator& tw = interp.get_evaluator ();

  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);
        }
    }

  octave::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<octave::tree_classdef_methods_block *> mb_list = b->methods_list ();

      octave::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, full_class_name + '>' + mname);
                  else if (mprefix == "set.")
                    set_methods[mname.substr (4)] =
                      make_fcn_handle (mtd, full_class_name + '>' + mname);
                  else
                    {
                      cdef_method meth = cdm.make_method (retval, mname, mtd);

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

                      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<octave::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

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

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

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

                  // 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.

                  std::map<std::string, octave_value>::iterator 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);
                    }

                  std::map<std::string, octave_value>::iterator 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_function*
cdef_class::get_method_function (const std::string& /* nm */)
{
  octave_classdef_meta *p = new octave_classdef_meta (*this);

  return p;
}

octave_value
cdef_property::cdef_property_rep::get_value (const cdef_object& obj,
                                             bool do_check_access,
                                             const std::string& who)
{
  octave_value retval;

  if (do_check_access && ! check_get_access ())
    err_property_access (who, wrap (), false);

  if (! obj.is_constructed ())
    {
      cdef_class cls (to_cdef (get ("DefiningClass")));

      if (! obj.is_partially_constructed_for (cls))
        error ("cannot reference properties of class `%s' for non-constructed object",
               cls.get_name ().c_str ());
    }

  octave_value get_fcn = get ("GetMethod");

  // FIXME: should check whether we're already in get accessor method

  if (get_fcn.isempty () || is_method_executing (get_fcn, obj))
    retval = obj.get (get ("Name").string_value ());
  else
    {
      octave_value_list args;

      args(0) = to_ov (obj);

      args = octave::feval (get_fcn, args, 1);

      retval = args(0);
    }

  return retval;
}

octave_value
cdef_property::cdef_property_rep::get_value (bool do_check_access,
                                             const std::string& who)
{
  if (do_check_access && ! check_get_access ())
    err_property_access (who, wrap (), false);

  return get ("DefaultValue");
}

bool
cdef_property::cdef_property_rep::is_recursive_set (const cdef_object& /* obj */) const
{
  // FIXME: implement
  return false;
}

void
cdef_property::cdef_property_rep::set_value (cdef_object& obj,
                                             const octave_value& val,
                                             bool do_check_access,
                                             const std::string& who)
{
  if (do_check_access && ! check_set_access ())
    err_property_access (who, wrap (), true);

  if (! obj.is_constructed ())
    {
      cdef_class cls (to_cdef (get ("DefiningClass")));

      if (! obj.is_partially_constructed_for (cls))
        error ("cannot reference properties of class `%s' for non-constructed object",
               cls.get_name ().c_str ());
    }

  octave_value set_fcn = get ("SetMethod");

  if (set_fcn.isempty () || is_method_executing (set_fcn, obj))
    obj.put (get ("Name").string_value (), val);
  else
    {
      octave_value_list args;

      args(0) = to_ov (obj);
      args(1) = val;

      args = octave::feval (set_fcn, args, 1);

      if (args.length () > 0 && args(0).is_defined ())
        {
          if (args (0).is_classdef_object ())
            {
              cdef_object new_obj = to_cdef (args(0));

              obj = new_obj;
            }
          else
            ::warning ("set-method of property `%s' returned a non-classdef object",
                       get_name ().c_str ());
        }
    }
}

bool
cdef_property::cdef_property_rep::check_get_access (void) const
{
  cdef_class cls (to_cdef (get ("DefiningClass")));

  return ::check_access (cls, get ("GetAccess"), "",
                         get_name (), false);

  return false;
}

bool
cdef_property::cdef_property_rep::check_set_access (void) const
{
  cdef_class cls (to_cdef (get ("DefiningClass")));

  return ::check_access (cls, get ("SetAccess"), "",
                         get_name (), true);

  return false;
}

void
cdef_method::cdef_method_rep::check_method (void)
{
  if (is_external ())
    {
      if (is_dummy_method (function))
        {
          octave::load_path& lp
            = octave::__get_load_path__ ("cdef_method::cdef_method_rep::check_method");

          std::string name = get_name ();
          std::string cls_name = dispatch_type;
          std::string pack_name;

          size_t pos = cls_name.rfind ('.');

          if (pos != std::string::npos)
            {
              pack_name = cls_name.substr (0, pos);
              cls_name = cls_name.substr (pos + 1);
            }

          std::string dir_name;
          std::string file_name = lp.find_method (cls_name, name,
                                                  dir_name, pack_name);

          if (! file_name.empty ())
            {
              octave_value ov_fcn
                = octave::load_fcn_from_file (file_name, dir_name,
                                              dispatch_type, pack_name);

              if (ov_fcn.is_defined ())
                {
                  function = ov_fcn;

                  make_function_of_class (dispatch_type, function);
                }
            }
        }
      else
        {
          // FIXME: check out-of-date status
        }

      if (is_dummy_method (function))
        error ("no definition found for method `%s' of class `%s'",
               get_name ().c_str (), dispatch_type.c_str ());
    }
}

octave_value_list
cdef_method::cdef_method_rep::execute (const octave_value_list& args,
                                       int nargout, bool do_check_access,
                                       const std::string& who)
{
  octave_value_list retval;

  if (do_check_access && ! check_access ())
    err_method_access (who, wrap ());

  if (get ("Abstract").bool_value ())
    error ("%s: cannot execute abstract method",
           get ("Name").string_value ().c_str ());

  check_method ();

  if (function.is_defined ())
    retval = octave::feval (function, args, nargout);

  return retval;
}

octave_value_list
cdef_method::cdef_method_rep::execute (const cdef_object& obj,
                                       const octave_value_list& args,
                                       int nargout, bool do_check_access,
                                       const std::string& who)
{
  octave_value_list retval;

  if (do_check_access && ! check_access ())
    err_method_access (who, wrap ());

  if (get ("Abstract").bool_value ())
    error ("%s: cannot execute abstract method",
           get ("Name").string_value ().c_str ());

  check_method ();

  if (function.is_defined ())
    {
      octave_value_list new_args;

      new_args.resize (args.length () + 1);

      new_args(0) = to_ov (obj);
      for (int i = 0; i < args.length (); i++)
        new_args(i+1) = args(i);

      retval = octave::feval (function, new_args, nargout);
    }

  return retval;
}

bool
cdef_method::cdef_method_rep::is_constructor (void) const
{
  if (function.is_function())
    return function.function_value ()->is_classdef_constructor ();

  return false;
}

bool
cdef_method::cdef_method_rep::check_access (void) const
{
  cdef_class cls (to_cdef (get ("DefiningClass")));

  return ::check_access (cls, get ("Access"), get_name ());
}

octave_value_list
cdef_method::cdef_method_rep::meta_subsref
  (const std::string& type, const std::list<octave_value_list>& idx,
   int nargout)
{
  octave_value_list retval;

  switch (type[0])
    {
    case '(':
      retval = (execute (idx.front (), type.length () > 1 ? 1 : nargout, true));
      break;

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

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

  return retval;
}

static cdef_package
lookup_package (const std::string& name, bool error_if_not_found = true,
                bool load_if_not_found = true)
{
  cdef_manager& cdm = octave::__get_cdef_manager__ ("lookup_package");

  return cdm.find_package (name, error_if_not_found, load_if_not_found);
}

static octave_value_list
package_fromName (const octave_value_list& args, int /* nargout */)
{
  octave_value_list retval;

  if (args.length () != 1)
    error ("fromName: invalid number of parameters");

  std::string name = args(0).xstring_value ("fromName: PACKAGE_NAME must be a string");

  retval(0) = to_ov (lookup_package (name, false));

  return retval;
}

static octave_value_list
package_get_classes (const octave_value_list& args, int /* nargout */)
{
  octave_value_list retval (1, Matrix ());

  if (args.length () == 1 && args(0).type_name () == "object"
      && args(0).class_name () == "meta.package")
    {
      cdef_package pack (to_cdef (args(0)));

      retval(0) = pack.get_classes ();
    }

  return retval;
}

static octave_value_list
package_get_functions (const octave_value_list& args, int /* nargout */)
{
  octave_value_list retval (1, Matrix ());

  if (args.length () == 0 && args(0).type_name () == "object"
      && args(0).class_name () == "meta.package")
    {
      cdef_package pack (to_cdef (args(0)));

      retval(0) = pack.get_functions ();
    }

  return retval;
}

static octave_value_list
package_get_packages (const octave_value_list& args, int /* nargout */)
{
  octave_value_list retval (1, Matrix ());

  if (args.length () == 0 && args(0).type_name () == "object"
      && args(0).class_name () == "meta.package")
    {
      cdef_package pack (to_cdef (args(0)));

      retval(0) = pack.get_packages ();
    }

  return retval;
}

static octave_value_list
package_getAllPackages (octave::interpreter& interp,
                        const octave_value_list& /* args */, int /* nargout */)
{
  std::map<std::string, cdef_package> toplevel_packages;

  octave::load_path& lp = interp.get_load_path ();

  std::list<std::string> names = lp.get_all_package_names ();

  cdef_manager& cdm = octave::__get_cdef_manager__ ("package_getAllPackages");

  toplevel_packages["meta"] = cdm.find_package ("meta", false, false);

  for (const auto& nm : names)
    toplevel_packages[nm] = cdm.find_package (nm, false, true);

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

  int i = 0;

  for (const auto& nm_pkg : toplevel_packages)
    c(i++,0) = to_ov (nm_pkg.second);

  return octave_value_list (octave_value (c));
}

void
cdef_package::cdef_package_rep::install_class (const cdef_class& cls,
                                               const std::string& nm)
{
  class_map[nm] = cls;

  member_count++;
}

void
cdef_package::cdef_package_rep::install_function (const octave_value& fcn,
                                                  const std::string& nm)
{
  function_map[nm] = fcn;
}

void
cdef_package::cdef_package_rep::install_package (const cdef_package& pack,
                                                 const std::string& nm)
{
  package_map[nm] = pack;

  member_count++;
}

template <typename T1, typename T2>
Cell
map2Cell (const std::map<T1, T2>& m)
{
  Cell retval (1, m.size ());
  int i = 0;

  for (typename std::map<T1, T2>::const_iterator it = m.begin ();
       it != m.end (); ++it, ++i)
    {
      retval(i) = to_ov (it->second);
    }

  return retval;
}

Cell
cdef_package::cdef_package_rep::get_classes (void) const
{ return map2Cell (class_map); }

Cell
cdef_package::cdef_package_rep::get_functions (void) const
{ return map2Cell (function_map); }

Cell
cdef_package::cdef_package_rep::get_packages (void) const
{ return map2Cell (package_map); }

octave_value
cdef_package::cdef_package_rep::find (const std::string& nm)
{
  std::string symbol_name = get_name () + '.' + nm;

  octave::symbol_table& symtab
    = octave::__get_symbol_table__ ("cdef_package::cdef_package_rep::find");

  return symtab.find (symbol_name, octave_value_list (), true, false);
}

octave_value_list
cdef_package::cdef_package_rep::meta_subsref
  (const std::string& type, const std::list<octave_value_list>& idx,
   int nargout)
{
  octave_value_list retval;

  switch (type[0])
    {
    case '.':
      {
        if (idx.front ().length () != 1)
          error ("invalid meta.package indexing");

        std::string nm = idx.front ()(0).xstring_value ("invalid meta.package indexing, expected a symbol name");

#if DEBUG_TRACE
        std::cerr << "meta.package query: " << nm << std::endl;
#endif

        octave_value o = find (nm);

        if (! o.is_defined ())
          error ("member `%s' in package `%s' does not exist",
                 nm.c_str (), get_name ().c_str ());

        if (o.is_function ())
          {
            octave_function *fcn = o.function_value ();

            // NOTE: the case where the package query is the last
            // part of this subsref index is handled in the parse
            // tree, because there is some logic to handle magic
            // "end" that makes it impossible to execute the
            // function call at this stage.

            if (type.size () > 1
                && ! fcn->accepts_postfix_index (type[1]))
              {
                octave_value_list tmp_args;

                retval = octave::feval (o, tmp_args, nargout);
              }
            else
              retval(0) = o;

            if (type.size () > 1 && idx.size () > 1)
              retval = retval(0).next_subsref (nargout, type,
                                               idx, 1);
          }
        else if (type.size () > 1 && idx.size () > 1)
          retval = o.next_subsref (nargout, type, idx, 1);
        else
          retval(0) = o;
      }
      break;

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

  return retval;
}

void
cdef_package::cdef_package_rep::meta_release (void)
{
  // FIXME: Do we really want to unregister the package, as it
  //        could still be referenced by classes or sub-packages?
  //        If the package object is recreated later on, it won't
  //        match the one already referenced by those classes or
  //        sub-packages.

  cdef_manager& cdm
    = octave::__get_cdef_manager__ ("cdef_package::cdef_package_rep::meta_release");

  // Don't delete the "meta" package.
  if (this != cdm.meta ().get_rep ())
    cdm.unregister_package (wrap ());
}

//----------------------------------------------------------------------------

cdef_manager::cdef_manager (octave::interpreter& interp)
  : m_interpreter (interp), m_all_classes (), m_all_packages (),
    m_meta_class (), m_meta_property (), m_meta_method (),
    m_meta_package (), m_meta ()
{
  octave::type_info& ti = m_interpreter.get_type_info ();

  octave_classdef::register_type (ti);

  // bootstrap
  cdef_class tmp_handle = make_class ("handle");

  m_meta_class = make_meta_class ("meta.class", tmp_handle);

  tmp_handle.set_class (m_meta_class);
  m_meta_class.set_class (m_meta_class);

  // meta classes
  m_meta_property = make_meta_class ("meta.property", tmp_handle);

  m_meta_method = make_meta_class ("meta.method", tmp_handle);

  m_meta_package = make_meta_class ("meta.package", tmp_handle);

  cdef_class tmp_meta_event
    = make_meta_class ("meta.event", tmp_handle);

  cdef_class tmp_meta_dynproperty
    = make_meta_class ("meta.dynamicproperty", tmp_handle);

  // meta.class properties
  m_meta_class.install_property
    (make_attribute (m_meta_class, "Abstract"));

  m_meta_class.install_property
    (make_attribute (m_meta_class, "ConstructOnLoad"));

  m_meta_class.install_property
    (make_property (m_meta_class, "ContainingPackage"));

  m_meta_class.install_property
    (make_property (m_meta_class, "Description"));

  m_meta_class.install_property
    (make_property (m_meta_class, "DetailedDescription"));

  m_meta_class.install_property
    (make_property (m_meta_class, "Events"));

  m_meta_class.install_property
    (make_attribute (m_meta_class, "HandleCompatible"));

  m_meta_class.install_property
    (make_attribute (m_meta_class, "Hidden"));

  m_meta_class.install_property
    (make_property (m_meta_class, "InferiorClasses",
                    make_fcn_handle (class_get_inferiorclasses,
                                     "meta.class>get.InferiorClasses"),
                    "public", Matrix (), "private"));

  m_meta_class.install_property
    (make_property (m_meta_class, "Methods",
                    make_fcn_handle (class_get_methods,
                                     "meta.class>get.Methods"),
                    "public", Matrix (), "private"));

  m_meta_class.install_property
    (make_property (m_meta_class, "MethodList",
                     make_fcn_handle (class_get_methods,
                                      "meta.class>get.MethodList"),
                    "public", Matrix (), "private"));

  m_meta_class.install_property (make_attribute (m_meta_class, "Name"));

  m_meta_class.install_property
    (make_property (m_meta_class, "Properties",
                    make_fcn_handle (class_get_properties,
                                     "meta.class>get.Properties"),
                    "public", Matrix (), "private"));

  m_meta_class.install_property
    (make_property (m_meta_class, "PropertyList",
                    make_fcn_handle (class_get_properties,
                                     "meta.class>get.PropertyList"),
                    "public", Matrix (), "private"));

  m_meta_class.install_property (make_attribute (m_meta_class, "Sealed"));

  m_meta_class.install_property
    (make_property (m_meta_class, "SuperClasses",
                    make_fcn_handle (class_get_superclasses,
                                     "meta.class>get.SuperClasses"),
                    "public", Matrix (), "private"));

  m_meta_class.install_property
    (make_property (m_meta_class, "SuperClassList",
                    make_fcn_handle (class_get_superclasses,
                                     "meta.class>get.SuperClassList"),
                    "public", Matrix (), "private"));

  // meta.class methods
  m_meta_class.install_method
    (make_method (m_meta_class, "fromName", class_fromName, "public", true));

  m_meta_class.install_method
    (make_method (m_meta_class, "fevalStatic", class_fevalStatic, "public",
                  false));

  m_meta_class.install_method
    (make_method (m_meta_class, "getConstant", class_getConstant, "public",
                  false));

  m_meta_class.install_method (make_method (m_meta_class, "eq", class_eq));
  m_meta_class.install_method (make_method (m_meta_class, "ne", class_ne));
  m_meta_class.install_method (make_method (m_meta_class, "lt", class_lt));
  m_meta_class.install_method (make_method (m_meta_class, "le", class_le));
  m_meta_class.install_method (make_method (m_meta_class, "gt", class_gt));
  m_meta_class.install_method (make_method (m_meta_class, "ge", class_ge));

  // meta.method properties
  m_meta_method.install_property
    (make_attribute (m_meta_method, "Abstract"));

  m_meta_method.install_property
    (make_attribute (m_meta_method, "Access"));

  m_meta_method.install_property
    (make_attribute (m_meta_method, "DefiningClass"));

  m_meta_method.install_property
    (make_attribute (m_meta_method, "Description"));

  m_meta_method.install_property
    (make_attribute (m_meta_method, "DetailedDescription"));

  m_meta_method.install_property
    (make_attribute (m_meta_method, "Hidden"));

  m_meta_method.install_property
    (make_attribute (m_meta_method, "Name"));

  m_meta_method.install_property
    (make_attribute (m_meta_method, "Sealed"));

  m_meta_method.install_property
    (make_attribute (m_meta_method, "Static"));

  // meta.property properties
  m_meta_property.install_property
    (make_attribute (m_meta_property, "Name"));

  m_meta_property.install_property
    (make_attribute (m_meta_property, "Description"));

  m_meta_property.install_property
    (make_attribute (m_meta_property, "DetailedDescription"));

  m_meta_property.install_property
    (make_attribute (m_meta_property, "Abstract"));

  m_meta_property.install_property
    (make_attribute (m_meta_property, "Constant"));

  m_meta_property.install_property
    (make_attribute (m_meta_property, "GetAccess"));

  m_meta_property.install_property
    (make_attribute (m_meta_property, "SetAccess"));

  m_meta_property.install_property
    (make_attribute (m_meta_property, "Dependent"));

  m_meta_property.install_property
    (make_attribute (m_meta_property, "Transient"));

  m_meta_property.install_property
    (make_attribute (m_meta_property, "Hidden"));

  m_meta_property.install_property
    (make_attribute (m_meta_property, "GetObservable"));

  m_meta_property.install_property
    (make_attribute (m_meta_property, "SetObservable"));

  m_meta_property.install_property
    (make_attribute (m_meta_property, "GetMethod"));

  m_meta_property.install_property
    (make_attribute (m_meta_property, "SetMethod"));

  m_meta_property.install_property
    (make_attribute (m_meta_property, "DefiningClass"));

  m_meta_property.install_property
    (make_property (m_meta_property, "DefaultValue",
                    make_fcn_handle (property_get_defaultvalue,
                                     "meta.property>get.DefaultValue"),
                    "public", Matrix (), "private"));

  m_meta_property.install_property
    (make_attribute (m_meta_property, "HasDefault"));

  // meta.property events
  // FIXME: add events

  // handle methods

  tmp_handle.install_method
    (make_method (tmp_handle, "delete", handle_delete));

  // meta.package properties

  m_meta_package.install_property
    (make_attribute (m_meta_package, "Name"));

  m_meta_package.install_property
    (make_property (m_meta_package, "ContainingPackage"));

  m_meta_package.install_property
    (make_property (m_meta_package, "ClassList",
                    make_fcn_handle (package_get_classes,
                                     "meta.package>get.ClassList"),
                    "public", Matrix (), "private"));

  m_meta_package.install_property
    (make_property (m_meta_package, "Classes",
                    make_fcn_handle (package_get_classes,
                                     "meta.package>get.Classes"),
                    "public", Matrix (), "private"));

  m_meta_package.install_property
    (make_property (m_meta_package, "FunctionList",
                    make_fcn_handle (package_get_functions,
                                     "meta.package>get.FunctionList"),
                    "public", Matrix (), "private"));

  m_meta_package.install_property
    (make_property (m_meta_package, "Functions",
                    make_fcn_handle (package_get_functions,
                                     "meta.package>get.Functions"),
                    "public", Matrix (), "private"));

  m_meta_package.install_property
    (make_property (m_meta_package, "PackageList",
                      make_fcn_handle (package_get_packages,
                                       "meta.package>get.PackageList"),
                    "public", Matrix (), "private"));

  m_meta_package.install_property
    (make_property (m_meta_package, "Packages",
                    make_fcn_handle (package_get_packages,
                                     "meta.package>get.Packages"),
                    "public", Matrix (), "private"));

  m_meta_package.install_method
    (make_method (m_meta_package, "fromName", package_fromName,
                  "public", true));

  m_meta_package.install_method
    (make_method (m_meta_package, "getAllPackages", package_getAllPackages,
                  "public", true));

  // create "meta" package
  cdef_package package_meta
    = m_meta
    = make_package ("meta");

  package_meta.install_class (m_meta_class, "class");
  package_meta.install_class (m_meta_property, "property");
  package_meta.install_class (m_meta_method, "method");
  package_meta.install_class (m_meta_package, "package");
  package_meta.install_class (tmp_meta_event, "event");
  package_meta.install_class (tmp_meta_dynproperty, "dynproperty");

  octave::symbol_table& symtab = m_interpreter.get_symbol_table ();

  // install built-in classes into the symbol table
  symtab.install_built_in_function
    ("meta.class",
     octave_value (m_meta_class.get_constructor_function ()));

  symtab.install_built_in_function
    ("meta.method",
     octave_value (m_meta_method.get_constructor_function ()));

  symtab.install_built_in_function
    ("meta.property",
     octave_value (m_meta_property.get_constructor_function ()));

  symtab.install_built_in_function
    ("meta.package",
     octave_value (m_meta_package.get_constructor_function ()));

// FIXME: meta.event and meta.dynproperty are not implemented
//        and should not be installed into symbol table.

//  symtab.install_built_in_function
//    ("meta.event",
//     octave_value (tmp_meta_event.get_constructor_function ()));

//  symtab.install_built_in_function
//    ("meta.dynproperty",
//     octave_value (tmp_meta_dynproperty.get_constructor_function ()));
}

cdef_class
cdef_manager::find_class (const std::string& name, bool error_if_not_found,
                          bool load_if_not_found)
{
  std::map<std::string, cdef_class>::iterator it = m_all_classes.find (name);

  if (it == m_all_classes.end ())
    {
      if (load_if_not_found)
        {
          octave_value ov_cls;

          size_t pos = name.rfind ('.');

          if (pos == std::string::npos)
            {
              octave::symbol_table& symtab
                = octave::__get_symbol_table__ ("cdef_manager::find_class");

              ov_cls = symtab.find (name);
            }
          else
            {
              std::string pack_name = name.substr (0, pos);

              cdef_package pack = find_package (pack_name, false, true);

              if (pack.ok ())
                ov_cls = pack.find (name.substr (pos+1));
            }

          if (ov_cls.is_defined ())
            it = m_all_classes.find (name);
        }
    }

  if (it == m_all_classes.end ())
    {
      if (error_if_not_found)
        error ("class not found: %s", name.c_str ());
    }
  else
    {
      cdef_class cls = it->second;

      if (! cls.is_builtin ())
        cls = lookup_class (cls);

      if (cls.ok ())
        return cls;
      else
        m_all_classes.erase (it);
    }

  return cdef_class ();
}

octave_function *
cdef_manager::find_method_symbol (const std::string& method_name,
                                  const std::string& class_name)
{
  octave_function *retval = nullptr;

  cdef_class cls = find_class (class_name, false, false);

  if (cls.ok ())
    {
      cdef_method meth = cls.find_method (method_name);

      if (meth.ok ())
        retval = new octave_classdef_meta (meth);
    }

  return retval;
}

cdef_package
cdef_manager::find_package (const std::string& name, bool error_if_not_found,
                            bool load_if_not_found)
{
  cdef_package retval;

  std::map<std::string, cdef_package>::const_iterator it
    = m_all_packages.find (name);

  if (it != m_all_packages.end ())
    {
      retval = it->second;

      if (! retval.ok ())
        error ("invalid package `%s'", name.c_str ());
    }
  else
    {
      octave::load_path& lp
        = octave::__get_load_path__ ("cdef_manager::find_package");

      if (load_if_not_found && lp.find_package (name))
        {
          size_t pos = name.find ('.');

          if (pos == std::string::npos)
            retval = make_package (name, "");
          else
            {
              std::string parent_name = name.substr (0, pos);

              retval = make_package (name, parent_name);
            }
        }
      else if (error_if_not_found)
        error ("unknown package `%s'", name.c_str ());
    }

  return retval;
}

octave_function *
cdef_manager::find_package_symbol (const std::string& pack_name)
{
  octave_function *retval = nullptr;

  cdef_package pack = find_package (pack_name, false);

  if (pack.ok ())
    retval = new octave_classdef_meta (pack);

  return retval;
}

//----------------------------------------------------------------------------

DEFUN (__meta_get_package__, args, ,
       doc: /* -*- texinfo -*-
@deftypefn {} {} __meta_get_package__ ()
Undocumented internal function.
@end deftypefn */)
{
  if (args.length () != 1)
    print_usage ();

  std::string cname = args(0).xstring_value ("PACKAGE_NAME must be a string");

  return to_ov (lookup_package (cname));
}

DEFUN (__superclass_reference__, args, ,
       doc: /* -*- texinfo -*-
@deftypefn {} {} __superclass_reference__ ()
Undocumented internal function.
@end deftypefn */)
{
  return ovl (new octave_classdef_superclass_ref (args));
}

DEFUN (__meta_class_query__, args, ,
       doc: /* -*- texinfo -*-
@deftypefn {} {} __meta_class_query__ ()
Undocumented internal function.
@end deftypefn */)
{
#if DEBUG_TRACE
  std::cerr << "__meta_class_query__ ("
            << args(0).string_value () << ')'
            << std::endl;
#endif

  if (args.length () != 1)
    print_usage ();

  std::string cls = args(0).xstring_value ("CLASS_NAME must be a string");

  return to_ov (lookup_class (cls));
}

DEFUN (metaclass, args, ,
       doc: /* -*- texinfo -*-
@deftypefn {} {} metaclass (obj)
Returns the meta.class object corresponding to the class of @var{obj}.
@end deftypefn */)
{
  if (args.length () != 1)
    print_usage ();

  cdef_object obj = to_cdef (args(0));

  return to_ov (obj.get_class ());
}

/*
;;; Local Variables: ***
;;; mode: C++ ***
;;; End: ***
*/