1 ////////////////////////////////////////////////////////////////////////
2 //
3 // Copyright (C) 2012-2021 The Octave Project Developers
4 //
5 // See the file COPYRIGHT.md in the top-level directory of this
6 // distribution or <https://octave.org/copyright/>.
7 //
8 // This file is part of Octave.
9 //
10 // Octave is free software: you can redistribute it and/or modify it
11 // under the terms of the GNU General Public License as published by
12 // the Free Software Foundation, either version 3 of the License, or
13 // (at your option) any later version.
14 //
15 // Octave is distributed in the hope that it will be useful, but
16 // WITHOUT ANY WARRANTY; without even the implied warranty of
17 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 // GNU General Public License for more details.
19 //
20 // You should have received a copy of the GNU General Public License
21 // along with Octave; see the file COPYING.  If not, see
22 // <https://www.gnu.org/licenses/>.
23 //
24 ////////////////////////////////////////////////////////////////////////
25 
26 #if defined (HAVE_CONFIG_H)
27 #  include "config.h"
28 #endif
29 
30 #include <algorithm>
31 #include <iomanip>
32 
33 #include "cdef-class.h"
34 #include "cdef-method.h"
35 #include "cdef-package.h"
36 #include "cdef-property.h"
37 #include "cdef-utils.h"
38 #include "defun.h"
39 #include "errwarn.h"
40 #include "interpreter-private.h"
41 #include "load-path.h"
42 #include "ov-classdef.h"
43 #include "ov-fcn-handle.h"
44 #include "ov-typeinfo.h"
45 #include "ov-usr-fcn.h"
46 #include "parse.h"
47 #include "pr-output.h"
48 #include "pt-eval.h"
49 #include "pt-misc.h"
50 #include "oct-lvalue.h"
51 
52 static bool
in_class_method(const octave::cdef_class & cls)53 in_class_method (const octave::cdef_class& cls)
54 {
55   octave::cdef_class ctx = octave::get_class_context ();
56 
57   return (ctx.ok () && octave::is_superclass (ctx, cls));
58 }
59 
60 int octave_classdef::t_id (-1);
61 
62 const std::string octave_classdef::t_name ("object");
63 
64 void
register_type(octave::type_info & ti)65 octave_classdef::register_type (octave::type_info& ti)
66 {
67   t_id = ti.register_type (octave_classdef::t_name, "<unknown>",
68                            octave_value (new octave_classdef ()));
69 }
70 
71 octave_value_list
subsref(const std::string & type,const std::list<octave_value_list> & idx,int nargout)72 octave_classdef::subsref (const std::string& type,
73                           const std::list<octave_value_list>& idx,
74                           int nargout)
75 {
76   std::size_t skip = 0;
77   octave_value_list retval;
78 
79   octave::cdef_class cls = object.get_class ();
80 
81   if (! in_class_method (cls) && ! called_from_builtin ())
82     {
83       octave::cdef_method meth = cls.find_method ("subsref");
84 
85       if (meth.ok ())
86         {
87           octave_value_list args;
88 
89           args(1) = make_idx_args (type, idx, "subsref");
90 
91           count++;
92           args(0) = octave_value (this);
93 
94           retval = meth.execute (args, nargout, true, "subsref");
95 
96           return retval;
97         }
98     }
99 
100   // At this point, the default subsref mechanism must be used.
101 
102   retval = object.subsref (type, idx, nargout, skip, octave::cdef_class ());
103 
104   if (type.length () > skip && idx.size () > skip)
105     retval = retval(0).next_subsref (nargout, type, idx, skip);
106 
107   return retval;
108 }
109 
110 octave_value
subsref(const std::string & type,const std::list<octave_value_list> & idx,bool auto_add)111 octave_classdef::subsref (const std::string& type,
112                           const std::list<octave_value_list>& idx,
113                           bool auto_add)
114 {
115   std::size_t skip = 0;
116   octave_value_list retval;
117 
118   // This variant of subsref is used to create temporary values when doing
119   // assignment with multi-level indexing.  AFAIK this is only used for internal
120   // purpose (not sure we should even implement this).
121 
122   octave::cdef_class cls = object.get_class ();
123 
124   if (! in_class_method (cls))
125     {
126       octave::cdef_method meth = cls.find_method ("subsref");
127 
128       if (meth.ok ())
129         {
130           octave_value_list args;
131 
132           args(1) = make_idx_args (type, idx, "subsref");
133 
134           count++;
135           args(0) = octave_value (this);
136 
137           retval = meth.execute (args, 1, true, "subsref");
138 
139           return retval.length () > 0 ? retval(0) : octave_value ();
140         }
141     }
142 
143   retval = object.subsref (type, idx, 1, skip, octave::cdef_class (), auto_add);
144 
145   if (type.length () > skip && idx.size () > skip)
146     retval = retval(0).next_subsref (1, type, idx, skip);
147 
148   return retval.length () > 0 ? retval(0) : octave_value ();
149 }
150 
151 octave_value
subsasgn(const std::string & type,const std::list<octave_value_list> & idx,const octave_value & rhs)152 octave_classdef::subsasgn (const std::string& type,
153                            const std::list<octave_value_list>& idx,
154                            const octave_value& rhs)
155 {
156   octave_value retval;
157 
158   octave::cdef_class cls = object.get_class ();
159 
160   if (! in_class_method (cls) && ! called_from_builtin ())
161     {
162       octave::cdef_method meth = cls.find_method ("subsasgn");
163 
164       if (meth.ok ())
165         {
166           octave_value_list args;
167 
168           args(1) = make_idx_args (type, idx, "subsasgn");
169 
170           count++;
171           args(0) = octave_value (this);
172           args(2) = rhs;
173 
174           octave_value_list retlist;
175 
176           retlist = meth.execute (args, 1, true, "subsasgn");
177 
178           if (retlist.empty ())
179             error ("overloaded method 'subsasgn' did not return any value");
180 
181           retval = retlist(0);
182         }
183     }
184 
185   if (! retval.is_defined ())
186     retval = object.subsasgn (type, idx, rhs);
187 
188   return retval;
189 }
190 
191 octave_value
undef_subsasgn(const std::string & type,const std::list<octave_value_list> & idx,const octave_value & rhs)192 octave_classdef::undef_subsasgn (const std::string& type,
193                                  const std::list<octave_value_list>& idx,
194                                  const octave_value& rhs)
195 {
196   if (type.length () == 1 && type[0] == '(')
197     {
198       object = object.make_array ();
199 
200       return subsasgn (type, idx, rhs);
201     }
202   else
203     return octave_base_value::undef_subsasgn (type, idx, rhs);
204 
205   return octave_value ();
206 }
207 
208 Matrix
size(void)209 octave_classdef::size (void)
210 {
211   octave::cdef_class cls = object.get_class ();
212 
213   if (! in_class_method (cls) && ! called_from_builtin ())
214     {
215       octave::cdef_method meth = cls.find_method ("size");
216 
217       if (meth.ok ())
218         {
219           count++;
220           octave_value_list args (1, octave_value (this));
221 
222           octave_value_list lv = meth.execute (args, 1, true, "size");
223           if (lv.length () <= 0
224               || ! lv(0).is_matrix_type () || ! lv(0).dims ().isvector ())
225             error ("%s.size: invalid return value", class_name ().c_str ());
226 
227           return lv(0).matrix_value ();
228         }
229     }
230 
231   return octave_base_value::size ();
232 }
233 
234 octave_idx_type
xnumel(const octave_value_list & idx)235 octave_classdef::xnumel (const octave_value_list& idx)
236 {
237   octave_idx_type retval = -1;
238 
239   octave::cdef_class cls = object.get_class ();
240 
241   if (! in_class_method (cls) && ! called_from_builtin ())
242     {
243       octave::cdef_method meth = cls.find_method ("numel");
244 
245       if (meth.ok ())
246         {
247           octave_value_list args (idx.length () + 1, octave_value ());
248 
249           count++;
250           args(0) = octave_value (this);
251 
252           for (octave_idx_type i = 0; i < idx.length (); i++)
253             args(i+1) = idx(i);
254 
255           // Temporarily set lvalue list of current statement to NULL, to avoid
256           // using that list for the execution of the method "numel"
257           octave::interpreter& interp = octave::__get_interpreter__ ("octave_classdef::xnumel");
258           octave::tree_evaluator& tw = interp.get_evaluator();
259 
260           octave::unwind_action act ([&tw] (const std::list<octave::octave_lvalue> *lvl)
261                             {
262                               tw.set_lvalue_list (lvl);
263                             }, tw.lvalue_list ());
264           tw.set_lvalue_list (nullptr);
265 
266           octave_value_list lv = meth.execute (args, 1, true, "numel");
267           if (lv.length () != 1 || ! lv(0).is_scalar_type ())
268             error ("@%s/numel: invalid return value", cls.get_name ().c_str ());
269 
270           retval = lv(0).idx_type_value (true);
271 
272           return retval;
273         }
274     }
275 
276   retval = octave_base_value::xnumel (idx);
277 
278   return retval;
279 }
280 
281 void
print(std::ostream & os,bool)282 octave_classdef::print (std::ostream& os, bool)
283 {
284   print_raw (os);
285 }
286 
287 void
print_raw(std::ostream & os,bool) const288 octave_classdef::print_raw (std::ostream& os, bool) const
289 {
290   octave::cdef_class cls = object.get_class ();
291 
292   if (cls.ok ())
293     {
294       bool is_array = object.is_array ();
295 
296       increment_indent_level ();
297 
298       indent (os);
299       os << class_name () << " object";
300       if (is_array)
301         os << " array";
302       os << " with properties:";
303       newline (os);
304       if (! Vcompact_format)
305         newline (os);
306 
307       increment_indent_level ();
308 
309       std::map<std::string, octave::cdef_property> property_map
310         = cls.get_property_map ();
311 
312       std::size_t max_len = 0;
313       for (const auto& pname_prop : property_map)
314         {
315           // FIXME: this loop duplicates a significant portion of the
316           // loop below and the loop in Fproperties.
317 
318           const octave::cdef_property& prop = pname_prop.second;
319 
320           const std::string nm = prop.get_name ();
321 
322           octave_value acc = prop.get ("GetAccess");
323 
324           if (! acc.is_string () || acc.string_value () != "public")
325             continue;
326 
327           octave_value hid = prop.get ("Hidden");
328 
329           if (hid.bool_value ())
330             continue;
331 
332           std::size_t sz = nm.size ();
333 
334           if (sz > max_len)
335             max_len = sz;
336         }
337 
338       for (auto& pname_prop : property_map)
339         {
340           const octave::cdef_property& prop = pname_prop.second;
341 
342           const std::string nm = prop.get_name ();
343 
344           octave_value acc = prop.get ("GetAccess");
345 
346           if (! acc.is_string () || acc.string_value () != "public")
347             continue;
348 
349           octave_value hid = prop.get ("Hidden");
350 
351           if (hid.bool_value ())
352             continue;
353 
354           indent (os);
355 
356           if (is_array)
357             os << "  " << nm;
358           else
359             {
360               octave_value val = prop.get_value (object, false);
361               dim_vector dims = val.dims ();
362 
363               os << std::setw (max_len+2) << nm << ": ";
364               if (val.is_string ())
365                 os << val.string_value ();
366               else if (val.islogical ())
367                 os << val.bool_value ();
368               else
369                 os << "[" << dims.str () << " " << val.class_name () << "]";
370             }
371 
372           newline (os);
373         }
374 
375       decrement_indent_level ();
376       decrement_indent_level ();
377     }
378 }
379 
380 bool
is_instance_of(const std::string & cls_name) const381 octave_classdef::is_instance_of (const std::string& cls_name) const
382 {
383   octave::cdef_class cls = octave::lookup_class (cls_name, false, false);
384 
385   if (cls.ok ())
386     return is_superclass (cls, object.get_class ());
387 
388   return false;
389 }
390 
391 octave_value
superclass_ref(const std::string & meth,const std::string & cls)392 octave_classdef::superclass_ref (const std::string& meth,
393                                  const std::string& cls)
394 {
395   return octave_value (new octave_classdef_superclass_ref (meth, cls));
396 }
397 
398 octave_value
metaclass_query(const std::string & cls)399 octave_classdef::metaclass_query (const std::string& cls)
400 {
401   return octave::to_ov (octave::lookup_class (cls));
402 }
403 
is_classdef_method(const std::string & cname) const404 bool octave_classdef_meta::is_classdef_method (const std::string& cname) const
405 {
406   bool retval = false;
407 
408   if (object.is_method ())
409     {
410       if (cname.empty ())
411         retval = true;
412       else
413         {
414           octave::cdef_method meth (object);
415 
416           return meth.is_defined_in_class (cname);
417         }
418     }
419 
420   return retval;
421 }
422 
is_classdef_constructor(const std::string & cname) const423 bool octave_classdef_meta::is_classdef_constructor (const std::string& cname) const
424 {
425   bool retval = false;
426 
427   if (object.is_class ())
428     {
429       if (cname.empty ())
430         retval = true;
431       else
432         {
433           octave::cdef_class cls (object);
434 
435           if (cls.get_name () == cname)
436             retval = true;
437         }
438     }
439 
440   return retval;
441 }
442 
doc_string(const std::string & meth_name) const443 std::string octave_classdef_meta::doc_string (const std::string& meth_name) const
444 {
445   if (object.is_class ())
446     {
447       octave::cdef_class cls (object);
448 
449       if (meth_name.empty ())
450         return cls.doc_string ();
451 
452       octave::cdef_method cdef_meth = cls.find_method (meth_name);
453 
454       if (cdef_meth.ok ())
455         return cdef_meth.get_doc_string ();
456     }
457 
458   return "";
459 }
460 
461 octave_value_list
execute(octave::tree_evaluator & tw,int nargout,const octave_value_list & idx)462 octave_classdef_superclass_ref::execute (octave::tree_evaluator& tw,
463                                          int nargout,
464                                          const octave_value_list& idx)
465 {
466   octave_value_list retval;
467 
468   std::string meth_name;
469   bool in_constructor;
470   octave::cdef_class ctx;
471 
472   ctx = octave::get_class_context (meth_name, in_constructor);
473 
474   if (! ctx.ok ())
475     error ("superclass calls can only occur in methods or constructors");
476 
477   std::string mname = m_method_name;
478   std::string cname = m_class_name;
479 
480   // CLS is the superclass.  The lookup_class function handles
481   // pkg.class names.
482 
483   octave::cdef_class cls = octave::lookup_class (cname);
484 
485   if (in_constructor)
486     {
487       if (! is_direct_superclass (cls, ctx))
488         error ("'%s' is not a direct superclass of '%s'",
489                cname.c_str (), ctx.get_name ().c_str ());
490 
491       if (! is_constructed_object (tw, mname))
492         error ("cannot call superclass constructor with variable '%s'",
493                mname.c_str ());
494 
495       octave_value sym = tw.varval (mname);
496 
497       cls.run_constructor (octave::to_cdef_ref (sym), idx);
498 
499       retval(0) = sym;
500     }
501   else
502     {
503       std::size_t pos = mname.find ('.');
504 
505       octave::cdef_object obj;
506 
507       if (pos != std::string::npos)
508         {
509           // We are looking at obj.meth.
510 
511           std::string oname = m_method_name.substr (0, pos);
512           mname = mname.substr (pos + 1);
513 
514           octave_value tval = tw.varval (oname);
515 
516           // FIXME: Can we only call superclass methods on the current
517           // object?  If so, and we are looking at something like
518           //
519           //   function meth (obj, ...)
520           //     obj.meth@superclass (...)
521           //
522           // Do we need to verify that the object that was passed to
523           // meth is the same as the object we find when looking up
524           // obj in the expression obj.meth?  If so, what is the right
525           // way to perform that check?
526 
527           if (tval.is_classdef_object ())
528             {
529               octave_classdef *cdobj = tval.classdef_object_value ();
530 
531               obj = cdobj->get_object ();
532             }
533         }
534 
535       if (mname != meth_name)
536         error ("method name mismatch ('%s' != '%s')",
537                mname.c_str (), meth_name.c_str ());
538 
539       if (! is_strict_superclass (cls, ctx))
540         error ("'%s' is not a superclass of '%s'",
541                cname.c_str (), ctx.get_name ().c_str ());
542 
543       // I see 2 possible implementations here:
544       // 1) use cdef_object::subsref with a different class
545       //    context; this avoids duplicating code, but
546       //    assumes the object is always the first argument
547       // 2) lookup the method manually and call
548       //    cdef_method::execute; this duplicates part of
549       //    logic in cdef_object::subsref, but avoid the
550       //    assumption of 1)
551       // Not being sure about the assumption of 1), I
552       // go with option 2) for the time being.
553 
554       octave::cdef_method meth = cls.find_method (meth_name, false);
555 
556       if (! meth.ok ())
557         error ("no method '%s' found in superclass '%s'",
558                meth_name.c_str (), cname.c_str ());
559 
560       retval = (obj.ok ()
561                 ? meth.execute (obj, idx, nargout, true, meth_name)
562                 : meth.execute (idx, nargout, true, meth_name));
563     }
564 
565   return retval;
566 }
567 
is_constructed_object(octave::tree_evaluator & tw,const std::string & nm)568 bool octave_classdef_superclass_ref::is_constructed_object (octave::tree_evaluator& tw,
569                                                             const std::string& nm)
570 {
571   octave_function *of = tw.current_function ();
572 
573   if (of->is_classdef_constructor ())
574     {
575       octave_user_function *uf = of->user_function_value (true);
576 
577       if (uf)
578         {
579           octave::tree_parameter_list *ret_list = uf->return_list ();
580 
581           if (ret_list && ret_list->length () == 1)
582             return (ret_list->front ()->name () == nm);
583         }
584     }
585 
586   return false;
587 }
588 
589 DEFUN (__meta_get_package__, args, ,
590        doc: /* -*- texinfo -*-
591 @deftypefn {} {} __meta_get_package__ ()
592 Undocumented internal function.
593 @end deftypefn */)
594 {
595   if (args.length () != 1)
596     print_usage ();
597 
598   std::string cname = args(0).xstring_value ("PACKAGE_NAME must be a string");
599 
600   return octave::to_ov (octave::lookup_package (cname));
601 }
602 
603 DEFUN (metaclass, args, ,
604        doc: /* -*- texinfo -*-
605 @deftypefn {} {} metaclass (obj)
606 Returns the meta.class object corresponding to the class of @var{obj}.
607 @end deftypefn */)
608 {
609   if (args.length () != 1)
610     print_usage ();
611 
612   octave::cdef_object obj = octave::to_cdef (args(0));
613 
614   return octave::to_ov (obj.get_class ());
615 }
616 
617 // FIXME: What about dynamic properties if obj is a scalar, or the
618 // properties of the class of obj if obj is an array?  Probably there
619 // should be a function to do this job so that the DEFUN is just a
620 // simple wrapper.
621 
622 DEFUN (properties, args, nargout,
623        doc: /* -*- texinfo -*-
624 @deftypefn  {} {} properties (@var{class_name})
625 @deftypefnx {} {} properties (@var{obj})
626 @deftypefnx {} {@var{plist} =} properties (@dots{})
627 Return or display the public properties for the named class
628 @var{class_name} or classdef object @var{obj}.
629 
630 If an output value is requested, return the list of property names in a
631 cell array.
632 
633 Programming Note: Property names are returned if the @code{GetAccess}
634 attribute is public and if the @code{Hidden} attribute is false.
635 @seealso{methods}
636 @end deftypefn */)
637 {
638   if (args.length () != 1)
639     print_usage ();
640 
641   octave_value arg = args(0);
642 
643   std::string class_name;
644 
645   if (arg.isobject ())
646     class_name = arg.class_name ();
647   else if (arg.is_string ())
648     class_name = arg.string_value ();
649   else
650     err_wrong_type_arg ("properties", arg);
651 
652   octave::cdef_class cls;
653 
654   cls = octave::lookup_class (class_name, false, true);
655 
656   if (! cls.ok ())
657     error ("invalid class: %s", class_name.c_str ());
658 
659   std::map<std::string, octave::cdef_property> property_map =
660     cls.get_property_map ();
661 
662   std::list<std::string> property_names;
663 
664   for (const auto& pname_prop : property_map)
665     {
666       // FIXME: this loop duplicates a significant portion of the loops
667       // in octave_classdef::print_raw.
668 
669       const octave::cdef_property& prop = pname_prop.second;
670 
671       std::string nm = prop.get_name ();
672 
673       octave_value acc = prop.get ("GetAccess");
674 
675       if (! acc.is_string () || acc.string_value () != "public")
676         continue;
677 
678       octave_value hid = prop.get ("Hidden");
679 
680       if (hid.bool_value ())
681         continue;
682 
683       property_names.push_back (nm);
684     }
685 
686   if (nargout > 0)
687     return octave_value (Cell (string_vector (property_names)));
688 
689   octave_stdout << "properties for class " << class_name << ":\n\n";
690 
691   for (const auto& nm : property_names)
692     octave_stdout << "  " << nm << "\n";
693 
694   octave_stdout << std::endl;
695 
696   return octave_value ();
697 }
698 
699 /*
700 %!assert (properties ("inputParser"),
701 %!        {"CaseSensitive"; "FunctionName"; "KeepUnmatched";
702 %!         "Parameters"; "PartialMatching"; "Results";
703 %!         "StructExpand"; "Unmatched"; "UsingDefaults"});
704 */
705 
706 // FIXME: Need to implement the -full option.
707 
708 DEFMETHOD (__methods__, interp, args, ,
709            doc: /* -*- texinfo -*-
710 @deftypefn  {} {} __methods__ (@var{x})
711 @deftypefnx {} {} __methods__ ("classname")
712 Internal function.
713 
714 Implements @code{methods} for Octave class objects and classnames.
715 @seealso{methods}
716 @end deftypefn */)
717 {
718   // Input validation has already been done in methods.m.
719   octave_value arg = args(0);
720 
721   std::string class_name;
722 
723   if (arg.isobject ())
724     class_name = arg.class_name ();
725   else if (arg.is_string ())
726     class_name = arg.string_value ();
727   else
728     err_wrong_type_arg ("__methods__", arg);
729 
730   string_vector sv;
731 
732   octave::cdef_class cls = octave::lookup_class (class_name, false, true);
733 
734   if (cls.ok ())
735     {
736       std::map<std::string, octave::cdef_method> method_map
737         = cls.get_method_map (false, true);
738 
739       std::list<std::string> method_names;
740 
741       for (const auto& nm_mthd : method_map)
742         {
743           std::string nm = nm_mthd.first;
744 
745           method_names.push_back (nm);
746         }
747 
748       sv = string_vector (method_names);
749     }
750 
751   // The following will also find methods for legacy @CLASS objects.
752 
753   octave::load_path& lp = interp.get_load_path ();
754 
755   sv.append (lp.methods (class_name));
756 
757   return ovl (Cell (sv));
758 }
759 
760 /*
761 ;;; Local Variables: ***
762 ;;; mode: C++ ***
763 ;;; End: ***
764 */
765