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