Mercurial > octave
view libinterp/octave-value/ov-class.cc @ 31390:2c037ce00450 stable release-7-3-0
Version 7.3.0 released.
* configure.ac (AC_INIT): Set version to 7.3.0.
(OCTAVE_MINOR_VERSION): Now 3.
(OCTAVE_PATCH_VERSION): Now 0.
(OCTAVE_RELEASE_DATE): Set to "2022-11-02".
* org.octave.Octave.appdata.xml: Add release info for version 7.3.0.
* CITATION: Update for 7.3.0.
* NEWS.7.md: Update release date.
author | John W. Eaton <jwe@octave.org> |
---|---|
date | Wed, 02 Nov 2022 14:19:56 -0400 |
parents | 796f54d4ddbf |
children | 83f9f8bda883 |
line wrap: on
line source
//////////////////////////////////////////////////////////////////////// // // Copyright (C) 2007-2022 The Octave Project Developers // // See the file COPYRIGHT.md in the top-level directory of this // distribution or <https://octave.org/copyright/>. // // This file is part of Octave. // // Octave is free software: you can redistribute it and/or modify it // under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Octave is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Octave; see the file COPYING. If not, see // <https://www.gnu.org/licenses/>. // //////////////////////////////////////////////////////////////////////// #if defined (HAVE_CONFIG_H) # include "config.h" #endif #include <istream> #include <memory> #include <ostream> #include "Array-util.h" #include "byte-swap.h" #include "oct-locbuf.h" #include "lo-mappers.h" #include "Cell.h" #include "defun.h" #include "error.h" #include "file-ops.h" #include "errwarn.h" #include "interpreter-private.h" #include "interpreter.h" #include "load-path.h" #include "ls-hdf5.h" #include "ls-oct-text.h" #include "ls-oct-binary.h" #include "ls-utils.h" #include "mxarray.h" #include "oct-lvalue.h" #include "oct-hdf5.h" #include "ov-class.h" #include "ov-fcn.h" #include "ov-typeinfo.h" #include "ov-usr-fcn.h" #include "pager.h" #include "parse.h" #include "pr-output.h" #include "unwind-prot.h" #include "variables.h" int octave_class::t_id (-1); const std::string octave_class::t_name ("class"); void octave_class::register_type (octave::type_info& ti) { t_id = ti.register_type (octave_class::t_name, "<unknown>", octave_value (new octave_class ())); } octave_class::octave_class (const octave_map& m, const std::string& id, const octave_value_list& parents) : octave_base_value (), m_map (m), c_name (id), m_obsolete_copies (0) { octave_idx_type n = parents.length (); for (octave_idx_type idx = 0; idx < n; idx++) { octave_value parent = parents(idx); if (! parent.isobject ()) error ("parents must be objects"); std::string pcnm = parent.class_name (); if (find_parent_class (pcnm)) error ("duplicate class in parent tree"); m_parent_list.push_back (pcnm); octave_idx_type nel = m_map.numel (); octave_idx_type p_nel = parent.numel (); if (nel == 0) { if (p_nel == 0) { // No elements in MAP or the parent class object, // so just add the field name. m_map.assign (pcnm, Cell (m_map.dims ())); } else if (p_nel == 1) { if (m_map.nfields () == 0) { // No elements or fields in MAP, but the // parent is class object with one element. // Resize to match size of parent class and // make the parent a field in MAP. m_map.resize (parent.dims ()); m_map.assign (pcnm, parent); } else { // No elements in MAP, but we have at least // one field. So don't resize, just add the // field name. m_map.assign (pcnm, Cell (m_map.dims ())); } } else if (m_map.nfields () == 0) { // No elements or fields in MAP and more than one // element in the parent class object, so we can // resize MAP to match parent dimsenions, then // distribute the elements of the parent object to // the elements of MAP. dim_vector parent_dims = parent.dims (); m_map.resize (parent_dims); Cell c (parent_dims); octave_map pmap = parent.map_value (); std::list<std::string> plist = parent.parent_class_name_list (); for (octave_idx_type i = 0; i < p_nel; i++) c(i) = octave_value (pmap.index (i), pcnm, plist); m_map.assign (pcnm, c); } else error ("class: parent class dimension mismatch"); } else if (nel == 1 && p_nel == 1) { // Simple assignment. m_map.assign (pcnm, parent); } else { if (p_nel == 1) { // Broadcast the scalar parent class object to // each element of MAP. Cell pcell (m_map.dims (), parent); m_map.assign (pcnm, pcell); } else if (nel == p_nel) { // FIXME: is there a better way to do this? // The parent class object has the same number of // elements as the map we are using to create the // new object, so distribute those elements to // each element of the new object by first // splitting the elements of the parent class // object into a cell array with one element per // cell. Then do the assignment all at once. Cell c (parent.dims ()); octave_map pmap = parent.map_value (); std::list<std::string> plist = parent.parent_class_name_list (); for (octave_idx_type i = 0; i < p_nel; i++) c(i) = octave_value (pmap.index (i), pcnm, plist); m_map.assign (pcnm, c); } else error ("class: parent class dimension mismatch"); } } octave::symbol_table& symtab = octave::__get_symbol_table__ ("octave_class"); symtab.add_to_parent_map (id, m_parent_list); } octave_base_value * octave_class::unique_clone (void) { if (count == m_obsolete_copies) { // All remaining copies are obsolete. We don't actually need to clone. count++; return this; } else { // In theory, this shouldn't be happening, but it's here just in case. if (count < m_obsolete_copies) m_obsolete_copies = 0; return clone (); } } std::string octave_class::get_current_method_class (void) { std::string retval = class_name (); if (nparents () > 0) { octave::tree_evaluator& tw = octave::__get_evaluator__ ("octave_class::get_current_method_class"); octave_function *fcn = tw.current_function (); // Here we are just looking to see if FCN is a method or constructor // for any class, not specifically this one. if (fcn && (fcn->is_class_method () || fcn->is_class_constructor ())) retval = fcn->dispatch_class (); } return retval; } OCTAVE_NORETURN static void err_invalid_index1 (void) { error ("invalid index for class"); } OCTAVE_NORETURN static void err_invalid_index_for_assignment (void) { error ("invalid index for class assignment"); } OCTAVE_NORETURN static void err_invalid_index_type (const std::string& nm, char t) { error ("%s cannot be indexed with %c", nm.c_str (), t); } void octave_class::break_closure_cycles (const std::shared_ptr<octave::stack_frame>& frame) { for (octave_idx_type j = 0; j < m_map.nfields (); j++) { Cell& c = m_map.contents (j); for (octave_idx_type i = 0; i < c.numel (); i++) c(i).break_closure_cycles (frame); } } Cell octave_class::dotref (const octave_value_list& idx) { assert (idx.length () == 1); std::string method_class = get_current_method_class (); // Find the class in which this method resides before attempting to access // the requested field. octave_base_value *obvp = find_parent_class (method_class); if (obvp == nullptr) error ("malformed class"); octave_map my_map = (obvp != this) ? obvp->map_value () : m_map; std::string nm = idx(0).xstring_value ("invalid index for class"); octave_map::const_iterator p = my_map.seek (nm); if (p == my_map.end ()) error ("class has no member '%s'", nm.c_str ()); return my_map.contents (p); } Matrix octave_class::size (void) { if (in_class_method () || called_from_builtin ()) return octave_base_value::size (); Matrix retval (1, 2, 1.0); octave::symbol_table& symtab = octave::__get_symbol_table__ ("octave_class::size"); octave_value meth = symtab.find_method ("size", class_name ()); if (meth.is_defined ()) { count++; octave_value_list args (1, octave_value (this)); octave_value_list lv = octave::feval (meth.function_value (), args, 1); if (lv.length () <= 0 || ! lv(0).is_matrix_type () || ! lv(0).dims ().isvector ()) error ("@%s/size: invalid return value", class_name ().c_str ()); retval = lv(0).matrix_value (); } else { dim_vector dv = dims (); int nd = dv.ndims (); retval.resize (1, nd); for (int i = 0; i < nd; i++) retval(i) = dv(i); } return retval; } octave_idx_type octave_class::xnumel (const octave_value_list& idx) { if (in_class_method () || called_from_builtin ()) return octave_base_value::xnumel (idx); octave_idx_type retval = -1; const std::string cn = class_name (); octave::symbol_table& symtab = octave::__get_symbol_table__ ("octave_class::numel"); octave_value meth = symtab.find_method ("numel", cn); if (meth.is_defined ()) { 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 = octave::feval (meth.function_value (), args, 1); if (lv.length () != 1 || ! lv(0).is_scalar_type ()) error ("@%s/numel: invalid return value", cn.c_str ()); retval = lv(0).idx_type_value (true); } else retval = octave_base_value::xnumel (idx); return retval; } octave_value_list octave_class::subsref (const std::string& type, const std::list<octave_value_list>& idx, int nargout) { octave_value_list retval; if (in_class_method () || called_from_builtin ()) { // FIXME: this block of code is the same as the body of // octave_struct::subsref. Maybe it could be shared instead of // duplicated. int skip = 1; switch (type[0]) { case '(': { if (type.length () > 1 && type[1] == '.') { auto p = idx.begin (); octave_value_list key_idx = *++p; Cell tmp = dotref (key_idx); Cell t = tmp.index (idx.front ()); retval(0) = (t.numel () == 1 ? t(0) : octave_value (t, true)); // We handled two index elements, so tell // next_subsref to skip both of them. skip++; } else retval(0) = octave_value (m_map.index (idx.front ()), c_name, m_parent_list); } break; case '.': { if (m_map.numel () > 0) { Cell t = dotref (idx.front ()); retval(0) = (t.numel () == 1) ? t(0) : octave_value (t, true); } } break; case '{': err_invalid_index_type (type_name (), type[0]); break; default: panic_impossible (); } // FIXME: perhaps there should be an // octave_value_list::next_subsref member function? See also // octave_user_function::subsref. if (idx.size () > 1) retval = retval(0).next_subsref (nargout, type, idx, skip); } else { octave::symbol_table& symtab = octave::__get_symbol_table__ ("octave_class::subsref"); octave_value meth = symtab.find_method ("subsref", class_name ()); if (meth.is_defined ()) { octave_value_list args; args(1) = make_idx_args (type, idx, "subsref"); count++; args(0) = octave_value (this); // FIXME: for Matlab compatibility, let us attempt to set up a proper // value for nargout at least in the simple case where the // cs-list-type expression - i.e., {} or ().x, is the leading one. // Note that Octave does not actually need this, since it will // be able to properly react to varargout a posteriori. bool maybe_cs_list_query = (type[0] == '.' || type[0] == '{' || (type.length () > 1 && type[0] == '(' && type[1] == '.')); int true_nargout = nargout; if (maybe_cs_list_query) { // Set up a proper nargout for the subsref call by calling numel. octave_value_list tmp; if (type[0] != '.') tmp = idx.front (); true_nargout = xnumel (tmp); } retval = octave::feval (meth.function_value (), args, true_nargout); // Since we're handling subsref, if the list has more than one // element, return it as a comma-separated list so that we can // pass it to the evaluator if (retval.length () > 1) retval = octave_value (retval); } else { if (type.length () == 1 && type[0] == '(') retval(0) = octave_value (m_map.index (idx.front ()), c_name, m_parent_list); else err_invalid_index1 (); } } return retval; } octave_value octave_class::numeric_conv (const Cell& val, const std::string& type) { octave_value retval; if (val.numel () != 1) err_invalid_index_for_assignment (); retval = val(0); if (type.length () > 0 && type[0] == '.' && ! retval.isstruct ()) retval = octave_map (); return retval; } octave_value octave_class::subsasgn (const std::string& type, const std::list<octave_value_list>& idx, const octave_value& rhs) { count++; return subsasgn_common (octave_value (this), type, idx, rhs); } octave_value octave_class::undef_subsasgn (const std::string& type, const std::list<octave_value_list>& idx, const octave_value& rhs) { // For compatibility with Matlab, pass [] as the first argument to the // the subsasgn function when the LHS of an indexed assignment is // undefined. return subsasgn_common (Matrix (), type, idx, rhs); } octave_value octave_class::subsasgn_common (const octave_value& obj, const std::string& type, const std::list<octave_value_list>& idx, const octave_value& rhs) { octave_value retval; if (! (in_class_method () || called_from_builtin ())) { octave::symbol_table& symtab = octave::__get_symbol_table__ ("octave_class::subsasgn_common"); octave_value meth = symtab.find_method ("subsasgn", class_name ()); if (meth.is_defined ()) { octave_value_list args; if (rhs.is_cs_list ()) { octave_value_list lrhs = rhs.list_value (); args.resize (2 + lrhs.length ()); for (octave_idx_type i = 0; i < lrhs.length (); i++) args(2+i) = lrhs(i); } else args(2) = rhs; args(1) = make_idx_args (type, idx, "subsasgn"); args(0) = obj; // Now comes the magic. Count copies with me: // 1. myself (obsolete) // 2. the copy inside args (obsolete) // 3. the copy in method's symbol table (working) // ... possibly more (not obsolete). // // So we mark 2 copies as obsolete and hold our fingers crossed. // But prior to doing that, check whether the routine is amenable // to the optimization. // It is essential that the handling function doesn't store extra // copies anywhere. If it does, things will not break but the // optimization won't work. octave_value_list tmp; if (m_obsolete_copies == 0 && meth.is_user_function () && meth.user_function_value ()->subsasgn_optimization_ok ()) { octave::unwind_protect_var<int> restore_var (m_obsolete_copies); m_obsolete_copies = 2; tmp = octave::feval (meth.function_value (), args); } else tmp = octave::feval (meth.function_value (), args); // FIXME: Should the subsasgn method be able to return // more than one value? if (tmp.length () > 1) error ("@%s/subsasgn returned more than one value", class_name ().c_str ()); else retval = tmp(0); return retval; } } // Find the class in which this method resides before // attempting to do the indexed assignment. std::string method_class = get_current_method_class (); octave_base_value *obvp = unique_parent_class (method_class); if (obvp != this) { if (! obvp) error ("malformed class"); obvp->subsasgn (type, idx, rhs); count++; retval = octave_value (this); return retval; } // FIXME: this block of code is the same as the body of // octave_struct::subsasgn. Maybe it could be shared instead of // duplicated. int n = type.length (); octave_value t_rhs = rhs; if (n > 1 && ! (type.length () == 2 && type[0] == '(' && type[1] == '.')) { switch (type[0]) { case '(': { if (type.length () > 1 && type[1] == '.') { auto p = idx.begin (); octave_value_list t_idx = *p; octave_value_list key_idx = *++p; assert (key_idx.length () == 1); std::string key = key_idx(0).xstring_value ("invalid index for class assignment"); octave_value u; if (! m_map.contains (key)) u = octave_value::empty_conv (type.substr (2), rhs); else { Cell map_val = m_map.contents (key); Cell map_elt = map_val.index (idx.front (), true); u = numeric_conv (map_elt, type.substr (2)); } std::list<octave_value_list> next_idx (idx); // We handled two index elements, so subsasgn to // needs to skip both of them. next_idx.erase (next_idx.begin ()); next_idx.erase (next_idx.begin ()); u.make_unique (); t_rhs = u.subsasgn (type.substr (2), next_idx, rhs); } else err_invalid_index_for_assignment (); } break; case '.': { octave_value_list key_idx = idx.front (); assert (key_idx.length () == 1); std::string key = key_idx(0).string_value (); std::list<octave_value_list> next_idx (idx); next_idx.erase (next_idx.begin ()); std::string next_type = type.substr (1); Cell tmpc (1, 1); auto pkey = m_map.seek (key); if (pkey != m_map.end ()) { m_map.contents (pkey).make_unique (); tmpc = m_map.contents (pkey); } // FIXME: better code reuse? if (tmpc.numel () != 1) err_indexed_cs_list (); octave_value& tmp = tmpc(0); if (! tmp.is_defined () || tmp.is_zero_by_zero ()) { tmp = octave_value::empty_conv (next_type, rhs); tmp.make_unique (); // probably a no-op. } else // optimization: ignore copy still stored inside our map. tmp.make_unique (1); t_rhs = tmp.subsasgn (next_type, next_idx, rhs); } break; case '{': err_invalid_index_type (type_name (), type[0]); break; default: panic_impossible (); } } switch (type[0]) { case '(': { if (n > 1 && type[1] == '.') { auto p = idx.begin (); octave_value_list key_idx = *++p; assert (key_idx.length () == 1); std::string key = key_idx(0).xstring_value ("assignment to class element failed"); m_map.assign (idx.front (), key, t_rhs); count++; retval = octave_value (this); } else { if (t_rhs.isobject () || t_rhs.isstruct ()) { octave_map rhs_map = t_rhs.xmap_value ("invalid class assignment"); m_map.assign (idx.front (), rhs_map); count++; retval = octave_value (this); } else { if (! t_rhs.isempty ()) error ("invalid class assignment"); m_map.delete_elements (idx.front ()); count++; retval = octave_value (this); } } } break; case '.': { octave_value_list key_idx = idx.front (); assert (key_idx.length () == 1); std::string key = key_idx(0).string_value (); if (t_rhs.is_cs_list ()) { Cell tmp_cell = Cell (t_rhs.list_value ()); // The shape of the RHS is irrelevant, we just want // the number of elements to agree and to preserve the // shape of the left hand side of the assignment. if (numel () == tmp_cell.numel ()) tmp_cell = tmp_cell.reshape (dims ()); m_map.setfield (key, tmp_cell); } else { Cell tmp_cell(1, 1); tmp_cell(0) = t_rhs.storable_value (); m_map.setfield (key, tmp_cell); } count++; retval = octave_value (this); } break; case '{': err_invalid_index_type (type_name (), type[0]); break; default: panic_impossible (); } return retval; } octave::idx_vector octave_class::index_vector (bool require_integers) const { octave::symbol_table& symtab = octave::__get_symbol_table__ ("octave_class::index_vector"); octave_value meth = symtab.find_method ("subsindex", class_name ()); if (! meth.is_defined ()) error ("no subsindex method defined for class %s", class_name ().c_str ()); octave_value_list args; args(0) = octave_value (new octave_class (m_map, c_name, m_parent_list)); octave_value_list tmp = octave::feval (meth.function_value (), args, 1); if (tmp(0).isobject ()) error ("subsindex function must return a valid index vector"); // Index vector returned by subsindex is zero based // (why this inconsistency Mathworks?), and so we must // add one to the value returned as the index_vector method // expects it to be one based. return octave::binary_op (octave_value::op_add, tmp (0), octave_value (1.0)).index_vector (require_integers); } std::size_t octave_class::byte_size (void) const { // Neglect the size of the fieldnames. std::size_t retval = 0; for (auto it = m_map.cbegin (); it != m_map.cend (); it++) { std::string key = m_map.key (it); octave_value val = octave_value (m_map.contents (it)); retval += val.byte_size (); } return retval; } bool octave_class::is_true (void) const { bool retval = false; octave::symbol_table& symtab = octave::__get_symbol_table__ ("octave_class::is_true"); octave_value meth = symtab.find_method ("logical", class_name ()); if (meth.is_defined ()) { octave_value in = new octave_class (*this); octave_value_list tmp = octave::feval (meth.function_value (), in, 1); retval = tmp(0).is_true (); } return retval; } string_vector octave_class::map_keys (void) const { err_wrong_type_arg ("octave_class::map_keys()", type_name ()); } octave_base_value * octave_class::find_parent_class (const std::string& parent_class_name) { octave_base_value *retval = nullptr; if (parent_class_name == class_name ()) retval = this; else { for (auto& par : m_parent_list) { octave_map::const_iterator smap = m_map.seek (par); const Cell& tmp = m_map.contents (smap); octave_value vtmp = tmp(0); octave_base_value *obvp = vtmp.internal_rep (); retval = obvp->find_parent_class (parent_class_name); if (retval) break; } } return retval; } octave_base_value * octave_class::unique_parent_class (const std::string& parent_class_name) { octave_base_value *retval = nullptr; if (parent_class_name == class_name ()) retval = this; else { for (auto& par : m_parent_list) { auto smap = m_map.seek (par); Cell& tmp = m_map.contents (smap); octave_value& vtmp = tmp(0); octave_base_value *obvp = vtmp.internal_rep (); // Use find_parent_class first to avoid uniquifying if not necessary. retval = obvp->find_parent_class (parent_class_name); if (retval) { vtmp.make_unique (); obvp = vtmp.internal_rep (); retval = obvp->unique_parent_class (parent_class_name); break; } } } return retval; } bool octave_class::is_instance_of (const std::string& cls_name) const { bool retval = false; if (cls_name == class_name ()) retval = true; else { for (auto& par : m_parent_list) { octave_map::const_iterator smap = m_map.seek (par); const Cell& tmp = m_map.contents (smap); const octave_value& vtmp = tmp(0); retval = vtmp.is_instance_of (cls_name); if (retval) break; } } return retval; } string_vector octave_class::string_vector_value (bool pad) const { string_vector retval; octave::symbol_table& symtab = octave::__get_symbol_table__ ("octave_class::string_vector_value"); octave_value meth = symtab.find_method ("char", class_name ()); if (! meth.is_defined ()) error ("no char method defined for class %s", class_name ().c_str ()); octave_value_list args; args(0) = octave_value (new octave_class (m_map, c_name, m_parent_list)); octave_value_list tmp = octave::feval (meth.function_value (), args, 1); if (tmp.length () >= 1) { if (! tmp(0).is_string ()) error ("cname/char method did not return a string"); retval = tmp(0).string_vector_value (pad); } return retval; } void octave_class::print (std::ostream& os, bool) { print_raw (os); } void octave_class::print_raw (std::ostream& os, bool) const { indent (os); os << " <class " << class_name () << '>'; newline (os); } // Loading a class properly requires an exemplar map entry for success. // If we don't have one, we attempt to create one by calling the constructor // with no arguments. bool octave_class::reconstruct_exemplar (void) { bool retval = false; octave_class::exemplar_const_iterator it = octave_class::exemplar_map.find (c_name); if (it != octave_class::exemplar_map.end ()) retval = true; else { octave::interpreter& interp = octave::__get_interpreter__ ("octave_class::reconstruct_exemplar"); octave::symbol_table& symtab = interp.get_symbol_table (); octave_value ctor = symtab.find_method (c_name, c_name); bool have_ctor = false; if (ctor.is_defined () && ctor.is_function ()) { octave_function *fcn = ctor.function_value (); if (fcn && fcn->is_class_constructor (c_name)) have_ctor = true; // Something has gone terribly wrong if // symbol_table::find_method (c_name, c_name) does not return // a class constructor for the class c_name... assert (have_ctor); } if (have_ctor) { octave::unwind_protect frame; // Simulate try/catch. octave::interpreter_try (frame); bool execution_error = false; octave_value_list result; try { result = octave::feval (ctor, ovl (), 1); } catch (const octave::execution_exception&) { interp.recover_from_exception (); execution_error = true; } if (! execution_error && result.length () == 1) retval = true; } else warning ("no constructor for class %s", c_name.c_str ()); } return retval; } void octave_class::clear_exemplar_map (void) { exemplar_map.clear (); } // Load/save does not provide enough information to reconstruct the // class inheritance structure. reconstruct_parents () attempts to // do so. If successful, a "true" value is returned. // // Note that we don't check the loaded object structure against the // class structure here so the user's loadobj method has a chance // to do its magic. bool octave_class::reconstruct_parents (void) { bool retval = true; bool might_have_inheritance = false; std::string dbgstr = "dork"; // First, check to see if there might be an issue with inheritance. for (auto it = m_map.cbegin (); it != m_map.cend (); it++) { std::string key = m_map.key (it); Cell val = m_map.contents (it); if (val(0).isobject ()) { dbgstr = "blork"; if (key == val(0).class_name ()) { might_have_inheritance = true; dbgstr = "cork"; break; } } } if (might_have_inheritance) { octave_class::exemplar_const_iterator it = octave_class::exemplar_map.find (c_name); if (it == octave_class::exemplar_map.end ()) retval = false; else { octave_class::exemplar_info exmplr = it->second; m_parent_list = exmplr.parents (); for (auto& par : m_parent_list) { dbgstr = par; bool dbgbool = m_map.contains (par); if (! dbgbool) { retval = false; break; } } } } return retval; } bool octave_class::save_ascii (std::ostream& os) { os << "# classname: " << class_name () << "\n"; octave_map m; octave::load_path& lp = octave::__get_load_path__ ("octave_class::save_ascii"); if (lp.find_method (class_name (), "saveobj") != "") { octave_value in = new octave_class (*this); octave_value_list tmp = octave::feval ("saveobj", in, 1); m = tmp(0).map_value (); } else m = map_value (); os << "# length: " << m.nfields () << "\n"; auto i = m.begin (); while (i != m.end ()) { octave_value val = m_map.contents (i); bool b = save_text_data (os, val, m.key (i), false, 0); if (! b) return ! os.fail (); i++; } return true; } bool octave_class::load_ascii (std::istream& is) { octave_idx_type len = 0; std::string classname; if (! extract_keyword (is, "classname", classname) || classname.empty ()) error ("load: failed to extract name of class"); if (! extract_keyword (is, "length", len) || len < 0) error ("load: failed to extract number of elements in class"); if (len > 0) { octave_map m (m_map); for (octave_idx_type j = 0; j < len; j++) { octave_value t2; bool dummy; // recurse to read cell elements std::string nm = read_text_data (is, "", dummy, t2, j); if (! is) break; Cell tcell = (t2.iscell () ? t2.xcell_value ("load: internal error loading class elements") : Cell (t2)); m.assign (nm, tcell); } if (! is) error ("load: failed to load class"); c_name = classname; reconstruct_exemplar (); m_map = m; if (! reconstruct_parents ()) warning ("load: unable to reconstruct object inheritance"); octave::load_path& lp = octave::__get_load_path__ ("octave_class::load_ascii"); if (lp.find_method (classname, "loadobj") != "") { octave_value in = new octave_class (*this); octave_value_list tmp = octave::feval ("loadobj", in, 1); m_map = tmp(0).map_value (); } } else if (len == 0) { m_map = octave_map (dim_vector (1, 1)); c_name = classname; } else panic_impossible (); return true; } bool octave_class::save_binary (std::ostream& os, bool save_as_floats) { int32_t classname_len = class_name ().length (); os.write (reinterpret_cast<char *> (&classname_len), 4); os << class_name (); octave_map m; octave::load_path& lp = octave::__get_load_path__ ("octave_class::save_binary"); if (lp.find_method (class_name (), "saveobj") != "") { octave_value in = new octave_class (*this); octave_value_list tmp = octave::feval ("saveobj", in, 1); m = tmp(0).map_value (); } else m = map_value (); int32_t len = m.nfields (); os.write (reinterpret_cast<char *> (&len), 4); auto i = m.begin (); while (i != m.end ()) { octave_value val = m_map.contents (i); bool b = save_binary_data (os, val, m.key (i), "", 0, save_as_floats); if (! b) return ! os.fail (); i++; } return true; } bool octave_class::load_binary (std::istream& is, bool swap, octave::mach_info::float_format fmt) { bool success = true; int32_t classname_len; is.read (reinterpret_cast<char *> (&classname_len), 4); if (! is) return false; else if (swap) swap_bytes<4> (&classname_len); { OCTAVE_LOCAL_BUFFER (char, classname, classname_len+1); classname[classname_len] = '\0'; if (! is.read (reinterpret_cast<char *> (classname), classname_len)) return false; c_name = classname; } reconstruct_exemplar (); int32_t len; if (! is.read (reinterpret_cast<char *> (&len), 4)) return false; if (swap) swap_bytes<4> (&len); if (len > 0) { octave_map m (m_map); for (octave_idx_type j = 0; j < len; j++) { octave_value t2; bool dummy; std::string doc; // recurse to read cell elements std::string nm = read_binary_data (is, swap, fmt, "", dummy, t2, doc); if (! is) break; Cell tcell = (t2.iscell () ? t2.xcell_value ("load: internal error loading class elements") : Cell (t2)); m.assign (nm, tcell); } if (is) { m_map = m; if (! reconstruct_parents ()) warning ("load: unable to reconstruct object inheritance"); octave::load_path& lp = octave::__get_load_path__ ("octave_class::load_binary"); if (lp.find_method (c_name, "loadobj") != "") { octave_value in = new octave_class (*this); octave_value_list tmp = octave::feval ("loadobj", in, 1); m_map = tmp(0).map_value (); } } else { warning ("load: failed to load class"); success = false; } } else if (len == 0) m_map = octave_map (dim_vector (1, 1)); else panic_impossible (); return success; } bool octave_class::save_hdf5 (octave_hdf5_id loc_id, const char *name, bool save_as_floats) { #if defined (HAVE_HDF5) hsize_t hdims[3]; hid_t group_hid = -1; hid_t type_hid = -1; hid_t space_hid = -1; hid_t class_hid = -1; hid_t data_hid = -1; octave_map m; octave_map::iterator i; octave::load_path& lp = octave::__get_load_path__ ("octave_class::save_hdf5"); #if defined (HAVE_HDF5_18) group_hid = H5Gcreate (loc_id, name, octave_H5P_DEFAULT, octave_H5P_DEFAULT, octave_H5P_DEFAULT); #else group_hid = H5Gcreate (loc_id, name, 0); #endif if (group_hid < 0) goto error_cleanup; // Add the class name to the group type_hid = H5Tcopy (H5T_C_S1); H5Tset_size (type_hid, c_name.length () + 1); if (type_hid < 0) goto error_cleanup; hdims[0] = 0; space_hid = H5Screate_simple (0, hdims, nullptr); if (space_hid < 0) goto error_cleanup; #if defined (HAVE_HDF5_18) class_hid = H5Dcreate (group_hid, "classname", type_hid, space_hid, octave_H5P_DEFAULT, octave_H5P_DEFAULT, octave_H5P_DEFAULT); #else class_hid = H5Dcreate (group_hid, "classname", type_hid, space_hid, octave_H5P_DEFAULT); #endif if (class_hid < 0 || H5Dwrite (class_hid, type_hid, octave_H5S_ALL, octave_H5S_ALL, octave_H5P_DEFAULT, c_name.c_str ()) < 0) goto error_cleanup; #if defined (HAVE_HDF5_18) data_hid = H5Gcreate (group_hid, "value", octave_H5P_DEFAULT, octave_H5P_DEFAULT, octave_H5P_DEFAULT); #else data_hid = H5Gcreate (group_hid, "value", 0); #endif if (data_hid < 0) goto error_cleanup; if (lp.find_method (class_name (), "saveobj") != "") { octave_value in = new octave_class (*this); octave_value_list tmp = octave::feval ("saveobj", in, 1); m = tmp(0).map_value (); } else m = map_value (); // recursively add each element of the class to this group i = m.begin (); while (i != m.end ()) { octave_value val = m_map.contents (i); bool retval2 = add_hdf5_data (data_hid, val, m.key (i), "", false, save_as_floats); if (! retval2) break; i++; } error_cleanup: if (data_hid > 0) H5Gclose (data_hid); if (class_hid > 0) H5Dclose (class_hid); if (space_hid > 0) H5Sclose (space_hid); if (type_hid > 0) H5Tclose (type_hid); if (group_hid > 0) H5Gclose (group_hid); return true; #else octave_unused_parameter (loc_id); octave_unused_parameter (name); octave_unused_parameter (save_as_floats); warn_save ("hdf5"); return false; #endif } bool octave_class::load_hdf5 (octave_hdf5_id loc_id, const char *name) { bool retval = false; #if defined (HAVE_HDF5) hid_t group_hid = -1; hid_t data_hid = -1; hid_t type_hid = -1; hid_t type_class_hid = -1; hid_t space_hid = -1; hid_t subgroup_hid = -1; hid_t st_id = -1; hdf5_callback_data dsub; herr_t retval2 = 0; octave_map m (dim_vector (1, 1)); int current_item = 0; hsize_t num_obj = 0; int slen = 0; hsize_t rank = 0; #if defined (HAVE_HDF5_18) group_hid = H5Gopen (loc_id, name, octave_H5P_DEFAULT); #else group_hid = H5Gopen (loc_id, name); #endif if (group_hid < 0) goto error_cleanup; #if defined (HAVE_HDF5_18) data_hid = H5Dopen (group_hid, "classname", octave_H5P_DEFAULT); #else data_hid = H5Dopen (group_hid, "classname"); #endif if (data_hid < 0) goto error_cleanup; type_hid = H5Dget_type (data_hid); type_class_hid = H5Tget_class (type_hid); if (type_class_hid != H5T_STRING) goto error_cleanup; space_hid = H5Dget_space (data_hid); rank = H5Sget_simple_extent_ndims (space_hid); if (rank != 0) goto error_cleanup; slen = H5Tget_size (type_hid); if (slen < 0) goto error_cleanup; // do-while loop here to prevent goto crossing initialization of classname do { OCTAVE_LOCAL_BUFFER (char, classname, slen); // create datatype for (null-terminated) string to read into: st_id = H5Tcopy (H5T_C_S1); H5Tset_size (st_id, slen); if (H5Dread (data_hid, st_id, octave_H5S_ALL, octave_H5S_ALL, octave_H5P_DEFAULT, classname) < 0) { H5Tclose (st_id); H5Dclose (data_hid); H5Gclose (group_hid); return false; } H5Tclose (st_id); H5Dclose (data_hid); data_hid = -1; c_name = classname; } while (0); reconstruct_exemplar (); #if defined (HAVE_HDF5_18) subgroup_hid = H5Gopen (group_hid, name, octave_H5P_DEFAULT); #else subgroup_hid = H5Gopen (group_hid, name); #endif H5Gget_num_objs (subgroup_hid, &num_obj); H5Gclose (subgroup_hid); while (current_item < static_cast<int> (num_obj) && (retval2 = hdf5_h5g_iterate (group_hid, name, ¤t_item, &dsub)) > 0) { octave_value t2 = dsub.tc; Cell tcell = (t2.iscell () ? t2.xcell_value ("load: internal error loading class elements") : Cell (t2)); m.assign (dsub.name, tcell); } if (retval2 >= 0) { m_map = m; if (! reconstruct_parents ()) warning ("load: unable to reconstruct object inheritance"); octave::load_path& lp = octave::__get_load_path__ ("octave_class::load_hdf5"); if (lp.find_method (c_name, "loadobj") != "") { octave_value in = new octave_class (*this); octave_value_list tmp = octave::feval ("loadobj", in, 1); m_map = tmp(0).map_value (); retval = true; } } error_cleanup: if (data_hid > 0) H5Dclose (data_hid); if (data_hid > 0) H5Gclose (group_hid); #else octave_unused_parameter (loc_id); octave_unused_parameter (name); warn_load ("hdf5"); #endif return retval; } mxArray * octave_class::as_mxArray (bool) const { err_wrong_type_arg ("octave_class::as_mxArray ()", type_name ()); } bool octave_class::in_class_method (void) { octave::tree_evaluator& tw = octave::__get_evaluator__ ("octave_class::in_class_method"); octave_function *fcn = tw.current_function (); return (fcn && (fcn->is_class_method () || fcn->is_class_constructor () || fcn->is_anonymous_function_of_class () || fcn->is_private_function_of_class (class_name ())) && find_parent_class (fcn->dispatch_class ())); } octave_class::exemplar_info::exemplar_info (const octave_value& obj) : m_field_names (), m_parent_class_names () { if (! obj.isobject ()) error ("invalid call to exemplar_info constructor"); octave_map m = obj.map_value (); m_field_names = m.keys (); m_parent_class_names = obj.parent_class_name_list (); } // A map from class names to lists of fields. std::map<std::string, octave_class::exemplar_info> octave_class::exemplar_map; bool octave_class::exemplar_info::compare (const octave_value& obj) const { if (! obj.isobject ()) error ("invalid comparison of class exemplar to non-class object"); if (nfields () != obj.nfields ()) error ("mismatch in number of fields"); octave_map obj_map = obj.map_value (); string_vector obj_fnames = obj_map.keys (); string_vector fnames = fields (); for (octave_idx_type i = 0; i < nfields (); i++) { if (obj_fnames[i] != fnames[i]) error ("mismatch in field names"); } if (nparents () != obj.nparents ()) error ("mismatch in number of parent classes"); const std::list<std::string> obj_parents = obj.parent_class_name_list (); const std::list<std::string> pnames = parents (); auto p = obj_parents.begin (); auto q = pnames.begin (); while (p != obj_parents.end ()) { if (*p++ != *q++) error ("mismatch in parent classes"); } return true; } OCTAVE_NAMESPACE_BEGIN DEFMETHOD (class, interp, args, , doc: /* -*- texinfo -*- @deftypefn {} {@var{classname} =} class (@var{obj}) @deftypefnx {} {} class (@var{s}, @var{id}) @deftypefnx {} {} class (@var{s}, @var{id}, @var{p}, @dots{}) Return the class of the object @var{obj}, or create a class with fields from structure @var{s} and name (string) @var{id}. Additional arguments name a list of parent classes from which the new class is derived. @seealso{typeinfo, isa} @end deftypefn */) { int nargin = args.length (); if (nargin == 0) print_usage (); octave_value retval; if (nargin == 1) // Called for class of object retval = args(0).class_name (); else { // Called as class constructor std::string id = args(1).xstring_value ("class: ID (class name) must be a string"); tree_evaluator& tw = interp.get_evaluator (); octave_function *fcn = tw.caller_function (); if (! fcn) error ("class: invalid call from outside class constructor or method"); if (! fcn->is_class_constructor (id) && ! fcn->is_class_method (id)) error ("class: '%s' is invalid as a class name in this context", id.c_str ()); octave_map m = args(0).xmap_value ("class: S must be a valid structure"); if (nargin == 2) retval = octave_value (new octave_class (m, id, std::list<std::string> ())); else { octave_value_list parents = args.slice (2, nargin-2); retval = octave_value (new octave_class (m, id, parents)); } octave_class::exemplar_const_iterator it = octave_class::exemplar_map.find (id); if (it == octave_class::exemplar_map.end ()) octave_class::exemplar_map[id] = octave_class::exemplar_info (retval); else if (! it->second.compare (retval)) error ("class: object of class '%s' does not match previously constructed objects", id.c_str ()); } return retval; } /* %!assert (class (1.1), "double") %!assert (class (single (1.1)), "single") %!assert (class (uint8 (1)), "uint8") %!testif HAVE_JAVA; usejava ("jvm") %! jobj = javaObject ("java.lang.StringBuffer"); %! assert (class (jobj), "java.lang.StringBuffer"); ## Test Input Validation %!error class () */ DEFUN (isa, args, , doc: /* -*- texinfo -*- @deftypefn {} {} isa (@var{obj}, @var{classname}) Return true if @var{obj} is an object from the class @var{classname}. @var{classname} may also be one of the following class categories: @table @asis @item @qcode{"float"} Floating point value comprising classes @qcode{"double"} and @qcode{"single"}. @item @qcode{"integer"} Integer value comprising classes (u)int8, (u)int16, (u)int32, (u)int64. @item @qcode{"numeric"} Numeric value comprising either a floating point or integer value. @end table If @var{classname} is a cell array of string, a logical array of the same size is returned, containing true for each class to which @var{obj} belongs to. @seealso{class, typeinfo} @end deftypefn */) { if (args.length () != 2) print_usage (); octave_value obj = args(0); // not const because of find_parent_class () std::string obj_cls = obj.class_name (); Array<std::string> clsnames = args(1).xcellstr_value ("isa: CLASSNAME must be a string or cell array of strings"); boolNDArray matches (clsnames.dims (), false); for (octave_idx_type idx = 0; idx < clsnames.numel (); idx++) { std::string cls = clsnames(idx); if (obj_cls == cls || (cls == "float" && obj.isfloat ()) || (cls == "integer" && obj.isinteger ()) || (cls == "numeric" && obj.isnumeric ()) || obj.is_instance_of (cls)) matches(idx) = true; } return ovl (matches); } /* %!assert (isa ("char", "float"), false) %!assert (isa (logical (1), "float"), false) %!assert (isa (double (13), "float"), true) %!assert (isa (single (13), "float"), true) %!assert (isa (int8 (13), "float"), false) %!assert (isa (int16 (13), "float"), false) %!assert (isa (int32 (13), "float"), false) %!assert (isa (int64 (13), "float"), false) %!assert (isa (uint8 (13), "float"), false) %!assert (isa (uint16 (13), "float"), false) %!assert (isa (uint32 (13), "float"), false) %!assert (isa (uint64 (13), "float"), false) %!assert (isa ("char", "numeric"), false) %!assert (isa (logical (1), "numeric"), false) %!assert (isa (double (13), "numeric"), true) %!assert (isa (single (13), "numeric"), true) %!assert (isa (int8 (13), "numeric"), true) %!assert (isa (int16 (13), "numeric"), true) %!assert (isa (int32 (13), "numeric"), true) %!assert (isa (int64 (13), "numeric"), true) %!assert (isa (uint8 (13), "numeric"), true) %!assert (isa (uint16 (13), "numeric"), true) %!assert (isa (uint32 (13), "numeric"), true) %!assert (isa (uint64 (13), "numeric"), true) %!assert (isa (uint8 (13), "integer"), true) %!assert (isa (double (13), "integer"), false) %!assert (isa (single (13), "integer"), false) %!assert (isa (single (13), {"integer", "float", "single"}), [false true true]) %!assert (isa (double (13), "double")) %!assert (isa (single (13), "single")) %!assert (isa (int8 (13), "int8")) %!assert (isa (int16 (13), "int16")) %!assert (isa (int32 (13), "int32")) %!assert (isa (int64 (13), "int64")) %!assert (isa (uint8 (13), "uint8")) %!assert (isa (uint16 (13), "uint16")) %!assert (isa (uint32 (13), "uint32")) %!assert (isa (uint64 (13), "uint64")) %!assert (isa ("string", "char")) %!assert (isa (true, "logical")) %!assert (isa (false, "logical")) %!assert (isa ({1, 2}, "cell")) %!assert (isa ({1, 2}, {"numeric", "integer", "cell"}), [false false true]) %!testif HAVE_JAVA; usejava ("jvm") %! ## The first and last assert() are equal on purpose. The assert() in %! ## the middle with an invalid class name will cause the java code to %! ## throw exceptions which we then must clear properly (or all other calls %! ## will fail). So we test this too. %! assert (isa (javaObject ("java.lang.Double", 10), "java.lang.Number")); %! assert (isa (javaObject ("java.lang.Double", 10), "not_a_class"), false); %! assert (isa (javaObject ("java.lang.Double", 10), "java.lang.Number")); %!test %! a.b = 1; %! assert (isa (a, "struct")); */ DEFUN (__parent_classes__, args, , doc: /* -*- texinfo -*- @deftypefn {} {} __parent_classes__ (@var{x}) Undocumented internal function. @end deftypefn */) { if (args.length () != 1) print_usage (); octave_value arg = args(0); if (arg.isobject ()) return ovl (Cell (arg.parent_class_names ())); else return ovl (Cell ()); } DEFUN (isobject, args, , doc: /* -*- texinfo -*- @deftypefn {} {} isobject (@var{x}) Return true if @var{x} is a class object. @seealso{class, typeinfo, isa, ismethod, isprop} @end deftypefn */) { if (args.length () != 1) print_usage (); return ovl (args(0).isobject ()); } static bool is_built_in_class (const std::string& cn) { static std::set<std::string> built_in_class_names; if (built_in_class_names.empty ()) { built_in_class_names.insert ("double"); built_in_class_names.insert ("single"); built_in_class_names.insert ("cell"); built_in_class_names.insert ("struct"); built_in_class_names.insert ("logical"); built_in_class_names.insert ("char"); built_in_class_names.insert ("function handle"); built_in_class_names.insert ("int8"); built_in_class_names.insert ("uint8"); built_in_class_names.insert ("int16"); built_in_class_names.insert ("uint16"); built_in_class_names.insert ("int32"); built_in_class_names.insert ("uint32"); built_in_class_names.insert ("int64"); built_in_class_names.insert ("uint64"); } return built_in_class_names.find (cn) != built_in_class_names.end (); } DEFMETHOD (superiorto, interp, args, , doc: /* -*- texinfo -*- @deftypefn {} {} superiorto (@var{class_name}, @dots{}) When called from a class constructor, mark the object currently constructed as having a higher precedence than @var{class_name}. More that one such class can be specified in a single call. This function may @emph{only} be called from a class constructor. @seealso{inferiorto} @end deftypefn */) { tree_evaluator& tw = interp.get_evaluator (); octave_function *fcn = tw.caller_function (); if (! fcn || ! fcn->is_class_constructor ()) error ("superiorto: invalid call from outside class constructor"); for (int i = 0; i < args.length (); i++) { std::string inf_class = args(i).xstring_value ("superiorto: CLASS_NAME must be a string"); // User defined classes always have higher precedence // than built-in classes if (is_built_in_class (inf_class)) break; symbol_table& symtab = interp.get_symbol_table (); std::string sup_class = fcn->name (); if (! symtab.set_class_relationship (sup_class, inf_class)) error ("superiorto: opposite precedence already set for %s and %s", sup_class.c_str (), inf_class.c_str ()); } return ovl (); } DEFMETHOD (inferiorto, interp, args, , doc: /* -*- texinfo -*- @deftypefn {} {} inferiorto (@var{class_name}, @dots{}) When called from a class constructor, mark the object currently constructed as having a lower precedence than @var{class_name}. More that one such class can be specified in a single call. This function may @emph{only} be called from a class constructor. @seealso{superiorto} @end deftypefn */) { tree_evaluator& tw = interp.get_evaluator (); octave_function *fcn = tw.caller_function (); if (! fcn || ! fcn->is_class_constructor ()) error ("inferiorto: invalid call from outside class constructor"); for (int i = 0; i < args.length (); i++) { std::string sup_class = args(i).xstring_value ("inferiorto: CLASS_NAME must be a string"); if (is_built_in_class (sup_class)) error ("inferiorto: cannot give user-defined class lower " "precedence than built-in class"); symbol_table& symtab = interp.get_symbol_table (); std::string inf_class = fcn->name (); if (! symtab.set_class_relationship (sup_class, inf_class)) error ("inferiorto: opposite precedence already set for %s and %s", inf_class.c_str (), sup_class.c_str ()); } return octave_value(); } // The following classes allow us to define "inline" function objects as // legacy @class objects (as they appear to be in Matlab) while // preserving the is_inline_function and function_value methods that // were previously available in the octave_fcn_inline class. However, // inline function objects no longer behave as octave_fcn_handle objects // so calling is_function_handle for them no longer returns true. I see // no reasonable way to preserve that behavior. The goal here is to // allow most code that used the old octave_inline_fcn object to // continue to work while eliminating the octave_inline_fcn class that // was derived from the octave_fcn_handle class. Making that change // appears to be necessary to properly fix function handle behavior and // improve Matlab compatibility. It's unfortunate if this change causes // trouble, but I see no better fix. Ultimately, we should replace all // uses of "inline" function objects with anonymous functions. class octave_inline; // The following class can be removed once the // octave_value::function_value method is removed. class octave_inline_fcn : public octave_function { public: octave_inline_fcn (octave_inline *obj) : m_inline_obj (obj) { } // No copying! octave_inline_fcn (const octave_inline_fcn& ob) = delete; octave_inline_fcn& operator = (const octave_inline_fcn& ob) = delete; ~octave_inline_fcn (void) = default; // Override default call method because we ultimately use feval to // execute the inline function and that will push a stack frame. octave_value_list call (tree_evaluator& tw, int nargout = 0, const octave_value_list& args = octave_value_list ()) { return execute (tw, nargout, args); } octave_value_list execute (tree_evaluator& tw, int nargout = 0, const octave_value_list& args = octave_value_list ()); private: octave_inline *m_inline_obj; }; // Once the octave_inline_fcn class is removed, we should also be able // to eliminate the octave_inline class below and replace the // octave_value::is_inline_function method with // // bool octave_value::is_inline_function (void) const // { // return class_name () == "inline"; // } class octave_inline : public octave_class { public: octave_inline (const octave_map& m) : octave_class (m, "inline"), m_fcn_obj (new octave_inline_fcn (this)) { } octave_inline (const octave_inline&) = default; ~octave_inline (void) = default; octave_base_value * clone (void) const { return new octave_inline (*this); } octave_base_value * empty_clone (void) const { return new octave_inline (octave_map (map_keys ())); } bool is_inline_function (void) const { return true; } octave_function * function_value (bool) { return m_fcn_obj.get (); } private: std::shared_ptr<octave_inline_fcn> m_fcn_obj; }; octave_value_list octave_inline_fcn::execute (tree_evaluator& tw, int nargout, const octave_value_list& args) { interpreter& interp = tw.get_interpreter (); return interp.feval (octave_value (m_inline_obj, true), args, nargout); } DEFUN (__inline_ctor__, args, , doc: /* -*- texinfo -*- @deftypefn {} {} __inline_ctor__ (@var{prop_struct}) Internal function. Implements final construction for inline objects. @end deftypefn */) { // Input validation has already been done in input.m. return octave_value (new octave_inline (args(0).map_value ())); } OCTAVE_NAMESPACE_END