Mercurial > octave
changeset 31809:32acdc376a36
maint: Remove unnecessary indent after OCTAVE_BEGIN_NAMESPACE in libinterp/
* ov.cc, bp-table.h, pt-eval.cc:
Remove unnecessary indent after OCTAVE_BEGIN_NAMESPACE in libinterp/.
author | Rik <rik@octave.org> |
---|---|
date | Thu, 02 Feb 2023 12:32:15 -0800 |
parents | 90621682cc03 |
children | ce32aca67acf |
files | libinterp/octave-value/ov.cc libinterp/parse-tree/bp-table.h libinterp/parse-tree/pt-eval.cc |
diffstat | 3 files changed, 6140 insertions(+), 6140 deletions(-) [+] |
line wrap: on
line diff
--- a/libinterp/octave-value/ov.cc Thu Feb 02 12:25:51 2023 -0800 +++ b/libinterp/octave-value/ov.cc Thu Feb 02 12:32:15 2023 -0800 @@ -2658,950 +2658,950 @@ OCTAVE_BEGIN_NAMESPACE(octave) - OCTAVE_NORETURN static void - err_binary_op (const std::string& on, const std::string& tn1, - const std::string& tn2) - { - error ("binary operator '%s' not implemented for '%s' by '%s' operations", - on.c_str (), tn1.c_str (), tn2.c_str ()); - } - - OCTAVE_NORETURN static void - err_binary_op_conv (const std::string& on) - { - error ("type conversion failed for binary operator '%s'", on.c_str ()); - } - - octave_value - binary_op (type_info& ti, octave_value::binary_op op, - const octave_value& v1, const octave_value& v2) - { - octave_value retval; - - int t1 = v1.type_id (); - int t2 = v2.type_id (); - - if (t1 == octave_class::static_type_id () - || t2 == octave_class::static_type_id () - || t1 == octave_classdef::static_type_id () - || t2 == octave_classdef::static_type_id ()) - { - type_info::binary_class_op_fcn f = ti.lookup_binary_class_op (op); - - if (! f) - err_binary_op (octave_value::binary_op_as_string (op), - v1.class_name (), v2.class_name ()); - +OCTAVE_NORETURN static void +err_binary_op (const std::string& on, const std::string& tn1, + const std::string& tn2) +{ + error ("binary operator '%s' not implemented for '%s' by '%s' operations", + on.c_str (), tn1.c_str (), tn2.c_str ()); +} + +OCTAVE_NORETURN static void +err_binary_op_conv (const std::string& on) +{ + error ("type conversion failed for binary operator '%s'", on.c_str ()); +} + +octave_value +binary_op (type_info& ti, octave_value::binary_op op, + const octave_value& v1, const octave_value& v2) +{ + octave_value retval; + + int t1 = v1.type_id (); + int t2 = v2.type_id (); + + if (t1 == octave_class::static_type_id () + || t2 == octave_class::static_type_id () + || t1 == octave_classdef::static_type_id () + || t2 == octave_classdef::static_type_id ()) + { + type_info::binary_class_op_fcn f = ti.lookup_binary_class_op (op); + + if (! f) + err_binary_op (octave_value::binary_op_as_string (op), + v1.class_name (), v2.class_name ()); + + retval = f (v1, v2); + } + else + { + // FIXME: we need to handle overloading operators for built-in + // classes (double, char, int8, etc.) + + type_info::binary_op_fcn f + = ti.lookup_binary_op (op, t1, t2); + + if (f) + retval = f (v1.get_rep (), v2.get_rep ()); + else + { + octave_value tv1; + octave_base_value::type_conv_info cf1 + = v1.numeric_conversion_function (); + + octave_value tv2; + octave_base_value::type_conv_info cf2 + = v2.numeric_conversion_function (); + + // Try biased (one-sided) conversions first. + if (cf2.type_id () >= 0 + && ti.lookup_binary_op (op, t1, cf2.type_id ())) + cf1 = nullptr; + else if (cf1.type_id () >= 0 + && ti.lookup_binary_op (op, cf1.type_id (), t2)) + cf2 = nullptr; + + if (cf1) + { + octave_base_value *tmp = cf1 (v1.get_rep ()); + + if (! tmp) + err_binary_op_conv (octave_value::binary_op_as_string (op)); + + tv1 = octave_value (tmp); + t1 = tv1.type_id (); + } + else + tv1 = v1; + + if (cf2) + { + octave_base_value *tmp = cf2 (v2.get_rep ()); + + if (! tmp) + err_binary_op_conv (octave_value::binary_op_as_string (op)); + + tv2 = octave_value (tmp); + t2 = tv2.type_id (); + } + else + tv2 = v2; + + if (cf1 || cf2) + { + retval = binary_op (op, tv1, tv2); + } + else + { + //demote double -> single and try again + cf1 = tv1.numeric_demotion_function (); + + cf2 = tv2.numeric_demotion_function (); + + // Try biased (one-sided) conversions first. + if (cf2.type_id () >= 0 + && ti.lookup_binary_op (op, t1, cf2.type_id ())) + cf1 = nullptr; + else if (cf1.type_id () >= 0 + && ti.lookup_binary_op (op, cf1.type_id (), t2)) + cf2 = nullptr; + + if (cf1) + { + octave_base_value *tmp = cf1 (tv1.get_rep ()); + + if (! tmp) + err_binary_op_conv (octave_value::binary_op_as_string (op)); + + tv1 = octave_value (tmp); + t1 = tv1.type_id (); + } + + if (cf2) + { + octave_base_value *tmp = cf2 (tv2.get_rep ()); + + if (! tmp) + err_binary_op_conv (octave_value::binary_op_as_string (op)); + + tv2 = octave_value (tmp); + t2 = tv2.type_id (); + } + + if (! cf1 && ! cf2) + err_binary_op (octave_value::binary_op_as_string (op), + v1.type_name (), v2.type_name ()); + + f = ti.lookup_binary_op (op, t1, t2); + + if (! f) + err_binary_op (octave_value::binary_op_as_string (op), + v1.type_name (), v2.type_name ()); + + retval = f (tv1.get_rep (), tv2.get_rep ()); + } + } + } + + return retval; +} + +octave_value +binary_op (octave_value::binary_op op, const octave_value& v1, + const octave_value& v2) +{ + type_info& ti = __get_type_info__ (); + + return binary_op (ti, op, v1, v2); +} + +static octave_value +decompose_binary_op (type_info& ti, octave_value::compound_binary_op op, + const octave_value& v1, const octave_value& v2) +{ + switch (op) + { + case octave_value::op_trans_mul: + return binary_op (octave_value::op_mul, + unary_op (octave_value::op_transpose, v1), v2); + + case octave_value::op_mul_trans: + return binary_op (ti, octave_value::op_mul, + v1, unary_op (octave_value::op_transpose, v2)); + + case octave_value::op_herm_mul: + return binary_op (ti, octave_value::op_mul, + unary_op (octave_value::op_hermitian, v1), v2); + + case octave_value::op_mul_herm: + return binary_op (ti, octave_value::op_mul, + v1, unary_op (octave_value::op_hermitian, v2)); + + case octave_value::op_trans_ldiv: + return binary_op (ti, octave_value::op_ldiv, + unary_op (octave_value::op_transpose, v1), v2); + + case octave_value::op_herm_ldiv: + return binary_op (ti, octave_value::op_ldiv, + unary_op (octave_value::op_hermitian, v1), v2); + + case octave_value::op_el_not_and: + return binary_op (ti, octave_value::op_el_and, + unary_op (octave_value::op_not, v1), v2); + + case octave_value::op_el_not_or: + return binary_op (ti, octave_value::op_el_or, + unary_op (octave_value::op_not, v1), v2); + + case octave_value::op_el_and_not: + return binary_op (ti, octave_value::op_el_and, + v1, unary_op (octave_value::op_not, v2)); + + case octave_value::op_el_or_not: + return binary_op (ti, octave_value::op_el_or, + v1, unary_op (octave_value::op_not, v2)); + + default: + error ("invalid compound operator"); + } +} + +octave_value +binary_op (type_info& ti, octave_value::compound_binary_op op, + const octave_value& v1, const octave_value& v2) +{ + octave_value retval; + + int t1 = v1.type_id (); + int t2 = v2.type_id (); + + if (t1 == octave_class::static_type_id () + || t2 == octave_class::static_type_id () + || t1 == octave_classdef::static_type_id () + || t2 == octave_classdef::static_type_id ()) + { + type_info::binary_class_op_fcn f = ti.lookup_binary_class_op (op); + + if (f) retval = f (v1, v2); - } - else - { - // FIXME: we need to handle overloading operators for built-in - // classes (double, char, int8, etc.) - - type_info::binary_op_fcn f - = ti.lookup_binary_op (op, t1, t2); - - if (f) - retval = f (v1.get_rep (), v2.get_rep ()); - else - { - octave_value tv1; - octave_base_value::type_conv_info cf1 - = v1.numeric_conversion_function (); - - octave_value tv2; - octave_base_value::type_conv_info cf2 - = v2.numeric_conversion_function (); - - // Try biased (one-sided) conversions first. - if (cf2.type_id () >= 0 - && ti.lookup_binary_op (op, t1, cf2.type_id ())) - cf1 = nullptr; - else if (cf1.type_id () >= 0 - && ti.lookup_binary_op (op, cf1.type_id (), t2)) - cf2 = nullptr; - - if (cf1) - { - octave_base_value *tmp = cf1 (v1.get_rep ()); - - if (! tmp) - err_binary_op_conv (octave_value::binary_op_as_string (op)); - - tv1 = octave_value (tmp); - t1 = tv1.type_id (); - } - else - tv1 = v1; - - if (cf2) - { - octave_base_value *tmp = cf2 (v2.get_rep ()); - - if (! tmp) - err_binary_op_conv (octave_value::binary_op_as_string (op)); - - tv2 = octave_value (tmp); - t2 = tv2.type_id (); - } - else - tv2 = v2; - - if (cf1 || cf2) - { - retval = binary_op (op, tv1, tv2); - } - else - { - //demote double -> single and try again - cf1 = tv1.numeric_demotion_function (); - - cf2 = tv2.numeric_demotion_function (); - - // Try biased (one-sided) conversions first. - if (cf2.type_id () >= 0 - && ti.lookup_binary_op (op, t1, cf2.type_id ())) - cf1 = nullptr; - else if (cf1.type_id () >= 0 - && ti.lookup_binary_op (op, cf1.type_id (), t2)) - cf2 = nullptr; - - if (cf1) - { - octave_base_value *tmp = cf1 (tv1.get_rep ()); - - if (! tmp) - err_binary_op_conv (octave_value::binary_op_as_string (op)); - - tv1 = octave_value (tmp); - t1 = tv1.type_id (); - } - - if (cf2) - { - octave_base_value *tmp = cf2 (tv2.get_rep ()); - - if (! tmp) - err_binary_op_conv (octave_value::binary_op_as_string (op)); - - tv2 = octave_value (tmp); - t2 = tv2.type_id (); - } - - if (! cf1 && ! cf2) - err_binary_op (octave_value::binary_op_as_string (op), - v1.type_name (), v2.type_name ()); - - f = ti.lookup_binary_op (op, t1, t2); - - if (! f) - err_binary_op (octave_value::binary_op_as_string (op), - v1.type_name (), v2.type_name ()); - - retval = f (tv1.get_rep (), tv2.get_rep ()); - } - } - } - - return retval; - } - - octave_value - binary_op (octave_value::binary_op op, const octave_value& v1, - const octave_value& v2) - { - type_info& ti = __get_type_info__ (); - - return binary_op (ti, op, v1, v2); - } - - static octave_value - decompose_binary_op (type_info& ti, octave_value::compound_binary_op op, - const octave_value& v1, const octave_value& v2) - { - switch (op) - { - case octave_value::op_trans_mul: - return binary_op (octave_value::op_mul, - unary_op (octave_value::op_transpose, v1), v2); - - case octave_value::op_mul_trans: - return binary_op (ti, octave_value::op_mul, - v1, unary_op (octave_value::op_transpose, v2)); - - case octave_value::op_herm_mul: - return binary_op (ti, octave_value::op_mul, - unary_op (octave_value::op_hermitian, v1), v2); - - case octave_value::op_mul_herm: - return binary_op (ti, octave_value::op_mul, - v1, unary_op (octave_value::op_hermitian, v2)); - - case octave_value::op_trans_ldiv: - return binary_op (ti, octave_value::op_ldiv, - unary_op (octave_value::op_transpose, v1), v2); - - case octave_value::op_herm_ldiv: - return binary_op (ti, octave_value::op_ldiv, - unary_op (octave_value::op_hermitian, v1), v2); - - case octave_value::op_el_not_and: - return binary_op (ti, octave_value::op_el_and, - unary_op (octave_value::op_not, v1), v2); - - case octave_value::op_el_not_or: - return binary_op (ti, octave_value::op_el_or, - unary_op (octave_value::op_not, v1), v2); - - case octave_value::op_el_and_not: - return binary_op (ti, octave_value::op_el_and, - v1, unary_op (octave_value::op_not, v2)); - - case octave_value::op_el_or_not: - return binary_op (ti, octave_value::op_el_or, - v1, unary_op (octave_value::op_not, v2)); - - default: - error ("invalid compound operator"); - } - } - - octave_value - binary_op (type_info& ti, octave_value::compound_binary_op op, - const octave_value& v1, const octave_value& v2) - { - octave_value retval; - - int t1 = v1.type_id (); - int t2 = v2.type_id (); - - if (t1 == octave_class::static_type_id () - || t2 == octave_class::static_type_id () - || t1 == octave_classdef::static_type_id () - || t2 == octave_classdef::static_type_id ()) - { - type_info::binary_class_op_fcn f = ti.lookup_binary_class_op (op); - - if (f) - retval = f (v1, v2); - else - retval = decompose_binary_op (ti, op, v1, v2); - } - else - { - type_info::binary_op_fcn f = ti.lookup_binary_op (op, t1, t2); - - if (f) - retval = f (v1.get_rep (), v2.get_rep ()); - else - retval = decompose_binary_op (ti, op, v1, v2); - } - - return retval; - } - - octave_value - binary_op (octave_value::compound_binary_op op, - const octave_value& v1, const octave_value& v2) - { - type_info& ti = __get_type_info__ (); - - return binary_op (ti, op, v1, v2); - } - - OCTAVE_NORETURN static void - err_cat_op (const std::string& tn1, const std::string& tn2) - { - error ("concatenation operator not implemented for '%s' by '%s' operations", - tn1.c_str (), tn2.c_str ()); - } - - OCTAVE_NORETURN static void - err_cat_op_conv () - { - error ("type conversion failed for concatenation operator"); - } - - octave_value - cat_op (type_info& ti, const octave_value& v1, - const octave_value& v2, const Array<octave_idx_type>& ra_idx) - { - octave_value retval; - - // Can't rapid return for concatenation with an empty object here as - // something like cat(1,[],single([]) must return the correct type. - - int t1 = v1.type_id (); - int t2 = v2.type_id (); - - type_info::cat_op_fcn f = ti.lookup_cat_op (t1, t2); - - if (f) - retval = f (v1.get_rep (), v2.get_rep (), ra_idx); - else - { - octave_value tv1; - octave_base_value::type_conv_info cf1 = v1.numeric_conversion_function (); - - octave_value tv2; - octave_base_value::type_conv_info cf2 = v2.numeric_conversion_function (); - - // Try biased (one-sided) conversions first. - if (cf2.type_id () >= 0 && ti.lookup_cat_op (t1, cf2.type_id ())) - cf1 = nullptr; - else if (cf1.type_id () >= 0 && ti.lookup_cat_op (cf1.type_id (), t2)) - cf2 = nullptr; - - if (cf1) - { - octave_base_value *tmp = cf1 (v1.get_rep ()); - - if (! tmp) - err_cat_op_conv (); - - tv1 = octave_value (tmp); - t1 = tv1.type_id (); - } - else - tv1 = v1; - - if (cf2) - { - octave_base_value *tmp = cf2 (v2.get_rep ()); - - if (! tmp) - err_cat_op_conv (); - - tv2 = octave_value (tmp); - t2 = tv2.type_id (); - } - else - tv2 = v2; - - if (! cf1 && ! cf2) - err_cat_op (v1.type_name (), v2.type_name ()); - - retval = cat_op (ti, tv1, tv2, ra_idx); - } - - return retval; - } - - octave_value - cat_op (const octave_value& v1, const octave_value& v2, - const Array<octave_idx_type>& ra_idx) - { - type_info& ti = __get_type_info__ (); - - return cat_op (ti, v1, v2, ra_idx); - } - - // Unless the colon operator is used with a class or classdef object, - // then all arguments must be the same type or mixed with double - // values. - - static builtin_type_t - get_colon_op_type (builtin_type_t op1_type, builtin_type_t op2_type) - { - if (op1_type == op2_type) - return op1_type; - - if (op1_type == btyp_double) - return op2_type; - - if (op2_type == btyp_double) - return op1_type; - - return btyp_unknown; - } - - static builtin_type_t - get_colon_op_type (const octave_value& base, const octave_value& increment, - const octave_value& limit) - { - builtin_type_t typ - = get_colon_op_type (base.builtin_type (), increment.builtin_type ()); - - if (typ == btyp_unknown) - return typ; - - return get_colon_op_type (typ, limit.builtin_type ()); - } - - // This check depends on the type of VAL either being the expected - // integer type or a double value. - - template <typename T> - static void - check_colon_operand (const octave_value& val, const char *op_str) - { - if (! val.is_double_type ()) - return; - - double dval = val.double_value (); - double intpart; - static const double out_of_range_top - = static_cast<double> (std::numeric_limits<typename T::val_type>::max ()) - + 1.; - - if (dval >= out_of_range_top - || dval < std::numeric_limits<typename T::val_type>::min () - || std::modf (dval, &intpart) != 0.0) - error ("colon operator %s invalid (not an integer or out of range for given integer type)", op_str); - } - - // Return the difference between two unsigned integers as an unsigned - // integer of the same type. - - template <typename UT, - typename std::enable_if<(std::is_integral<UT>::value - && std::is_unsigned<UT>::value), - bool>::type = true> - UT - integer_difference (UT a, UT b) - { - return a > b ? a - b : b - a; - } - - // Return the difference between two signed integers as an unsigned - // integer corresponding to the signed type. - - template <typename ST, - typename UT = typename std::make_unsigned<ST>::type, - typename std::enable_if<(std::is_integral<ST>::value - && std::is_signed<ST>::value), - bool>::type = true> - UT - integer_difference (ST a, ST b) - { - // Map to unsigned. - // Idea from https://stackoverflow.com/questions/10589559 - - static const UT offset - = UT (0) - static_cast<UT> (std::numeric_limits<ST>::min ()); - - UT au = static_cast<UT> (a) + offset; - UT bu = static_cast<UT> (b) + offset; - - return integer_difference (au, bu); - } - - // Number of elements in an integer range taking care to avoid - // overflow. Base and limit are of the same type. If they are - // unsigned, then increment is also of the same type. If they are - // signed, then the type of increment is the unsigned type - // corresponding to T. Assumes that the base and limit values are - // consistent with the sign of the original increment (not an empty - // range) so we can calculate numel with the absolute value of the - // increment and the absolute difference between the base and limit - // values. - - template <typename T, - typename UT = typename std::make_unsigned<T>::type, - typename std::enable_if<std::is_integral<T>::value, - bool>::type = true> - octave_idx_type - range_numel_aux (T base, UT unsigned_increment, T limit) - { - // Adding one to DIFF/INCREMENT may overflow, so check whether it is - // out of range before adding. - - UT nel_m1 = integer_difference (limit, base) / unsigned_increment; - - // FIXME: fix error message. - if (nel_m1 > std::numeric_limits<octave_idx_type>::max () - 1) - error ("too many elements for range!"); - - return static_cast<octave_idx_type> (nel_m1) + 1; - } - - // Convert signed range increment to unsigned. - - template <typename ST, - typename UT = typename std::make_unsigned<ST>::type, - typename std::enable_if<(std::is_integral<ST>::value - && std::is_signed<ST>::value), - bool>::type = true> - UT - range_increment (ST increment) - { - return (increment < 0 - ? UT (0) - static_cast<UT> (increment) - : static_cast<UT> (increment)); - } - - // "Convert" unsigned range increment to unsigned. A no-op, but - // needed to provide a consistent interface for other template - // functions. - - template <typename T, - typename UT = typename std::make_unsigned<T>::type, - typename std::enable_if<(std::is_integral<UT>::value - && std::is_unsigned<UT>::value), - bool>::type = true> - UT - range_increment (UT increment) - { - return increment; - } - - // Convert double range increment to unsigned. Enable by return type. - - template <typename T, - typename UT = typename std::make_unsigned<T>::type> - typename std::enable_if<(std::is_integral<UT>::value - && std::is_unsigned<UT>::value), UT>::type - range_increment (double increment) - { - double abs_increment = std::abs (increment); - - return static_cast<UT> (abs_increment); - } - - // Number of elements in an integer range base:increment:limit. Base, - // increment, and limit are of the same signed type. - - template <typename ST, - typename std::enable_if<(std::is_integral<ST>::value - && std::is_signed<ST>::value), - bool>::type = true> - octave_idx_type - range_numel (ST base, ST increment, ST limit) - { - typedef typename std::make_unsigned<ST>::type UT; - - if (increment == 0 - || (increment > 0 && base > limit) - || (increment < 0 && base < limit)) - return 0; - - UT unsigned_increment = range_increment<ST> (increment); - - return range_numel_aux (base, unsigned_increment, limit); - } - - // Number of elements in an integer range base:increment:limit. Base, - // increment, and limit are unsigned and of the same type. - - template <typename UT, - typename std::enable_if<(std::is_integral<UT>::value - && std::is_unsigned<UT>::value), - bool>::type = true> - octave_idx_type - range_numel (UT base, UT increment, UT limit) - { - // Unsigned, INCREMENT is always >= 0. - if (increment == 0 || base > limit) - return 0; - - return range_numel_aux (base, increment, limit); - } - - // Number of elements in an integer range base:increment:limit. Base - // and limit are of the same type and increment is a double value. - - template <typename T, - typename UT = typename std::make_unsigned<T>::type, - typename std::enable_if<std::is_integral<T>::value, - bool>::type = true> - octave_idx_type - range_numel (T base, double increment, T limit) - { - double intpart; - if (math::isnan (increment) || std::modf (increment, &intpart) != 0.0) - error ("colon operator increment invalid (not an integer)"); - - if (increment == 0 - || (increment > 0 && base > limit) - || (increment < 0 && base < limit)) - return 0; - - static const double out_of_range_top - = static_cast<double> (std::numeric_limits<UT>::max ()) + 1.; - - double abs_increment = std::abs (increment); - - // Technically, this condition should be - // `abs_increment > std::numeric_limits<UT>::max ()`. - // But intmax('uint64') is not representable exactly as floating point - // number. Instead, it "rounds" up by 1 to 2^64. To account for - // this, use the following expression which works for all unsigned - // integer types. - if (abs_increment >= out_of_range_top) - return 1; - - UT unsigned_increment = range_increment<T> (increment); - - return range_numel_aux (base, unsigned_increment, limit); - } - - // Make a range from integer values. Increment may be integer or double. - - template <typename T, - typename IT, - typename std::enable_if<(std::is_integral<T>::value - && std::is_arithmetic<IT>::value), - bool>::type = true> - octave_value - make_int_range (T base, IT increment, T limit) - { - octave_idx_type nel = range_numel (base, increment, limit); - - // For now, we create arrays instead of range<T> for all types - // except double. - - Array<octave_int<T>> result (dim_vector (1, nel)); - - if (nel > 0) - { - typedef typename std::make_unsigned<T>::type UT; - - UT unsigned_increment = range_increment<T> (increment); - - T val = base; - result.xelem (0) = val; - - if (limit > base) - { - for (octave_idx_type i = 1; i < nel; i++) - { - val += unsigned_increment; - result.xelem (i) = val; - } - } - else - { - for (octave_idx_type i = 1; i < nel; i++) - { - val -= unsigned_increment; - result.xelem (i) = val; - } - } - } - - return octave_value (result); - } - - // Make a range from floating point values. - - // FIXME: Try again to define memory efficient range classes for - // integer and floating point values? Maybe with the templates - // defined in this file we could do that in a reasonable way? - // Regardless of that, it might be good to provide special treatment - // of colon expressions in FOR loops so that we can eliminate the - // "is_for_cmd_expr / force_range" flag from the parser and the - // octave_value constructors for range objects. - - // NOTE: We define this function separately for float and double so - // that we can avoid having to instantiate ov_range<float>. We DO - // instantiate range<float> but only so we can take advantage of the - // range<T> class to generate the corresponding array of float values - // and not have to duplicate that code here. - - template <typename T, - typename std::enable_if<std::is_same<T, double>::value, - bool>::type = true> - octave_value - make_float_range (T base, T increment, T limit, bool is_for_cmd_expr) - { - if (math::isnan (base) - || math::isnan (increment) - || math::isnan (limit)) - return octave_value (numeric_limits<T>::NaN ()); - - if (increment == 0 - || (increment > 0 && base > limit) - || (increment < 0 && base < limit)) - return octave_value (Array<T> (dim_vector (1, 0))); - - // At this point, we know that the base and limit values are - // consistent with the sign of the increment (not an empty range). - - range<T> r (base, increment, limit); - - if (! is_for_cmd_expr && ! r.is_storable ()) - error ("range with infinite number of elements cannot be stored"); - - return octave_value (r, is_for_cmd_expr); - } - - template <typename T, - typename std::enable_if<std::is_same<T, float>::value, - bool>::type = true> - octave_value - make_float_range (T base, T increment, T limit, bool is_for_cmd_expr) - { - if (math::isnan (base) - || math::isnan (increment) - || math::isnan (limit)) - return octave_value (numeric_limits<T>::NaN ()); - - if (increment == 0 - || (increment > 0 && base > limit) - || (increment < 0 && base < limit)) - return octave_value (Array<T> (dim_vector (1, 0))); - - // At this point, we know that the base and limit values are - // consistent with the sign of the increment (not an empty range). - - range<T> r (base, increment, limit); - - if (! is_for_cmd_expr && ! r.is_storable ()) - error ("range with infinite number of elements cannot be stored"); - - return octave_value (r.array_value ()); - } - - template <typename T, - typename std::enable_if<(std::is_same<T, octave_int8>::value - || std::is_same<T, octave_uint8>::value - || std::is_same<T, octave_int16>::value - || std::is_same<T, octave_uint16>::value - || std::is_same<T, octave_int32>::value - || std::is_same<T, octave_uint32>::value - || std::is_same<T, octave_int64>::value - || std::is_same<T, octave_uint64>::value), - bool>::type = true> - octave_value - make_int_range (const octave_value& base, const octave_value& increment, - const octave_value& limit) - { - if (base.isempty () || increment.isempty () || limit.isempty ()) - return octave_value (Array<T> (dim_vector (1, 0))); - - check_colon_operand<T> (base, "lower bound"); - check_colon_operand<T> (limit, "upper bound"); - - typename T::val_type base_val = octave_value_extract<T> (base).value (); - typename T::val_type limit_val = octave_value_extract<T> (limit).value (); - - if (increment.is_double_type ()) - { - double increment_val = increment.double_value (); - - return make_int_range (base_val, increment_val, limit_val); - } - - check_colon_operand<T> (increment, "increment"); - - typename T::val_type increment_val - = octave_value_extract<T> (increment).value (); - - return make_int_range (base_val, increment_val, limit_val); - } - - template <typename T, - typename std::enable_if<std::is_floating_point<T>::value, - bool>::type = true> - octave_value - make_float_range (const octave_value& base, const octave_value& increment, - const octave_value& limit, bool is_for_cmd_expr) - { - if (base.isempty () || increment.isempty () || limit.isempty ()) - return octave_value (Array<T> (dim_vector (1, 0))); - - T base_val = octave_value_extract<T> (base); - T increment_val = octave_value_extract<T> (increment); - T limit_val = octave_value_extract<T> (limit); - - return make_float_range (base_val, increment_val, limit_val, - is_for_cmd_expr); - } - - - octave_value - make_char_range (const octave_value& base, const octave_value& increment, + else + retval = decompose_binary_op (ti, op, v1, v2); + } + else + { + type_info::binary_op_fcn f = ti.lookup_binary_op (op, t1, t2); + + if (f) + retval = f (v1.get_rep (), v2.get_rep ()); + else + retval = decompose_binary_op (ti, op, v1, v2); + } + + return retval; +} + +octave_value +binary_op (octave_value::compound_binary_op op, + const octave_value& v1, const octave_value& v2) +{ + type_info& ti = __get_type_info__ (); + + return binary_op (ti, op, v1, v2); +} + +OCTAVE_NORETURN static void +err_cat_op (const std::string& tn1, const std::string& tn2) +{ + error ("concatenation operator not implemented for '%s' by '%s' operations", + tn1.c_str (), tn2.c_str ()); +} + +OCTAVE_NORETURN static void +err_cat_op_conv () +{ + error ("type conversion failed for concatenation operator"); +} + +octave_value +cat_op (type_info& ti, const octave_value& v1, + const octave_value& v2, const Array<octave_idx_type>& ra_idx) +{ + octave_value retval; + + // Can't rapid return for concatenation with an empty object here as + // something like cat(1,[],single([]) must return the correct type. + + int t1 = v1.type_id (); + int t2 = v2.type_id (); + + type_info::cat_op_fcn f = ti.lookup_cat_op (t1, t2); + + if (f) + retval = f (v1.get_rep (), v2.get_rep (), ra_idx); + else + { + octave_value tv1; + octave_base_value::type_conv_info cf1 = v1.numeric_conversion_function (); + + octave_value tv2; + octave_base_value::type_conv_info cf2 = v2.numeric_conversion_function (); + + // Try biased (one-sided) conversions first. + if (cf2.type_id () >= 0 && ti.lookup_cat_op (t1, cf2.type_id ())) + cf1 = nullptr; + else if (cf1.type_id () >= 0 && ti.lookup_cat_op (cf1.type_id (), t2)) + cf2 = nullptr; + + if (cf1) + { + octave_base_value *tmp = cf1 (v1.get_rep ()); + + if (! tmp) + err_cat_op_conv (); + + tv1 = octave_value (tmp); + t1 = tv1.type_id (); + } + else + tv1 = v1; + + if (cf2) + { + octave_base_value *tmp = cf2 (v2.get_rep ()); + + if (! tmp) + err_cat_op_conv (); + + tv2 = octave_value (tmp); + t2 = tv2.type_id (); + } + else + tv2 = v2; + + if (! cf1 && ! cf2) + err_cat_op (v1.type_name (), v2.type_name ()); + + retval = cat_op (ti, tv1, tv2, ra_idx); + } + + return retval; +} + +octave_value +cat_op (const octave_value& v1, const octave_value& v2, + const Array<octave_idx_type>& ra_idx) +{ + type_info& ti = __get_type_info__ (); + + return cat_op (ti, v1, v2, ra_idx); +} + +// Unless the colon operator is used with a class or classdef object, +// then all arguments must be the same type or mixed with double +// values. + +static builtin_type_t +get_colon_op_type (builtin_type_t op1_type, builtin_type_t op2_type) +{ + if (op1_type == op2_type) + return op1_type; + + if (op1_type == btyp_double) + return op2_type; + + if (op2_type == btyp_double) + return op1_type; + + return btyp_unknown; +} + +static builtin_type_t +get_colon_op_type (const octave_value& base, const octave_value& increment, const octave_value& limit) - { - octave_value retval; - - bool dq_str = (base.is_dq_string () || increment.is_dq_string () - || limit.is_dq_string ()); - - char type = dq_str ? '"' : '\''; - - if (base.isempty () || increment.isempty () || limit.isempty ()) - retval = octave_value ("", type); - else - { - Matrix mtx_base = base.matrix_value (true); - Matrix mtx_increment = increment.matrix_value (true); - Matrix mtx_limit = limit.matrix_value (true); - - range<double> tmp (mtx_base(0), mtx_increment(0), mtx_limit(0)); - - retval = octave_value (tmp); - } - - return retval.convert_to_str (false, true, type); - } - - octave_value - colon_op (const octave_value& base, const octave_value& increment_arg, - const octave_value& limit, bool is_for_cmd_expr) - { - if (base.isobject () || increment_arg.isobject () || limit.isobject ()) - { - octave_value_list tmp1; - - if (increment_arg.is_defined ()) - { - tmp1(2) = limit; - tmp1(1) = increment_arg; - tmp1(0) = base; - } - else - { - tmp1(1) = limit; - tmp1(0) = base; - } - - interpreter& interp = __get_interpreter__ (); - - symbol_table& symtab = interp.get_symbol_table (); - - octave_value fcn = symtab.find_function ("colon", tmp1); - - if (fcn.is_defined ()) - { - octave_value_list tmp2 = interp.feval (fcn, tmp1, 1); - - return tmp2(0); - } - } - - octave_value increment - = increment_arg.is_defined () ? increment_arg : octave_value (1.0); - - if (base.numel () > 1 || limit.numel () > 1 || increment.numel () > 1) - warning_with_id ("Octave:colon-nonscalar-argument", - "colon arguments should be scalars"); - - if (base.iscomplex () || limit.iscomplex () || increment.iscomplex ()) - warning_with_id ("Octave:colon-complex-argument", - "imaginary part of complex colon arguments is ignored"); - - // FIXME: is there a better way to do this job, maybe using type traits? - - builtin_type_t type_id = get_colon_op_type (base, increment, limit); - - // For compatibility with Matlab, don't allow the range used in - // a FOR loop expression to be converted to a Matrix. - - // For now, these functions create arrays instead of range<T> for - // all types except double. - - switch (type_id) - { - case btyp_double: - case btyp_complex: - return make_float_range<double> (base, increment, limit, is_for_cmd_expr); - - case btyp_float: - case btyp_float_complex: - return make_float_range<float> (base, increment, limit, is_for_cmd_expr); - - case btyp_int8: - return make_int_range<octave_int8> (base, increment, limit); - - case btyp_int16: - return make_int_range<octave_int16> (base, increment, limit); - - case btyp_int32: - return make_int_range<octave_int32> (base, increment, limit); - - case btyp_int64: - return make_int_range<octave_int64> (base, increment, limit); - - case btyp_uint8: - return make_int_range<octave_uint8> (base, increment, limit); - - case btyp_uint16: - return make_int_range<octave_uint16> (base, increment, limit); - - case btyp_uint32: - return make_int_range<octave_uint32> (base, increment, limit); - - case btyp_uint64: - return make_int_range<octave_uint64> (base, increment, limit); - - case btyp_char: - return make_char_range (base, increment, limit); - - case btyp_unknown: - error ("incompatible types found in range expression"); - - default: - error ("invalid types found in range expression"); - } - - return octave_value (); - } - - OCTAVE_NORETURN static void - err_unary_op_conv (const std::string& on) - { - error ("type conversion failed for unary operator '%s'", on.c_str ()); - } - - octave_value - unary_op (type_info& ti, octave_value::unary_op op, - const octave_value& v) - { - octave_value retval; - - int t = v.type_id (); - - if (t == octave_class::static_type_id () - || t == octave_classdef::static_type_id ()) - { - type_info::unary_class_op_fcn f = ti.lookup_unary_class_op (op); - - if (! f) - err_unary_op (octave_value::unary_op_as_string (op), v.class_name ()); - - retval = f (v); - } - else - { - // FIXME: we need to handle overloading operators for built-in - // classes (double, char, int8, etc.) - - type_info::unary_op_fcn f = ti.lookup_unary_op (op, t); - - if (f) - retval = f (v.get_rep ()); - else - { - octave_value tv; - octave_base_value::type_conv_fcn cf - = v.numeric_conversion_function (); - - if (! cf) - err_unary_op (octave_value::unary_op_as_string (op), - v.type_name ()); - - octave_base_value *tmp = cf (v.get_rep ()); - - if (! tmp) - err_unary_op_conv (octave_value::unary_op_as_string (op)); - - tv = octave_value (tmp); - retval = unary_op (op, tv); - } - } - - return retval; - } - - octave_value - unary_op (octave_value::unary_op op, const octave_value& v) - { - type_info& ti = __get_type_info__ (); - - return unary_op (ti, op, v); - } +{ + builtin_type_t typ + = get_colon_op_type (base.builtin_type (), increment.builtin_type ()); + + if (typ == btyp_unknown) + return typ; + + return get_colon_op_type (typ, limit.builtin_type ()); +} + +// This check depends on the type of VAL either being the expected +// integer type or a double value. + +template <typename T> +static void +check_colon_operand (const octave_value& val, const char *op_str) +{ + if (! val.is_double_type ()) + return; + + double dval = val.double_value (); + double intpart; + static const double out_of_range_top + = static_cast<double> (std::numeric_limits<typename T::val_type>::max ()) + + 1.; + + if (dval >= out_of_range_top + || dval < std::numeric_limits<typename T::val_type>::min () + || std::modf (dval, &intpart) != 0.0) + error ("colon operator %s invalid (not an integer or out of range for given integer type)", op_str); +} + +// Return the difference between two unsigned integers as an unsigned +// integer of the same type. + +template <typename UT, + typename std::enable_if<(std::is_integral<UT>::value + && std::is_unsigned<UT>::value), + bool>::type = true> +UT +integer_difference (UT a, UT b) +{ + return a > b ? a - b : b - a; +} + +// Return the difference between two signed integers as an unsigned +// integer corresponding to the signed type. + +template <typename ST, + typename UT = typename std::make_unsigned<ST>::type, + typename std::enable_if<(std::is_integral<ST>::value + && std::is_signed<ST>::value), + bool>::type = true> +UT +integer_difference (ST a, ST b) +{ + // Map to unsigned. + // Idea from https://stackoverflow.com/questions/10589559 + + static const UT offset + = UT (0) - static_cast<UT> (std::numeric_limits<ST>::min ()); + + UT au = static_cast<UT> (a) + offset; + UT bu = static_cast<UT> (b) + offset; + + return integer_difference (au, bu); +} + +// Number of elements in an integer range taking care to avoid +// overflow. Base and limit are of the same type. If they are +// unsigned, then increment is also of the same type. If they are +// signed, then the type of increment is the unsigned type +// corresponding to T. Assumes that the base and limit values are +// consistent with the sign of the original increment (not an empty +// range) so we can calculate numel with the absolute value of the +// increment and the absolute difference between the base and limit +// values. + +template <typename T, + typename UT = typename std::make_unsigned<T>::type, + typename std::enable_if<std::is_integral<T>::value, + bool>::type = true> +octave_idx_type +range_numel_aux (T base, UT unsigned_increment, T limit) +{ + // Adding one to DIFF/INCREMENT may overflow, so check whether it is + // out of range before adding. + + UT nel_m1 = integer_difference (limit, base) / unsigned_increment; + + // FIXME: fix error message. + if (nel_m1 > std::numeric_limits<octave_idx_type>::max () - 1) + error ("too many elements for range!"); + + return static_cast<octave_idx_type> (nel_m1) + 1; +} + +// Convert signed range increment to unsigned. + +template <typename ST, + typename UT = typename std::make_unsigned<ST>::type, + typename std::enable_if<(std::is_integral<ST>::value + && std::is_signed<ST>::value), + bool>::type = true> +UT +range_increment (ST increment) +{ + return (increment < 0 + ? UT (0) - static_cast<UT> (increment) + : static_cast<UT> (increment)); +} + +// "Convert" unsigned range increment to unsigned. A no-op, but +// needed to provide a consistent interface for other template +// functions. + +template <typename T, + typename UT = typename std::make_unsigned<T>::type, + typename std::enable_if<(std::is_integral<UT>::value + && std::is_unsigned<UT>::value), + bool>::type = true> +UT +range_increment (UT increment) +{ + return increment; +} + +// Convert double range increment to unsigned. Enable by return type. + +template <typename T, + typename UT = typename std::make_unsigned<T>::type> +typename std::enable_if<(std::is_integral<UT>::value + && std::is_unsigned<UT>::value), UT>::type +range_increment (double increment) +{ + double abs_increment = std::abs (increment); + + return static_cast<UT> (abs_increment); +} + +// Number of elements in an integer range base:increment:limit. Base, +// increment, and limit are of the same signed type. + +template <typename ST, + typename std::enable_if<(std::is_integral<ST>::value + && std::is_signed<ST>::value), + bool>::type = true> +octave_idx_type +range_numel (ST base, ST increment, ST limit) +{ + typedef typename std::make_unsigned<ST>::type UT; + + if (increment == 0 + || (increment > 0 && base > limit) + || (increment < 0 && base < limit)) + return 0; + + UT unsigned_increment = range_increment<ST> (increment); + + return range_numel_aux (base, unsigned_increment, limit); +} + +// Number of elements in an integer range base:increment:limit. Base, +// increment, and limit are unsigned and of the same type. + +template <typename UT, + typename std::enable_if<(std::is_integral<UT>::value + && std::is_unsigned<UT>::value), + bool>::type = true> +octave_idx_type +range_numel (UT base, UT increment, UT limit) +{ + // Unsigned, INCREMENT is always >= 0. + if (increment == 0 || base > limit) + return 0; + + return range_numel_aux (base, increment, limit); +} + +// Number of elements in an integer range base:increment:limit. Base +// and limit are of the same type and increment is a double value. + +template <typename T, + typename UT = typename std::make_unsigned<T>::type, + typename std::enable_if<std::is_integral<T>::value, + bool>::type = true> +octave_idx_type +range_numel (T base, double increment, T limit) +{ + double intpart; + if (math::isnan (increment) || std::modf (increment, &intpart) != 0.0) + error ("colon operator increment invalid (not an integer)"); + + if (increment == 0 + || (increment > 0 && base > limit) + || (increment < 0 && base < limit)) + return 0; + + static const double out_of_range_top + = static_cast<double> (std::numeric_limits<UT>::max ()) + 1.; + + double abs_increment = std::abs (increment); + + // Technically, this condition should be + // `abs_increment > std::numeric_limits<UT>::max ()`. + // But intmax('uint64') is not representable exactly as floating point + // number. Instead, it "rounds" up by 1 to 2^64. To account for + // this, use the following expression which works for all unsigned + // integer types. + if (abs_increment >= out_of_range_top) + return 1; + + UT unsigned_increment = range_increment<T> (increment); + + return range_numel_aux (base, unsigned_increment, limit); +} + +// Make a range from integer values. Increment may be integer or double. + +template <typename T, + typename IT, + typename std::enable_if<(std::is_integral<T>::value + && std::is_arithmetic<IT>::value), + bool>::type = true> +octave_value +make_int_range (T base, IT increment, T limit) +{ + octave_idx_type nel = range_numel (base, increment, limit); + + // For now, we create arrays instead of range<T> for all types + // except double. + + Array<octave_int<T>> result (dim_vector (1, nel)); + + if (nel > 0) + { + typedef typename std::make_unsigned<T>::type UT; + + UT unsigned_increment = range_increment<T> (increment); + + T val = base; + result.xelem (0) = val; + + if (limit > base) + { + for (octave_idx_type i = 1; i < nel; i++) + { + val += unsigned_increment; + result.xelem (i) = val; + } + } + else + { + for (octave_idx_type i = 1; i < nel; i++) + { + val -= unsigned_increment; + result.xelem (i) = val; + } + } + } + + return octave_value (result); +} + +// Make a range from floating point values. + +// FIXME: Try again to define memory efficient range classes for +// integer and floating point values? Maybe with the templates +// defined in this file we could do that in a reasonable way? +// Regardless of that, it might be good to provide special treatment +// of colon expressions in FOR loops so that we can eliminate the +// "is_for_cmd_expr / force_range" flag from the parser and the +// octave_value constructors for range objects. + +// NOTE: We define this function separately for float and double so +// that we can avoid having to instantiate ov_range<float>. We DO +// instantiate range<float> but only so we can take advantage of the +// range<T> class to generate the corresponding array of float values +// and not have to duplicate that code here. + +template <typename T, + typename std::enable_if<std::is_same<T, double>::value, + bool>::type = true> +octave_value +make_float_range (T base, T increment, T limit, bool is_for_cmd_expr) +{ + if (math::isnan (base) + || math::isnan (increment) + || math::isnan (limit)) + return octave_value (numeric_limits<T>::NaN ()); + + if (increment == 0 + || (increment > 0 && base > limit) + || (increment < 0 && base < limit)) + return octave_value (Array<T> (dim_vector (1, 0))); + + // At this point, we know that the base and limit values are + // consistent with the sign of the increment (not an empty range). + + range<T> r (base, increment, limit); + + if (! is_for_cmd_expr && ! r.is_storable ()) + error ("range with infinite number of elements cannot be stored"); + + return octave_value (r, is_for_cmd_expr); +} + +template <typename T, + typename std::enable_if<std::is_same<T, float>::value, + bool>::type = true> +octave_value +make_float_range (T base, T increment, T limit, bool is_for_cmd_expr) +{ + if (math::isnan (base) + || math::isnan (increment) + || math::isnan (limit)) + return octave_value (numeric_limits<T>::NaN ()); + + if (increment == 0 + || (increment > 0 && base > limit) + || (increment < 0 && base < limit)) + return octave_value (Array<T> (dim_vector (1, 0))); + + // At this point, we know that the base and limit values are + // consistent with the sign of the increment (not an empty range). + + range<T> r (base, increment, limit); + + if (! is_for_cmd_expr && ! r.is_storable ()) + error ("range with infinite number of elements cannot be stored"); + + return octave_value (r.array_value ()); +} + +template <typename T, + typename std::enable_if<(std::is_same<T, octave_int8>::value + || std::is_same<T, octave_uint8>::value + || std::is_same<T, octave_int16>::value + || std::is_same<T, octave_uint16>::value + || std::is_same<T, octave_int32>::value + || std::is_same<T, octave_uint32>::value + || std::is_same<T, octave_int64>::value + || std::is_same<T, octave_uint64>::value), + bool>::type = true> +octave_value +make_int_range (const octave_value& base, const octave_value& increment, + const octave_value& limit) +{ + if (base.isempty () || increment.isempty () || limit.isempty ()) + return octave_value (Array<T> (dim_vector (1, 0))); + + check_colon_operand<T> (base, "lower bound"); + check_colon_operand<T> (limit, "upper bound"); + + typename T::val_type base_val = octave_value_extract<T> (base).value (); + typename T::val_type limit_val = octave_value_extract<T> (limit).value (); + + if (increment.is_double_type ()) + { + double increment_val = increment.double_value (); + + return make_int_range (base_val, increment_val, limit_val); + } + + check_colon_operand<T> (increment, "increment"); + + typename T::val_type increment_val + = octave_value_extract<T> (increment).value (); + + return make_int_range (base_val, increment_val, limit_val); +} + +template <typename T, + typename std::enable_if<std::is_floating_point<T>::value, + bool>::type = true> +octave_value +make_float_range (const octave_value& base, const octave_value& increment, + const octave_value& limit, bool is_for_cmd_expr) +{ + if (base.isempty () || increment.isempty () || limit.isempty ()) + return octave_value (Array<T> (dim_vector (1, 0))); + + T base_val = octave_value_extract<T> (base); + T increment_val = octave_value_extract<T> (increment); + T limit_val = octave_value_extract<T> (limit); + + return make_float_range (base_val, increment_val, limit_val, + is_for_cmd_expr); +} + + +octave_value +make_char_range (const octave_value& base, const octave_value& increment, + const octave_value& limit) +{ + octave_value retval; + + bool dq_str = (base.is_dq_string () || increment.is_dq_string () + || limit.is_dq_string ()); + + char type = dq_str ? '"' : '\''; + + if (base.isempty () || increment.isempty () || limit.isempty ()) + retval = octave_value ("", type); + else + { + Matrix mtx_base = base.matrix_value (true); + Matrix mtx_increment = increment.matrix_value (true); + Matrix mtx_limit = limit.matrix_value (true); + + range<double> tmp (mtx_base(0), mtx_increment(0), mtx_limit(0)); + + retval = octave_value (tmp); + } + + return retval.convert_to_str (false, true, type); +} + +octave_value +colon_op (const octave_value& base, const octave_value& increment_arg, + const octave_value& limit, bool is_for_cmd_expr) +{ + if (base.isobject () || increment_arg.isobject () || limit.isobject ()) + { + octave_value_list tmp1; + + if (increment_arg.is_defined ()) + { + tmp1(2) = limit; + tmp1(1) = increment_arg; + tmp1(0) = base; + } + else + { + tmp1(1) = limit; + tmp1(0) = base; + } + + interpreter& interp = __get_interpreter__ (); + + symbol_table& symtab = interp.get_symbol_table (); + + octave_value fcn = symtab.find_function ("colon", tmp1); + + if (fcn.is_defined ()) + { + octave_value_list tmp2 = interp.feval (fcn, tmp1, 1); + + return tmp2(0); + } + } + + octave_value increment + = increment_arg.is_defined () ? increment_arg : octave_value (1.0); + + if (base.numel () > 1 || limit.numel () > 1 || increment.numel () > 1) + warning_with_id ("Octave:colon-nonscalar-argument", + "colon arguments should be scalars"); + + if (base.iscomplex () || limit.iscomplex () || increment.iscomplex ()) + warning_with_id ("Octave:colon-complex-argument", + "imaginary part of complex colon arguments is ignored"); + + // FIXME: is there a better way to do this job, maybe using type traits? + + builtin_type_t type_id = get_colon_op_type (base, increment, limit); + + // For compatibility with Matlab, don't allow the range used in + // a FOR loop expression to be converted to a Matrix. + + // For now, these functions create arrays instead of range<T> for + // all types except double. + + switch (type_id) + { + case btyp_double: + case btyp_complex: + return make_float_range<double> (base, increment, limit, is_for_cmd_expr); + + case btyp_float: + case btyp_float_complex: + return make_float_range<float> (base, increment, limit, is_for_cmd_expr); + + case btyp_int8: + return make_int_range<octave_int8> (base, increment, limit); + + case btyp_int16: + return make_int_range<octave_int16> (base, increment, limit); + + case btyp_int32: + return make_int_range<octave_int32> (base, increment, limit); + + case btyp_int64: + return make_int_range<octave_int64> (base, increment, limit); + + case btyp_uint8: + return make_int_range<octave_uint8> (base, increment, limit); + + case btyp_uint16: + return make_int_range<octave_uint16> (base, increment, limit); + + case btyp_uint32: + return make_int_range<octave_uint32> (base, increment, limit); + + case btyp_uint64: + return make_int_range<octave_uint64> (base, increment, limit); + + case btyp_char: + return make_char_range (base, increment, limit); + + case btyp_unknown: + error ("incompatible types found in range expression"); + + default: + error ("invalid types found in range expression"); + } + + return octave_value (); +} + +OCTAVE_NORETURN static void +err_unary_op_conv (const std::string& on) +{ + error ("type conversion failed for unary operator '%s'", on.c_str ()); +} + +octave_value +unary_op (type_info& ti, octave_value::unary_op op, + const octave_value& v) +{ + octave_value retval; + + int t = v.type_id (); + + if (t == octave_class::static_type_id () + || t == octave_classdef::static_type_id ()) + { + type_info::unary_class_op_fcn f = ti.lookup_unary_class_op (op); + + if (! f) + err_unary_op (octave_value::unary_op_as_string (op), v.class_name ()); + + retval = f (v); + } + else + { + // FIXME: we need to handle overloading operators for built-in + // classes (double, char, int8, etc.) + + type_info::unary_op_fcn f = ti.lookup_unary_op (op, t); + + if (f) + retval = f (v.get_rep ()); + else + { + octave_value tv; + octave_base_value::type_conv_fcn cf + = v.numeric_conversion_function (); + + if (! cf) + err_unary_op (octave_value::unary_op_as_string (op), + v.type_name ()); + + octave_base_value *tmp = cf (v.get_rep ()); + + if (! tmp) + err_unary_op_conv (octave_value::unary_op_as_string (op)); + + tv = octave_value (tmp); + retval = unary_op (op, tv); + } + } + + return retval; +} + +octave_value +unary_op (octave_value::unary_op op, const octave_value& v) +{ + type_info& ti = __get_type_info__ (); + + return unary_op (ti, op, v); +} OCTAVE_END_NAMESPACE(octave)
--- a/libinterp/parse-tree/bp-table.h Thu Feb 02 12:25:51 2023 -0800 +++ b/libinterp/parse-tree/bp-table.h Thu Feb 02 12:32:15 2023 -0800 @@ -39,172 +39,172 @@ OCTAVE_BEGIN_NAMESPACE(octave) - class tree_evaluator; +class tree_evaluator; - struct bp_type - { - public: - bp_type (int l, const std::string& c) : line (l), cond (c) { } +struct bp_type +{ +public: + bp_type (int l, const std::string& c) : line (l), cond (c) { } - //-------- + //-------- + + int line; + std::string cond; +}; - int line; - std::string cond; - }; +// Interface to breakpoints. +class OCTINTERP_API bp_table +{ +public: - // Interface to breakpoints. - class OCTINTERP_API bp_table - { - public: + bp_table (tree_evaluator& tw) + : m_evaluator (tw), m_bp_set (), m_errors_that_stop (), + m_caught_that_stop (), m_warnings_that_stop () + { } + + ~bp_table () = default; + + // Set of breakpoint lines. + typedef std::set<int> bp_lines; + + typedef bp_lines::const_iterator const_bp_lines_iterator; + typedef bp_lines::iterator bp_lines_iterator; - bp_table (tree_evaluator& tw) - : m_evaluator (tw), m_bp_set (), m_errors_that_stop (), - m_caught_that_stop (), m_warnings_that_stop () - { } + typedef std::map <std::string, bp_lines> fname_line_map; + + typedef fname_line_map::const_iterator const_fname_line_map_iterator; + typedef fname_line_map::iterator fname_line_map_iterator; - ~bp_table () = default; + typedef std::map <std::string, std::list<bp_type>> fname_bp_map; + typedef fname_bp_map::const_iterator const_fname_bp_map_iterator; + typedef fname_bp_map::iterator fname_bp_map_iterator; - // Set of breakpoint lines. - typedef std::set<int> bp_lines; - - typedef bp_lines::const_iterator const_bp_lines_iterator; - typedef bp_lines::iterator bp_lines_iterator; + // Add a breakpoint at the nearest executable line in a function. + int add_breakpoint_in_function (const std::string& fname = "", + const std::string& class_name = "", + int line = 1, + const std::string& condition = ""); - typedef std::map <std::string, bp_lines> fname_line_map; - - typedef fname_line_map::const_iterator const_fname_line_map_iterator; - typedef fname_line_map::iterator fname_line_map_iterator; + // Add a set of breakpoints at the nearest executable lines in a + // function. + bp_lines add_breakpoints_in_function (const std::string& fname = "", + const std::string& class_name = "", + const bp_lines& lines = bp_lines (), + const std::string& condition = ""); - typedef std::map <std::string, std::list<bp_type>> fname_bp_map; - typedef fname_bp_map::const_iterator const_fname_bp_map_iterator; - typedef fname_bp_map::iterator fname_bp_map_iterator; + // Add a breakpoint at the nearest executable line in a file. + int add_breakpoint_in_file (const std::string& file = "", + int line = 1, + const std::string& condition = ""); - // Add a breakpoint at the nearest executable line in a function. - int add_breakpoint_in_function (const std::string& fname = "", - const std::string& class_name = "", - int line = 1, + // Add a set of breakpoints at the nearest executable lines in a + // file. + bp_lines add_breakpoints_in_file (const std::string& file = "", + const bp_lines& lines = bp_lines (), const std::string& condition = ""); - // Add a set of breakpoints at the nearest executable lines in a - // function. - bp_lines add_breakpoints_in_function (const std::string& fname = "", - const std::string& class_name = "", - const bp_lines& lines = bp_lines (), - const std::string& condition = ""); + // Remove a breakpoint from the given line in file. + int remove_breakpoint_from_function (const std::string& fname = "", + int line = 1); - // Add a breakpoint at the nearest executable line in a file. - int add_breakpoint_in_file (const std::string& file = "", - int line = 1, - const std::string& condition = ""); - - // Add a set of breakpoints at the nearest executable lines in a - // file. - bp_lines add_breakpoints_in_file (const std::string& file = "", - const bp_lines& lines = bp_lines (), - const std::string& condition = ""); + // Remove a set of breakpoints from the given lines in file. + int remove_breakpoints_from_function (const std::string& fname = "", + const bp_lines& lines = bp_lines ()); - // Remove a breakpoint from the given line in file. - int remove_breakpoint_from_function (const std::string& fname = "", - int line = 1); - - // Remove a set of breakpoints from the given lines in file. - int remove_breakpoints_from_function (const std::string& fname = "", - const bp_lines& lines = bp_lines ()); + // Remove all the breakpoints in a specified function. + bp_lines remove_all_breakpoints_from_function (const std::string& fname, + bool silent = false); - // Remove all the breakpoints in a specified function. - bp_lines remove_all_breakpoints_from_function (const std::string& fname, - bool silent = false); + // Remove a breakpoint from the given line in file. + int remove_breakpoint_from_file (const std::string& file = "", + int line = 1); - // Remove a breakpoint from the given line in file. - int remove_breakpoint_from_file (const std::string& file = "", - int line = 1); - - // Remove a set of breakpoints from the given lines in file. - int remove_breakpoints_from_file (const std::string& file = "", - const bp_lines& lines = bp_lines ()); + // Remove a set of breakpoints from the given lines in file. + int remove_breakpoints_from_file (const std::string& file = "", + const bp_lines& lines = bp_lines ()); - // Remove all the breakpoints from a file. - bp_lines remove_all_breakpoints_from_file (const std::string& file, - bool silent = false); + // Remove all the breakpoints from a file. + bp_lines remove_all_breakpoints_from_file (const std::string& file, + bool silent = false); - // Remove all the breakpoints registered with octave. - void remove_all_breakpoints (); + // Remove all the breakpoints registered with octave. + void remove_all_breakpoints (); - // Return all breakpoints. Each element of the map is a vector - // containing the breakpoints corresponding to a given function name. - fname_bp_map get_breakpoint_list (const octave_value_list& fname_list); + // Return all breakpoints. Each element of the map is a vector + // containing the breakpoints corresponding to a given function name. + fname_bp_map get_breakpoint_list (const octave_value_list& fname_list); - bool have_breakpoints () { return (! m_bp_set.empty ()); } + bool have_breakpoints () { return (! m_bp_set.empty ()); } - // Should we enter debugging for this particular error identifier? - bool debug_on_err (const std::string& id) - { - return (m_errors_that_stop.empty () || m_errors_that_stop.count (id)); - } + // Should we enter debugging for this particular error identifier? + bool debug_on_err (const std::string& id) + { + return (m_errors_that_stop.empty () || m_errors_that_stop.count (id)); + } - // Should we enter debugging for this particular identifier in a try/catch? - bool debug_on_caught (const std::string& id) - { - return (m_caught_that_stop.empty () || m_caught_that_stop.count (id)); - } + // Should we enter debugging for this particular identifier in a try/catch? + bool debug_on_caught (const std::string& id) + { + return (m_caught_that_stop.empty () || m_caught_that_stop.count (id)); + } - // Should we enter debugging for this particular warning identifier? - bool debug_on_warn (const std::string& id) - { - return (m_warnings_that_stop.empty () || m_warnings_that_stop.count (id)); - } + // Should we enter debugging for this particular warning identifier? + bool debug_on_warn (const std::string& id) + { + return (m_warnings_that_stop.empty () || m_warnings_that_stop.count (id)); + } - octave_map stop_on_err_warn_status (bool to_screen); + octave_map stop_on_err_warn_status (bool to_screen); - void dbstop_process_map_args (const octave_map& mv); + void dbstop_process_map_args (const octave_map& mv); - void dbclear_all_signals (); + void dbclear_all_signals (); - bool condition_valid (const std::string& cond); + bool condition_valid (const std::string& cond); - void parse_dbfunction_params (const char *who, - const octave_value_list& args, - std::string& fcn_name, - std::string& class_name, - bp_table::bp_lines& lines, - std::string& cond); + void parse_dbfunction_params (const char *who, + const octave_value_list& args, + std::string& fcn_name, + std::string& class_name, + bp_table::bp_lines& lines, + std::string& cond); - private: +private: - typedef std::set<std::string>::const_iterator const_bp_set_iterator; - typedef std::set<std::string>::iterator bp_set_iterator; + typedef std::set<std::string>::const_iterator const_bp_set_iterator; + typedef std::set<std::string>::iterator bp_set_iterator; - tree_evaluator& m_evaluator; + tree_evaluator& m_evaluator; - // Set of function (.m file) names containing at least one breakpoint. - std::set<std::string> m_bp_set; + // Set of function (.m file) names containing at least one breakpoint. + std::set<std::string> m_bp_set; - // Set of error and warning message IDs that cause us to stop - // *if* Vdebug_on_error / Vdebug_on_caught / Vdebug_on_warning is set. - // Empty means stop on any error / caught error / warning. - std::set<std::string> m_errors_that_stop; - std::set<std::string> m_caught_that_stop; - std::set<std::string> m_warnings_that_stop; + // Set of error and warning message IDs that cause us to stop + // *if* Vdebug_on_error / Vdebug_on_caught / Vdebug_on_warning is set. + // Empty means stop on any error / caught error / warning. + std::set<std::string> m_errors_that_stop; + std::set<std::string> m_caught_that_stop; + std::set<std::string> m_warnings_that_stop; - void set_stop_flag (const char *who, const std::string& condition, - bool on_off); + void set_stop_flag (const char *who, const std::string& condition, + bool on_off); - void process_id_list (const char *who, const std::string& condition, - const octave_value_list& args, - int nargin, int& pos, bool on_off, - std::set<std::string>& id_list); + void process_id_list (const char *who, const std::string& condition, + const octave_value_list& args, + int nargin, int& pos, bool on_off, + std::set<std::string>& id_list); - bool add_breakpoint_1 (octave_user_code *fcn, const std::string& fname, - const bp_lines& line, const std::string& condition, - bp_lines& retval); + bool add_breakpoint_1 (octave_user_code *fcn, const std::string& fname, + const bp_lines& line, const std::string& condition, + bp_lines& retval); - int remove_breakpoint_1 (octave_user_code *fcn, const std::string&, - const bp_lines& lines); + int remove_breakpoint_1 (octave_user_code *fcn, const std::string&, + const bp_lines& lines); - bp_lines remove_all_breakpoints_in_file_1 (octave_user_code *fcn, - const std::string& fname); - }; + bp_lines remove_all_breakpoints_in_file_1 (octave_user_code *fcn, + const std::string& fname); +}; OCTAVE_END_NAMESPACE(octave)
--- a/libinterp/parse-tree/pt-eval.cc Thu Feb 02 12:25:51 2023 -0800 +++ b/libinterp/parse-tree/pt-eval.cc Thu Feb 02 12:32:15 2023 -0800 @@ -75,1731 +75,1731 @@ OCTAVE_BEGIN_NAMESPACE(octave) - // Normal evaluator. - - class quit_debug_exception - { - public: - - quit_debug_exception (bool all = false) : m_all (all) { } - - quit_debug_exception (const quit_debug_exception&) = default; - - quit_debug_exception& operator = (const quit_debug_exception&) = default; - - ~quit_debug_exception () = default; - - bool all () const { return m_all; } - - private: - - bool m_all; - }; - - class debugger - { - public: - - enum execution_mode - { - EX_NORMAL = 0, - EX_CONTINUE = 1, - EX_QUIT = 2, - EX_QUIT_ALL = 3 - }; - - debugger (interpreter& interp, std::size_t level) - : m_interpreter (interp), m_level (level), - m_execution_mode (EX_NORMAL), m_in_debug_repl (false) - { } - - int server_loop (); - - void repl (const std::string& prompt = "debug> "); - - bool in_debug_repl () const { return m_in_debug_repl; } - - void dbcont () { m_execution_mode = EX_CONTINUE; } - - void dbquit (bool all = false) +// Normal evaluator. + +class quit_debug_exception +{ +public: + + quit_debug_exception (bool all = false) : m_all (all) { } + + quit_debug_exception (const quit_debug_exception&) = default; + + quit_debug_exception& operator = (const quit_debug_exception&) = default; + + ~quit_debug_exception () = default; + + bool all () const { return m_all; } + +private: + + bool m_all; +}; + +class debugger +{ +public: + + enum execution_mode { - if (all) - m_execution_mode = EX_QUIT_ALL; - else - m_execution_mode = EX_QUIT; - } - - bool quitting_debugger () const; - - private: - - interpreter& m_interpreter; - - std::size_t m_level; - execution_mode m_execution_mode; - bool m_in_debug_repl; - }; - - // FIXME: Could the debugger server_loop and repl functions be merged - // with the corresponding tree_evaluator functions or do they need to - // remain separate? They perform nearly the same functions. - - int debugger::server_loop () + EX_NORMAL = 0, + EX_CONTINUE = 1, + EX_QUIT = 2, + EX_QUIT_ALL = 3 + }; + + debugger (interpreter& interp, std::size_t level) + : m_interpreter (interp), m_level (level), + m_execution_mode (EX_NORMAL), m_in_debug_repl (false) + { } + + int server_loop (); + + void repl (const std::string& prompt = "debug> "); + + bool in_debug_repl () const { return m_in_debug_repl; } + + void dbcont () { m_execution_mode = EX_CONTINUE; } + + void dbquit (bool all = false) { - // Process events from the event queue. - - tree_evaluator& tw = m_interpreter.get_evaluator (); - - void (tree_evaluator::*server_mode_fptr) (bool) - = &tree_evaluator::server_mode; - unwind_action act (server_mode_fptr, &tw, true); - - int exit_status = 0; - - do - { - if (m_execution_mode == EX_CONTINUE || tw.dbstep_flag ()) - break; - - if (quitting_debugger ()) - break; - - try - { - // FIXME: Should we call octave_quit in the octave::sleep - // and/or command_editor::run_event_hooks functions? - - octave_quit (); - - // FIXME: Running the event queue should be decoupled from - // the command_editor. - - // FIXME: Is it OK to use command_editor::run_event_hooks - // here? It may run more than one queued function per call, - // and it seems that the checks at the top of the loop - // probably need to be done after each individual event - // function is executed. For now, maybe the simplest thing - // would be to pass a predicate function (lambda expression) - // to the command_editor::run_event_hooks and have it check - // that and break out of the eval loop(s) if the condition - // is met? - - // FIXME: We should also use a condition variable to manage - // the execution of entries in the queue and eliminate the - // need for the busy-wait loop. - - command_editor::run_event_hooks (); - - release_unreferenced_dynamic_libraries (); - - sleep (0.1); - } - catch (const interrupt_exception&) - { - octave_interrupt_state = 1; - m_interpreter.recover_from_exception (); - - // Required newline when the user does Ctrl+C at the prompt. - if (m_interpreter.interactive ()) - octave_stdout << "\n"; - } - catch (const index_exception& e) - { - m_interpreter.recover_from_exception (); - - std::cerr << "error: unhandled index exception: " - << e.message () << " -- trying to return to prompt" - << std::endl; - } - catch (const execution_exception& ee) - { - error_system& es = m_interpreter.get_error_system (); - - es.save_exception (ee); - es.display_exception (ee); - - if (m_interpreter.interactive ()) - { - m_interpreter.recover_from_exception (); - } - else - { - // We should exit with a nonzero status. - exit_status = 1; - break; - } - } - catch (const quit_debug_exception& qde) - { - if (qde.all ()) - throw; - - // Continue in this debug level. - } - catch (const std::bad_alloc&) - { - m_interpreter.recover_from_exception (); - - std::cerr << "error: out of memory -- trying to return to prompt" - << std::endl; - } - } - while (exit_status == 0); - - if (exit_status == EOF) - { - if (m_interpreter.interactive ()) - octave_stdout << "\n"; - - exit_status = 0; - } - - return exit_status; + if (all) + m_execution_mode = EX_QUIT_ALL; + else + m_execution_mode = EX_QUIT; } - void debugger::repl (const std::string& prompt_arg) - { - unwind_protect frame; - - frame.protect_var (m_in_debug_repl); - frame.protect_var (m_execution_mode); - - m_in_debug_repl = true; - - tree_evaluator& tw = m_interpreter.get_evaluator (); - - bool silent = tw.quiet_breakpoint_flag (false); - - frame.add (&tree_evaluator::restore_frame, &tw, - tw.current_call_stack_frame_number ()); - - tw.goto_frame (tw.debug_frame ()); - - octave_user_code *caller = tw.current_user_code (); - std::string fcn_file_nm, fcn_nm; - - if (caller) - { - fcn_file_nm = caller->fcn_file_name (); - fcn_nm = fcn_file_nm.empty () ? caller->name () : fcn_file_nm; - } - - int curr_debug_line = tw.current_line (); - - std::ostringstream buf; - - input_system& input_sys = m_interpreter.get_input_system (); - - event_manager& evmgr = m_interpreter.get_event_manager (); - - if (! fcn_nm.empty ()) - { - if (input_sys.gud_mode ()) - { - static char ctrl_z = 'Z' & 0x1f; - - buf << ctrl_z << ctrl_z << fcn_nm << ':' << curr_debug_line; - } - else - { - // FIXME: we should come up with a clean way to detect - // that we are stopped on the no-op command that marks the - // end of a function or script. - - if (! silent) - { - std::shared_ptr<stack_frame> frm = tw.current_user_frame (); - - frm->display_stopped_in_message (buf); - } - - evmgr.enter_debugger_event (fcn_nm, fcn_file_nm, curr_debug_line); - - evmgr.set_workspace (); - - frame.add (&event_manager::execute_in_debugger_event, &evmgr, - fcn_nm, curr_debug_line); - - if (! silent) - { - std::string line_buf; - - if (caller) - line_buf = caller->get_code_line (curr_debug_line); - - if (! line_buf.empty ()) - buf << curr_debug_line << ": " << line_buf; - } - } - } - - if (silent) - command_editor::erase_empty_line (true); - - std::string stopped_in_msg = buf.str (); - - if (m_interpreter.server_mode ()) - { - if (! stopped_in_msg.empty ()) - octave_stdout << stopped_in_msg << std::endl; - - evmgr.push_event_queue (); - - frame.add (&event_manager::pop_event_queue, &evmgr); - - frame.add (&tree_evaluator::set_parser, &tw, tw.get_parser ()); - - std::shared_ptr<push_parser> - debug_parser (new push_parser (m_interpreter)); - - tw.set_parser (debug_parser); - - server_loop (); - } - else - { - if (! stopped_in_msg.empty ()) - std::cerr << stopped_in_msg << std::endl; - - std::string tmp_prompt = prompt_arg; - if (m_level > 0) - tmp_prompt = "[" + std::to_string (m_level) + "]" + prompt_arg; - - frame.add (&interpreter::set_PS1, &m_interpreter, m_interpreter.PS1 ()); - m_interpreter.PS1 (tmp_prompt); - - if (! m_interpreter.interactive ()) - { - void (interpreter::*interactive_fptr) (bool) - = &interpreter::interactive; - frame.add (interactive_fptr, &m_interpreter, - m_interpreter.interactive ()); - - m_interpreter.interactive (true); - - // FIXME: should debugging be possible in an embedded - // interpreter? - - application *app = application::app (); - - if (app) - { - void (application::*forced_interactive_fptr) (bool) - = &application::forced_interactive; - frame.add (forced_interactive_fptr, app, - app->forced_interactive ()); - - app->forced_interactive (true); - } - } + bool quitting_debugger () const; + +private: + + interpreter& m_interpreter; + + std::size_t m_level; + execution_mode m_execution_mode; + bool m_in_debug_repl; +}; + +// FIXME: Could the debugger server_loop and repl functions be merged +// with the corresponding tree_evaluator functions or do they need to +// remain separate? They perform nearly the same functions. + +int debugger::server_loop () +{ + // Process events from the event queue. + + tree_evaluator& tw = m_interpreter.get_evaluator (); + + void (tree_evaluator::*server_mode_fptr) (bool) + = &tree_evaluator::server_mode; + unwind_action act (server_mode_fptr, &tw, true); + + int exit_status = 0; + + do + { + if (m_execution_mode == EX_CONTINUE || tw.dbstep_flag ()) + break; + + if (quitting_debugger ()) + break; + + try + { + // FIXME: Should we call octave_quit in the octave::sleep + // and/or command_editor::run_event_hooks functions? + + octave_quit (); + + // FIXME: Running the event queue should be decoupled from + // the command_editor. + + // FIXME: Is it OK to use command_editor::run_event_hooks + // here? It may run more than one queued function per call, + // and it seems that the checks at the top of the loop + // probably need to be done after each individual event + // function is executed. For now, maybe the simplest thing + // would be to pass a predicate function (lambda expression) + // to the command_editor::run_event_hooks and have it check + // that and break out of the eval loop(s) if the condition + // is met? + + // FIXME: We should also use a condition variable to manage + // the execution of entries in the queue and eliminate the + // need for the busy-wait loop. + + command_editor::run_event_hooks (); + + release_unreferenced_dynamic_libraries (); + + sleep (0.1); + } + catch (const interrupt_exception&) + { + octave_interrupt_state = 1; + m_interpreter.recover_from_exception (); + + // Required newline when the user does Ctrl+C at the prompt. + if (m_interpreter.interactive ()) + octave_stdout << "\n"; + } + catch (const index_exception& e) + { + m_interpreter.recover_from_exception (); + + std::cerr << "error: unhandled index exception: " + << e.message () << " -- trying to return to prompt" + << std::endl; + } + catch (const execution_exception& ee) + { + error_system& es = m_interpreter.get_error_system (); + + es.save_exception (ee); + es.display_exception (ee); + + if (m_interpreter.interactive ()) + { + m_interpreter.recover_from_exception (); + } + else + { + // We should exit with a nonzero status. + exit_status = 1; + break; + } + } + catch (const quit_debug_exception& qde) + { + if (qde.all ()) + throw; + + // Continue in this debug level. + } + catch (const std::bad_alloc&) + { + m_interpreter.recover_from_exception (); + + std::cerr << "error: out of memory -- trying to return to prompt" + << std::endl; + } + } + while (exit_status == 0); + + if (exit_status == EOF) + { + if (m_interpreter.interactive ()) + octave_stdout << "\n"; + + exit_status = 0; + } + + return exit_status; +} + +void debugger::repl (const std::string& prompt_arg) +{ + unwind_protect frame; + + frame.protect_var (m_in_debug_repl); + frame.protect_var (m_execution_mode); + + m_in_debug_repl = true; + + tree_evaluator& tw = m_interpreter.get_evaluator (); + + bool silent = tw.quiet_breakpoint_flag (false); + + frame.add (&tree_evaluator::restore_frame, &tw, + tw.current_call_stack_frame_number ()); + + tw.goto_frame (tw.debug_frame ()); + + octave_user_code *caller = tw.current_user_code (); + std::string fcn_file_nm, fcn_nm; + + if (caller) + { + fcn_file_nm = caller->fcn_file_name (); + fcn_nm = fcn_file_nm.empty () ? caller->name () : fcn_file_nm; + } + + int curr_debug_line = tw.current_line (); + + std::ostringstream buf; + + input_system& input_sys = m_interpreter.get_input_system (); + + event_manager& evmgr = m_interpreter.get_event_manager (); + + if (! fcn_nm.empty ()) + { + if (input_sys.gud_mode ()) + { + static char ctrl_z = 'Z' & 0x1f; + + buf << ctrl_z << ctrl_z << fcn_nm << ':' << curr_debug_line; + } + else + { + // FIXME: we should come up with a clean way to detect + // that we are stopped on the no-op command that marks the + // end of a function or script. + + if (! silent) + { + std::shared_ptr<stack_frame> frm = tw.current_user_frame (); + + frm->display_stopped_in_message (buf); + } + + evmgr.enter_debugger_event (fcn_nm, fcn_file_nm, curr_debug_line); + + evmgr.set_workspace (); + + frame.add (&event_manager::execute_in_debugger_event, &evmgr, + fcn_nm, curr_debug_line); + + if (! silent) + { + std::string line_buf; + + if (caller) + line_buf = caller->get_code_line (curr_debug_line); + + if (! line_buf.empty ()) + buf << curr_debug_line << ": " << line_buf; + } + } + } + + if (silent) + command_editor::erase_empty_line (true); + + std::string stopped_in_msg = buf.str (); + + if (m_interpreter.server_mode ()) + { + if (! stopped_in_msg.empty ()) + octave_stdout << stopped_in_msg << std::endl; + + evmgr.push_event_queue (); + + frame.add (&event_manager::pop_event_queue, &evmgr); + + frame.add (&tree_evaluator::set_parser, &tw, tw.get_parser ()); + + std::shared_ptr<push_parser> + debug_parser (new push_parser (m_interpreter)); + + tw.set_parser (debug_parser); + + server_loop (); + } + else + { + if (! stopped_in_msg.empty ()) + std::cerr << stopped_in_msg << std::endl; + + std::string tmp_prompt = prompt_arg; + if (m_level > 0) + tmp_prompt = "[" + std::to_string (m_level) + "]" + prompt_arg; + + frame.add (&interpreter::set_PS1, &m_interpreter, m_interpreter.PS1 ()); + m_interpreter.PS1 (tmp_prompt); + + if (! m_interpreter.interactive ()) + { + void (interpreter::*interactive_fptr) (bool) + = &interpreter::interactive; + frame.add (interactive_fptr, &m_interpreter, + m_interpreter.interactive ()); + + m_interpreter.interactive (true); + + // FIXME: should debugging be possible in an embedded + // interpreter? + + application *app = application::app (); + + if (app) + { + void (application::*forced_interactive_fptr) (bool) + = &application::forced_interactive; + frame.add (forced_interactive_fptr, app, + app->forced_interactive ()); + + app->forced_interactive (true); + } + } #if defined (OCTAVE_ENABLE_COMMAND_LINE_PUSH_PARSER) - input_reader reader (m_interpreter); - - push_parser debug_parser (m_interpreter); + input_reader reader (m_interpreter); + + push_parser debug_parser (m_interpreter); #else - parser debug_parser (m_interpreter); + parser debug_parser (m_interpreter); #endif - error_system& es = m_interpreter.get_error_system (); - - while (m_in_debug_repl) - { - if (m_execution_mode == EX_CONTINUE || tw.dbstep_flag ()) - break; - - if (quitting_debugger ()) - break; - - try - { - debug_parser.reset (); + error_system& es = m_interpreter.get_error_system (); + + while (m_in_debug_repl) + { + if (m_execution_mode == EX_CONTINUE || tw.dbstep_flag ()) + break; + + if (quitting_debugger ()) + break; + + try + { + debug_parser.reset (); #if defined (OCTAVE_ENABLE_COMMAND_LINE_PUSH_PARSER) - int retval = 0; - - std::string prompt - = command_editor::decode_prompt_string (tmp_prompt); - - do - { - bool eof = false; - std::string input_line = reader.get_input (prompt, eof); - - if (eof) - { - retval = EOF; - break; - } - - retval = debug_parser.run (input_line, false); - - prompt = command_editor::decode_prompt_string (m_interpreter.PS2 ()); - } - while (retval < 0); + int retval = 0; + + std::string prompt + = command_editor::decode_prompt_string (tmp_prompt); + + do + { + bool eof = false; + std::string input_line = reader.get_input (prompt, eof); + + if (eof) + { + retval = EOF; + break; + } + + retval = debug_parser.run (input_line, false); + + prompt = command_editor::decode_prompt_string (m_interpreter.PS2 ()); + } + while (retval < 0); #else - int retval = debug_parser.run (); + int retval = debug_parser.run (); #endif - if (command_editor::interrupt (false)) - { - // Break regardless of m_execution_mode value. - - quitting_debugger (); - - break; - } - else - { - if (retval == 0) - { - std::shared_ptr<tree_statement_list> stmt_list - = debug_parser.statement_list (); - - if (stmt_list) - stmt_list->accept (tw); - - if (octave_completion_matches_called) - octave_completion_matches_called = false; - - // FIXME: the following statement is here because - // the last command may have been a dbup, dbdown, or - // dbstep command that changed the current debug - // frame. If so, we need to reset the current frame - // for the call stack. But is this right way to do - // this job? What if the statement list was - // something like "dbup; dbstack"? Will the call to - // dbstack use the right frame? If not, how can we - // fix this problem? - tw.goto_frame (tw.debug_frame ()); - } - - octave_quit (); - } - } - catch (const execution_exception& ee) - { - es.save_exception (ee); - es.display_exception (ee); - - // Ignore errors when in debugging mode; - m_interpreter.recover_from_exception (); - } - catch (const quit_debug_exception& qde) - { - if (qde.all ()) - throw; - - // Continue in this debug level. - } - } - } - } - - bool debugger::quitting_debugger () const - { - if (m_execution_mode == EX_QUIT) - { - // If there is no enclosing debug level or the top-level - // repl is not active, handle dbquit the same as dbcont. - - if (m_level > 0 || m_interpreter.server_mode () - || m_interpreter.in_top_level_repl ()) - throw quit_debug_exception (); - else - return true; - } - - if (m_execution_mode == EX_QUIT_ALL) - { - // If the top-level repl is not active, handle "dbquit all" - // the same as dbcont. - - if (m_interpreter.server_mode () || m_interpreter.in_top_level_repl ()) - throw quit_debug_exception (true); - else - return true; - } - - return false; - } - - bool tree_evaluator::at_top_level () const - { - return m_call_stack.at_top_level (); - } - - std::string - tree_evaluator::mfilename (const std::string& opt) const - { - std::string fname; - - octave_user_code *fcn = m_call_stack.current_user_code (); - - if (fcn) - { - fname = fcn->fcn_file_name (); - - if (fname.empty ()) - fname = fcn->name (); - } - - if (opt == "fullpathext") - return fname; - - std::size_t dpos = fname.rfind (sys::file_ops::dir_sep_char ()); - std::size_t epos = fname.rfind ('.'); - - if (epos <= dpos+1) - epos = std::string::npos; - - if (epos != std::string::npos) - fname = fname.substr (0, epos); - - if (opt == "fullpath") - return fname; - - if (dpos != std::string::npos) - fname = fname.substr (dpos+1); - + if (command_editor::interrupt (false)) + { + // Break regardless of m_execution_mode value. + + quitting_debugger (); + + break; + } + else + { + if (retval == 0) + { + std::shared_ptr<tree_statement_list> stmt_list + = debug_parser.statement_list (); + + if (stmt_list) + stmt_list->accept (tw); + + if (octave_completion_matches_called) + octave_completion_matches_called = false; + + // FIXME: the following statement is here because + // the last command may have been a dbup, dbdown, or + // dbstep command that changed the current debug + // frame. If so, we need to reset the current frame + // for the call stack. But is this right way to do + // this job? What if the statement list was + // something like "dbup; dbstack"? Will the call to + // dbstack use the right frame? If not, how can we + // fix this problem? + tw.goto_frame (tw.debug_frame ()); + } + + octave_quit (); + } + } + catch (const execution_exception& ee) + { + es.save_exception (ee); + es.display_exception (ee); + + // Ignore errors when in debugging mode; + m_interpreter.recover_from_exception (); + } + catch (const quit_debug_exception& qde) + { + if (qde.all ()) + throw; + + // Continue in this debug level. + } + } + } +} + +bool debugger::quitting_debugger () const +{ + if (m_execution_mode == EX_QUIT) + { + // If there is no enclosing debug level or the top-level + // repl is not active, handle dbquit the same as dbcont. + + if (m_level > 0 || m_interpreter.server_mode () + || m_interpreter.in_top_level_repl ()) + throw quit_debug_exception (); + else + return true; + } + + if (m_execution_mode == EX_QUIT_ALL) + { + // If the top-level repl is not active, handle "dbquit all" + // the same as dbcont. + + if (m_interpreter.server_mode () || m_interpreter.in_top_level_repl ()) + throw quit_debug_exception (true); + else + return true; + } + + return false; +} + +bool tree_evaluator::at_top_level () const +{ + return m_call_stack.at_top_level (); +} + +std::string +tree_evaluator::mfilename (const std::string& opt) const +{ + std::string fname; + + octave_user_code *fcn = m_call_stack.current_user_code (); + + if (fcn) + { + fname = fcn->fcn_file_name (); + + if (fname.empty ()) + fname = fcn->name (); + } + + if (opt == "fullpathext") + return fname; + + std::size_t dpos = fname.rfind (sys::file_ops::dir_sep_char ()); + std::size_t epos = fname.rfind ('.'); + + if (epos <= dpos+1) + epos = std::string::npos; + + if (epos != std::string::npos) + fname = fname.substr (0, epos); + + if (opt == "fullpath") return fname; - } - - void tree_evaluator::parse_and_execute (const std::string& input, - bool& incomplete_parse) - { - incomplete_parse = false; - - unwind_protect_var<bool> upv (m_in_top_level_repl, true); - - if (at_top_level ()) - { - dbstep_flag (0); - reset_debug_state (); - } - - // FIXME: OK to do this job here, or should it be in the functions - // that do the actual prompting? - - // Update the time stamp for the "prompt" so that automatically - // finding modified files based on file modification times will - // work. In the future, we may do something completely different to - // check for changes to files but for now, we rely on the prompt - // time stamp to limit the checks for file modification times. - - Vlast_prompt_time.stamp (); - - bool eof = false; - - event_manager& evmgr = m_interpreter.get_event_manager (); - - if (command_history::add (input)) - evmgr.append_history (input); - - m_exit_status = m_parser->run (input, eof); - - if (m_exit_status == 0) - { - std::shared_ptr<tree_statement_list> - stmt_list = m_parser->statement_list (); - - if (stmt_list) - { - command_editor::increment_current_command_number (); - - eval (stmt_list, m_interpreter.interactive ()); - - evmgr.set_workspace (); - } - else if (m_parser->at_end_of_input ()) - m_exit_status = EOF; - } - else - incomplete_parse = true; - - // FIXME: Should we be checking m_exit_status or incomplete_parse or - // both here? Could EOF have a value other than -1, and is there - // possible confusion between that state and the parser returning -1? - - if (m_exit_status == -1) - m_exit_status = 0; - else - m_parser->reset (); - - evmgr.pre_input_event (); - } - - void tree_evaluator::get_line_and_eval () - { - std::mutex mtx; - std::unique_lock<std::mutex> lock (mtx); - std::condition_variable cv; - bool incomplete_parse = false; - bool evaluation_pending = false; - bool exiting = false; - - event_manager& evmgr = m_interpreter.get_event_manager (); - - while (true) - { - // FIXME: Detect EOF? Use readline? If - // so, then we need to disable idle event loop hook function - // execution. - - std::string ps - = incomplete_parse ? m_interpreter.PS2 () : m_interpreter.PS1 (); - - std::cout << command_editor::decode_prompt_string (ps); - - std::string input; - std::getline (std::cin, input); - - if (input.empty ()) - continue; - - incomplete_parse = false; - evaluation_pending = true; - exiting = false; - - evmgr.post_event - ([&] (interpreter& interp) - { - // INTERPRETER THREAD - - std::lock_guard<std::mutex> local_lock (mtx); - - try - { - interp.parse_and_execute (input, incomplete_parse); - } - catch (const exit_exception&) - { - evaluation_pending = false; - exiting = true; - cv.notify_all (); - throw; - } - catch (const execution_exception& ee) - { - error_system& es = m_interpreter.get_error_system (); - - es.save_exception (ee); - es.display_exception (ee); - - if (m_interpreter.interactive ()) - { - m_interpreter.recover_from_exception (); - m_parser->reset (); - evaluation_pending = false; - cv.notify_all (); - } - else - { - evaluation_pending = false; - cv.notify_all (); - throw exit_exception (1); - } - } - catch (...) - { - evaluation_pending = false; - cv.notify_all (); - throw; - } - - evaluation_pending = false; - cv.notify_all (); - }); - - // Wait until evaluation is finished before prompting for input - // again. - - cv.wait (lock, [&] { return ! evaluation_pending; }); - - if (exiting) - break; - } - } - - int tree_evaluator::repl () - { - // The big loop. Read, Eval, Print, Loop. Normally user - // interaction at the command line in a terminal session, but we may - // also end up here when reading from a pipe or when stdin is - // connected to a file by the magic of input redirection. - - int exit_status = 0; - - // FIXME: should this choice be a command-line option? Note that we - // intend that the push parser interface only be used for - // interactive sessions. + + if (dpos != std::string::npos) + fname = fname.substr (dpos+1); + + return fname; +} + +void tree_evaluator::parse_and_execute (const std::string& input, + bool& incomplete_parse) +{ + incomplete_parse = false; + + unwind_protect_var<bool> upv (m_in_top_level_repl, true); + + if (at_top_level ()) + { + dbstep_flag (0); + reset_debug_state (); + } + + // FIXME: OK to do this job here, or should it be in the functions + // that do the actual prompting? + + // Update the time stamp for the "prompt" so that automatically + // finding modified files based on file modification times will + // work. In the future, we may do something completely different to + // check for changes to files but for now, we rely on the prompt + // time stamp to limit the checks for file modification times. + + Vlast_prompt_time.stamp (); + + bool eof = false; + + event_manager& evmgr = m_interpreter.get_event_manager (); + + if (command_history::add (input)) + evmgr.append_history (input); + + m_exit_status = m_parser->run (input, eof); + + if (m_exit_status == 0) + { + std::shared_ptr<tree_statement_list> + stmt_list = m_parser->statement_list (); + + if (stmt_list) + { + command_editor::increment_current_command_number (); + + eval (stmt_list, m_interpreter.interactive ()); + + evmgr.set_workspace (); + } + else if (m_parser->at_end_of_input ()) + m_exit_status = EOF; + } + else + incomplete_parse = true; + + // FIXME: Should we be checking m_exit_status or incomplete_parse or + // both here? Could EOF have a value other than -1, and is there + // possible confusion between that state and the parser returning -1? + + if (m_exit_status == -1) + m_exit_status = 0; + else + m_parser->reset (); + + evmgr.pre_input_event (); +} + +void tree_evaluator::get_line_and_eval () +{ + std::mutex mtx; + std::unique_lock<std::mutex> lock (mtx); + std::condition_variable cv; + bool incomplete_parse = false; + bool evaluation_pending = false; + bool exiting = false; + + event_manager& evmgr = m_interpreter.get_event_manager (); + + while (true) + { + // FIXME: Detect EOF? Use readline? If + // so, then we need to disable idle event loop hook function + // execution. + + std::string ps + = incomplete_parse ? m_interpreter.PS2 () : m_interpreter.PS1 (); + + std::cout << command_editor::decode_prompt_string (ps); + + std::string input; + std::getline (std::cin, input); + + if (input.empty ()) + continue; + + incomplete_parse = false; + evaluation_pending = true; + exiting = false; + + evmgr.post_event + ([&] (interpreter& interp) + { + // INTERPRETER THREAD + + std::lock_guard<std::mutex> local_lock (mtx); + + try + { + interp.parse_and_execute (input, incomplete_parse); + } + catch (const exit_exception&) + { + evaluation_pending = false; + exiting = true; + cv.notify_all (); + throw; + } + catch (const execution_exception& ee) + { + error_system& es = m_interpreter.get_error_system (); + + es.save_exception (ee); + es.display_exception (ee); + + if (m_interpreter.interactive ()) + { + m_interpreter.recover_from_exception (); + m_parser->reset (); + evaluation_pending = false; + cv.notify_all (); + } + else + { + evaluation_pending = false; + cv.notify_all (); + throw exit_exception (1); + } + } + catch (...) + { + evaluation_pending = false; + cv.notify_all (); + throw; + } + + evaluation_pending = false; + cv.notify_all (); + }); + + // Wait until evaluation is finished before prompting for input + // again. + + cv.wait (lock, [&] { return ! evaluation_pending; }); + + if (exiting) + break; + } +} + +int tree_evaluator::repl () +{ + // The big loop. Read, Eval, Print, Loop. Normally user + // interaction at the command line in a terminal session, but we may + // also end up here when reading from a pipe or when stdin is + // connected to a file by the magic of input redirection. + + int exit_status = 0; + + // FIXME: should this choice be a command-line option? Note that we + // intend that the push parser interface only be used for + // interactive sessions. #if defined (OCTAVE_ENABLE_COMMAND_LINE_PUSH_PARSER) - static bool use_command_line_push_parser = true; + static bool use_command_line_push_parser = true; #else - static bool use_command_line_push_parser = false; + static bool use_command_line_push_parser = false; #endif - // The following logic is written as it is to allow easy transition - // to setting USE_COMMAND_LINE_PUSH_PARSER at run time and to - // simplify the logic of the main loop below by using the same - // base_parser::run interface for both push and pull parsers. - - std::shared_ptr<base_parser> repl_parser; - - if (m_interpreter.interactive ()) - { - if (use_command_line_push_parser) - { - push_parser *pp - = new push_parser (m_interpreter, - new input_reader (m_interpreter)); - - repl_parser = std::shared_ptr<base_parser> (pp); - } - else - { - parser *pp = new parser (new lexer (m_interpreter)); - repl_parser = std::shared_ptr<base_parser> (pp); - } - } - else - { - parser *pp = new parser (new lexer (stdin, m_interpreter)); - repl_parser = std::shared_ptr<base_parser> (pp); - } - - do - { - try - { - unwind_protect_var<bool> upv (m_in_top_level_repl, true); - - repl_parser->reset (); - - if (at_top_level ()) - { - dbstep_flag (0); - reset_debug_state (); - } - - exit_status = repl_parser->run (); - - if (exit_status == 0) - { - std::shared_ptr<tree_statement_list> - stmt_list = repl_parser->statement_list (); - - if (stmt_list) - { - command_editor::increment_current_command_number (); - - eval (stmt_list, m_interpreter.interactive ()); - } - else if (repl_parser->at_end_of_input ()) - { - exit_status = EOF; - break; - } - } - } - catch (const interrupt_exception&) - { - m_interpreter.recover_from_exception (); - - // Required newline when the user does Ctrl+C at the prompt. - if (m_interpreter.interactive ()) - octave_stdout << "\n"; - } - catch (const index_exception& ie) - { - m_interpreter.recover_from_exception (); - - std::cerr << "error: unhandled index exception: " - << ie.message () << " -- trying to return to prompt" - << std::endl; - } - catch (const execution_exception& ee) - { - error_system& es = m_interpreter.get_error_system (); - - es.save_exception (ee); - es.display_exception (ee); - - if (m_interpreter.interactive ()) - m_interpreter.recover_from_exception (); - else - { - // We should exit with a nonzero status. - exit_status = 1; - break; - } - } - catch (const quit_debug_exception&) - { - m_interpreter.recover_from_exception (); - - // FIXME: Does anything else need to happen here? - } - catch (const std::bad_alloc&) - { - m_interpreter.recover_from_exception (); - - std::cerr << "error: out of memory -- trying to return to prompt" - << std::endl; - } - } - while (exit_status == 0); - - if (exit_status == EOF) - { - if (m_interpreter.interactive ()) - octave_stdout << "\n"; - - exit_status = 0; - } - - return exit_status; - } - - int tree_evaluator::server_loop () - { - // Process events from the event queue. - - unwind_protect_var<bool> upv1 (m_server_mode, true); - - m_exit_status = 0; - - std::shared_ptr<push_parser> parser (new push_parser (m_interpreter)); - unwind_protect_var<std::shared_ptr<push_parser>> upv2 (m_parser, parser); - - // FIXME: We are currently resetting the parser after every call to - // recover_from_exception. This action should probably be handled - // in a more consistent way, but resetting the parser in every call - // to interpreter::recover_from_exception appears to cause - // segfaults in the test suite. - - do - { - try - { - // FIXME: Should we call octave_quit in the octave::sleep - // and/or command_editor::run_event_hooks functions? - - octave_quit (); - - // FIXME: Running the event queue should be decoupled from - // the command_editor. We should also use a condition - // variable to manage the execution of entries in the queue - // and eliminate the need for the busy-wait loop. - - command_editor::run_event_hooks (); - - release_unreferenced_dynamic_libraries (); - - sleep (0.1); - } - catch (const interrupt_exception&) - { - octave_interrupt_state = 1; - m_interpreter.recover_from_exception (); - m_parser->reset (); - - // Required newline when the user does Ctrl+C at the prompt. - if (m_interpreter.interactive ()) - octave_stdout << "\n"; - } - catch (const index_exception& e) - { + // The following logic is written as it is to allow easy transition + // to setting USE_COMMAND_LINE_PUSH_PARSER at run time and to + // simplify the logic of the main loop below by using the same + // base_parser::run interface for both push and pull parsers. + + std::shared_ptr<base_parser> repl_parser; + + if (m_interpreter.interactive ()) + { + if (use_command_line_push_parser) + { + push_parser *pp + = new push_parser (m_interpreter, + new input_reader (m_interpreter)); + + repl_parser = std::shared_ptr<base_parser> (pp); + } + else + { + parser *pp = new parser (new lexer (m_interpreter)); + repl_parser = std::shared_ptr<base_parser> (pp); + } + } + else + { + parser *pp = new parser (new lexer (stdin, m_interpreter)); + repl_parser = std::shared_ptr<base_parser> (pp); + } + + do + { + try + { + unwind_protect_var<bool> upv (m_in_top_level_repl, true); + + repl_parser->reset (); + + if (at_top_level ()) + { + dbstep_flag (0); + reset_debug_state (); + } + + exit_status = repl_parser->run (); + + if (exit_status == 0) + { + std::shared_ptr<tree_statement_list> + stmt_list = repl_parser->statement_list (); + + if (stmt_list) + { + command_editor::increment_current_command_number (); + + eval (stmt_list, m_interpreter.interactive ()); + } + else if (repl_parser->at_end_of_input ()) + { + exit_status = EOF; + break; + } + } + } + catch (const interrupt_exception&) + { + m_interpreter.recover_from_exception (); + + // Required newline when the user does Ctrl+C at the prompt. + if (m_interpreter.interactive ()) + octave_stdout << "\n"; + } + catch (const index_exception& ie) + { + m_interpreter.recover_from_exception (); + + std::cerr << "error: unhandled index exception: " + << ie.message () << " -- trying to return to prompt" + << std::endl; + } + catch (const execution_exception& ee) + { + error_system& es = m_interpreter.get_error_system (); + + es.save_exception (ee); + es.display_exception (ee); + + if (m_interpreter.interactive ()) m_interpreter.recover_from_exception (); - m_parser->reset (); - - std::cerr << "error: unhandled index exception: " - << e.message () << " -- trying to return to prompt" - << std::endl; - } - catch (const execution_exception& ee) - { - error_system& es = m_interpreter.get_error_system (); - - es.save_exception (ee); - es.display_exception (ee); - - if (m_interpreter.interactive ()) - { - m_interpreter.recover_from_exception (); - m_parser->reset (); - } - else - { - // We should exit with a nonzero status. - m_exit_status = 1; + else + { + // We should exit with a nonzero status. + exit_status = 1; + break; + } + } + catch (const quit_debug_exception&) + { + m_interpreter.recover_from_exception (); + + // FIXME: Does anything else need to happen here? + } + catch (const std::bad_alloc&) + { + m_interpreter.recover_from_exception (); + + std::cerr << "error: out of memory -- trying to return to prompt" + << std::endl; + } + } + while (exit_status == 0); + + if (exit_status == EOF) + { + if (m_interpreter.interactive ()) + octave_stdout << "\n"; + + exit_status = 0; + } + + return exit_status; +} + +int tree_evaluator::server_loop () +{ + // Process events from the event queue. + + unwind_protect_var<bool> upv1 (m_server_mode, true); + + m_exit_status = 0; + + std::shared_ptr<push_parser> parser (new push_parser (m_interpreter)); + unwind_protect_var<std::shared_ptr<push_parser>> upv2 (m_parser, parser); + + // FIXME: We are currently resetting the parser after every call to + // recover_from_exception. This action should probably be handled + // in a more consistent way, but resetting the parser in every call + // to interpreter::recover_from_exception appears to cause + // segfaults in the test suite. + + do + { + try + { + // FIXME: Should we call octave_quit in the octave::sleep + // and/or command_editor::run_event_hooks functions? + + octave_quit (); + + // FIXME: Running the event queue should be decoupled from + // the command_editor. We should also use a condition + // variable to manage the execution of entries in the queue + // and eliminate the need for the busy-wait loop. + + command_editor::run_event_hooks (); + + release_unreferenced_dynamic_libraries (); + + sleep (0.1); + } + catch (const interrupt_exception&) + { + octave_interrupt_state = 1; + m_interpreter.recover_from_exception (); + m_parser->reset (); + + // Required newline when the user does Ctrl+C at the prompt. + if (m_interpreter.interactive ()) + octave_stdout << "\n"; + } + catch (const index_exception& e) + { + m_interpreter.recover_from_exception (); + m_parser->reset (); + + std::cerr << "error: unhandled index exception: " + << e.message () << " -- trying to return to prompt" + << std::endl; + } + catch (const execution_exception& ee) + { + error_system& es = m_interpreter.get_error_system (); + + es.save_exception (ee); + es.display_exception (ee); + + if (m_interpreter.interactive ()) + { + m_interpreter.recover_from_exception (); + m_parser->reset (); + } + else + { + // We should exit with a nonzero status. + m_exit_status = 1; + break; + } + } + catch (const quit_debug_exception&) + { + octave_interrupt_state = 1; + m_interpreter.recover_from_exception (); + m_parser->reset (); + } + catch (const exit_exception& xe) + { + m_exit_status = xe.exit_status (); + break; + } + catch (const std::bad_alloc&) + { + m_interpreter.recover_from_exception (); + m_parser->reset (); + + std::cerr << "error: out of memory -- trying to return to prompt" + << std::endl; + } + } + while (m_exit_status == 0); + + if (m_exit_status == EOF) + { + if (m_interpreter.interactive ()) + octave_stdout << "\n"; + + m_exit_status = 0; + } + + return m_exit_status; +} + +void tree_evaluator::eval (std::shared_ptr<tree_statement_list>& stmt_list, + bool interactive) +{ + try + { + stmt_list->accept (*this); + + octave_quit (); + + if (! interactive) + { + bool quit = (m_returning || m_breaking); + + if (m_returning) + m_returning = 0; + + if (m_breaking) + m_breaking--; + + if (quit) + return; + } + + if (octave_completion_matches_called) + octave_completion_matches_called = false; + } + catch (const quit_debug_exception&) + { + m_interpreter.recover_from_exception (); + } +} + +octave_value_list +tree_evaluator::eval_string (const std::string& eval_str, bool silent, + int& parse_status, int nargout) +{ + octave_value_list retval; + + parser eval_parser (eval_str, m_interpreter); + + do + { + eval_parser.reset (); + + // If we are looking at + // + // val = eval ("code"); + // + // then don't allow code to be parsed as a command. + + if (nargout > 0) + eval_parser.disallow_command_syntax (); + + parse_status = eval_parser.run (); + + if (parse_status == 0) + { + std::shared_ptr<tree_statement_list> stmt_list + = eval_parser.statement_list (); + + if (stmt_list) + { + tree_statement *stmt = nullptr; + + if (stmt_list->length () == 1 + && (stmt = stmt_list->front ()) + && stmt->is_expression ()) + { + tree_expression *expr = stmt->expression (); + + if (silent) + expr->set_print_flag (false); + + retval = expr->evaluate_n (*this, nargout); + + bool do_bind_ans = false; + + if (expr->is_identifier ()) + do_bind_ans = ! is_variable (expr); + else + do_bind_ans = ! expr->is_assignment_expression (); + + if (do_bind_ans && ! retval.empty ()) + bind_ans (retval(0), expr->print_result ()); + + if (nargout == 0) + retval = octave_value_list (); + } + else if (nargout == 0) + stmt_list->accept (*this); + else + error ("eval: invalid use of statement list"); + + if (returning () || breaking () || continuing ()) break; - } - } - catch (const quit_debug_exception&) - { - octave_interrupt_state = 1; - m_interpreter.recover_from_exception (); - m_parser->reset (); - } - catch (const exit_exception& xe) - { - m_exit_status = xe.exit_status (); + } + else if (eval_parser.at_end_of_input ()) break; - } - catch (const std::bad_alloc&) - { - m_interpreter.recover_from_exception (); - m_parser->reset (); - - std::cerr << "error: out of memory -- trying to return to prompt" - << std::endl; - } - } - while (m_exit_status == 0); - - if (m_exit_status == EOF) - { - if (m_interpreter.interactive ()) - octave_stdout << "\n"; - - m_exit_status = 0; - } - - return m_exit_status; - } - - void tree_evaluator::eval (std::shared_ptr<tree_statement_list>& stmt_list, - bool interactive) - { - try - { - stmt_list->accept (*this); - - octave_quit (); - - if (! interactive) - { - bool quit = (m_returning || m_breaking); - - if (m_returning) - m_returning = 0; - - if (m_breaking) - m_breaking--; - - if (quit) - return; - } - - if (octave_completion_matches_called) - octave_completion_matches_called = false; - } - catch (const quit_debug_exception&) - { - m_interpreter.recover_from_exception (); - } - } - - octave_value_list - tree_evaluator::eval_string (const std::string& eval_str, bool silent, - int& parse_status, int nargout) - { - octave_value_list retval; - - parser eval_parser (eval_str, m_interpreter); - - do - { - eval_parser.reset (); - - // If we are looking at - // - // val = eval ("code"); - // - // then don't allow code to be parsed as a command. - - if (nargout > 0) - eval_parser.disallow_command_syntax (); - - parse_status = eval_parser.run (); - - if (parse_status == 0) - { - std::shared_ptr<tree_statement_list> stmt_list - = eval_parser.statement_list (); - - if (stmt_list) - { - tree_statement *stmt = nullptr; - - if (stmt_list->length () == 1 - && (stmt = stmt_list->front ()) - && stmt->is_expression ()) - { - tree_expression *expr = stmt->expression (); - - if (silent) - expr->set_print_flag (false); - - retval = expr->evaluate_n (*this, nargout); - - bool do_bind_ans = false; - - if (expr->is_identifier ()) - do_bind_ans = ! is_variable (expr); - else - do_bind_ans = ! expr->is_assignment_expression (); - - if (do_bind_ans && ! retval.empty ()) - bind_ans (retval(0), expr->print_result ()); - - if (nargout == 0) - retval = octave_value_list (); - } - else if (nargout == 0) - stmt_list->accept (*this); - else - error ("eval: invalid use of statement list"); - - if (returning () || breaking () || continuing ()) - break; - } - else if (eval_parser.at_end_of_input ()) - break; - } - } - while (parse_status == 0); - - return retval; - } - - octave_value tree_evaluator::eval_string (const std::string& eval_str, - bool silent, int& parse_status) - { - octave_value retval; - - octave_value_list tmp = eval_string (eval_str, silent, parse_status, 1); - - if (! tmp.empty ()) - retval = tmp(0); - - return retval; - } - - octave_value_list tree_evaluator::eval_string (const octave_value& arg, - bool silent, int& parse_status, - int nargout) - { - std::string s = arg.xstring_value ("eval: expecting string argument"); - - return eval_string (s, silent, parse_status, nargout); - } - - octave_value_list tree_evaluator::eval (const std::string& try_code, + } + } + while (parse_status == 0); + + return retval; +} + +octave_value tree_evaluator::eval_string (const std::string& eval_str, + bool silent, int& parse_status) +{ + octave_value retval; + + octave_value_list tmp = eval_string (eval_str, silent, parse_status, 1); + + if (! tmp.empty ()) + retval = tmp(0); + + return retval; +} + +octave_value_list tree_evaluator::eval_string (const octave_value& arg, + bool silent, int& parse_status, + int nargout) +{ + std::string s = arg.xstring_value ("eval: expecting string argument"); + + return eval_string (s, silent, parse_status, nargout); +} + +octave_value_list tree_evaluator::eval (const std::string& try_code, + int nargout) +{ + int parse_status = 0; + + return eval_string (try_code, nargout > 0, parse_status, nargout); +} + +octave_value_list tree_evaluator::eval (const std::string& try_code, + const std::string& catch_code, + int nargout) +{ + octave_value_list retval; + + error_system& es = m_interpreter.get_error_system (); + + int parse_status = 0; + + bool execution_error = false; + + octave_value_list tmp; + + try + { + tmp = eval_string (try_code, nargout > 0, parse_status, nargout); + } + catch (const execution_exception& ee) + { + es.save_exception (ee); + m_interpreter.recover_from_exception (); + + execution_error = true; + } + + if (parse_status != 0 || execution_error) + { + tmp = eval_string (catch_code, nargout > 0, parse_status, nargout); + + retval = (nargout > 0) ? tmp : octave_value_list (); + } + else + { + if (nargout > 0) + retval = tmp; + + // FIXME: we should really be rethrowing whatever + // exception occurred, not just throwing an + // execution exception. + if (execution_error) + throw execution_exception (); + } + + return retval; +} + +octave_value_list tree_evaluator::evalin (const std::string& context, + const std::string& try_code, int nargout) - { - int parse_status = 0; - - return eval_string (try_code, nargout > 0, parse_status, nargout); - } - - octave_value_list tree_evaluator::eval (const std::string& try_code, +{ + unwind_action act ([=] (std::size_t frm) + { + m_call_stack.restore_frame (frm); + }, m_call_stack.current_frame ()); + + if (context == "caller") + m_call_stack.goto_caller_frame (); + else if (context == "base") + m_call_stack.goto_base_frame (); + else + error (R"(evalin: CONTEXT must be "caller" or "base")"); + + int parse_status = 0; + + return eval_string (try_code, nargout > 0, parse_status, nargout); +} + +octave_value_list tree_evaluator::evalin (const std::string& context, + const std::string& try_code, const std::string& catch_code, int nargout) - { - octave_value_list retval; - - error_system& es = m_interpreter.get_error_system (); - - int parse_status = 0; - - bool execution_error = false; - - octave_value_list tmp; - - try - { - tmp = eval_string (try_code, nargout > 0, parse_status, nargout); - } - catch (const execution_exception& ee) - { - es.save_exception (ee); - m_interpreter.recover_from_exception (); - - execution_error = true; - } - - if (parse_status != 0 || execution_error) - { - tmp = eval_string (catch_code, nargout > 0, parse_status, nargout); - - retval = (nargout > 0) ? tmp : octave_value_list (); - } - else - { - if (nargout > 0) - retval = tmp; - - // FIXME: we should really be rethrowing whatever - // exception occurred, not just throwing an - // execution exception. - if (execution_error) - throw execution_exception (); - } - - return retval; - } - - octave_value_list tree_evaluator::evalin (const std::string& context, - const std::string& try_code, - int nargout) - { - unwind_action act ([=] (std::size_t frm) - { - m_call_stack.restore_frame (frm); - }, m_call_stack.current_frame ()); - - if (context == "caller") - m_call_stack.goto_caller_frame (); - else if (context == "base") - m_call_stack.goto_base_frame (); - else - error (R"(evalin: CONTEXT must be "caller" or "base")"); - - int parse_status = 0; - - return eval_string (try_code, nargout > 0, parse_status, nargout); - } - - octave_value_list tree_evaluator::evalin (const std::string& context, - const std::string& try_code, - const std::string& catch_code, - int nargout) - { - octave_value_list retval; - - unwind_action act1 ([=] (std::size_t frm) - { - m_call_stack.restore_frame (frm); - }, m_call_stack.current_frame ()); - - if (context == "caller") - m_call_stack.goto_caller_frame (); - else if (context == "base") - m_call_stack.goto_base_frame (); - else - error (R"(evalin: CONTEXT must be "caller" or "base")"); - - error_system& es = m_interpreter.get_error_system (); - - int parse_status = 0; - - bool execution_error = false; - - octave_value_list tmp; - - try - { - tmp = eval_string (try_code, nargout > 0, parse_status, nargout); - } - catch (const execution_exception& ee) - { - es.save_exception (ee); - m_interpreter.recover_from_exception (); - - execution_error = true; - } - - if (parse_status != 0 || execution_error) - { - tmp = eval_string (catch_code, nargout > 0, parse_status, nargout); - - retval = (nargout > 0) ? tmp : octave_value_list (); - } - else - { - if (nargout > 0) - retval = tmp; - - // FIXME: we should really be rethrowing whatever - // exception occurred, not just throwing an - // execution exception. - if (execution_error) - throw execution_exception (); - } - +{ + octave_value_list retval; + + unwind_action act1 ([=] (std::size_t frm) + { + m_call_stack.restore_frame (frm); + }, m_call_stack.current_frame ()); + + if (context == "caller") + m_call_stack.goto_caller_frame (); + else if (context == "base") + m_call_stack.goto_base_frame (); + else + error (R"(evalin: CONTEXT must be "caller" or "base")"); + + error_system& es = m_interpreter.get_error_system (); + + int parse_status = 0; + + bool execution_error = false; + + octave_value_list tmp; + + try + { + tmp = eval_string (try_code, nargout > 0, parse_status, nargout); + } + catch (const execution_exception& ee) + { + es.save_exception (ee); + m_interpreter.recover_from_exception (); + + execution_error = true; + } + + if (parse_status != 0 || execution_error) + { + tmp = eval_string (catch_code, nargout > 0, parse_status, nargout); + + retval = (nargout > 0) ? tmp : octave_value_list (); + } + else + { + if (nargout > 0) + retval = tmp; + + // FIXME: we should really be rethrowing whatever + // exception occurred, not just throwing an + // execution exception. + if (execution_error) + throw execution_exception (); + } + + return retval; +} + +void +tree_evaluator::visit_anon_fcn_handle (tree_anon_fcn_handle&) +{ + panic_impossible (); +} + +void +tree_evaluator::visit_argument_list (tree_argument_list&) +{ + panic_impossible (); +} + +void +tree_evaluator::visit_arguments_block (tree_arguments_block&) +{ + warning ("function arguments validation blocks are not supported; INCORRECT RESULTS ARE POSSIBLE"); +} + +void +tree_evaluator::visit_args_block_attribute_list (tree_args_block_attribute_list&) +{ + panic_impossible (); +} + +void +tree_evaluator::visit_args_block_validation_list (tree_args_block_validation_list&) +{ + panic_impossible (); +} + +void +tree_evaluator::visit_arg_validation (tree_arg_validation&) +{ + panic_impossible (); +} + +void +tree_evaluator::visit_arg_size_spec (tree_arg_size_spec&) +{ + panic_impossible (); +} + +void +tree_evaluator::visit_arg_validation_fcns (tree_arg_validation_fcns&) +{ + panic_impossible (); +} + +void +tree_evaluator::visit_binary_expression (tree_binary_expression&) +{ + panic_impossible (); +} + +void +tree_evaluator::visit_boolean_expression (tree_boolean_expression&) +{ + panic_impossible (); +} + +void +tree_evaluator::visit_compound_binary_expression (tree_compound_binary_expression&) +{ + panic_impossible (); +} + +void +tree_evaluator::visit_break_command (tree_break_command& cmd) +{ + if (m_echo_state) + { + int line = cmd.line (); + if (line < 0) + line = 1; + echo_code (line); + m_echo_file_pos = line + 1; + } + + if (m_debug_mode) + do_breakpoint (cmd.is_active_breakpoint (*this)); + + if (m_in_loop_command) + m_breaking = 1; + else + error ("break must appear in a loop in the same file as loop command"); +} + +void +tree_evaluator::visit_colon_expression (tree_colon_expression&) +{ + panic_impossible (); +} + +void +tree_evaluator::visit_continue_command (tree_continue_command& cmd) +{ + if (m_echo_state) + { + int line = cmd.line (); + if (line < 0) + line = 1; + echo_code (line); + m_echo_file_pos = line + 1; + } + + if (m_debug_mode) + do_breakpoint (cmd.is_active_breakpoint (*this)); + + if (m_in_loop_command) + m_continuing = 1; +} + +bool +tree_evaluator::statement_printing_enabled () +{ + return ! (m_silent_functions && (m_statement_context == SC_FUNCTION + || m_statement_context == SC_SCRIPT)); +} + +void +tree_evaluator::reset_debug_state () +{ + m_debug_mode = (m_bp_table.have_breakpoints () + || m_dbstep_flag != 0 + || m_break_on_next_stmt + || in_debug_repl ()); +} + +void +tree_evaluator::reset_debug_state (bool mode) +{ + m_debug_mode = mode; +} + +void +tree_evaluator::enter_debugger (const std::string& prompt) +{ + unwind_protect frame; + + frame.add (command_history::ignore_entries, + command_history::ignoring_entries ()); + + command_history::ignore_entries (false); + + frame.add (&call_stack::restore_frame, &m_call_stack, + m_call_stack.current_frame ()); + + // Don't allow errors or warnings at the debug prompt to push us + // into deeper levels of debugging. + + error_system& es = m_interpreter.get_error_system (); + + frame.add (&error_system::set_debug_on_error, &es, es.debug_on_error ()); + + frame.add (&error_system::set_debug_on_warning, &es, + es.debug_on_warning ()); + + es.debug_on_error (false); + es.debug_on_warning (false); + + // Go up to the nearest user code frame. + + m_debug_frame = m_call_stack.dbupdown (0); + + // FIXME: probably we just want to print one line, not the + // entire statement, which might span many lines... + // + // tree_print_code tpc (octave_stdout); + // stmt.accept (tpc); + + debugger *dbgr = new debugger (m_interpreter, m_debugger_stack.size ()); + + m_debugger_stack.push (dbgr); + + frame.add ([=] () + { + delete m_debugger_stack.top (); + m_debugger_stack.pop (); + reset_debug_state (); + }); + + dbgr->repl (prompt); +} + +void +tree_evaluator::keyboard (const std::string& prompt) +{ + enter_debugger (prompt); +} + +void +tree_evaluator::dbupdown (int n, bool verbose) +{ + m_debug_frame = m_call_stack.dbupdown (n, verbose); +} + +Matrix +tree_evaluator::ignored_fcn_outputs () const +{ + Matrix retval; + + const std::list<octave_lvalue> *lvalues = m_lvalue_list; + + if (! lvalues) return retval; - } - - void - tree_evaluator::visit_anon_fcn_handle (tree_anon_fcn_handle&) - { - panic_impossible (); - } - - void - tree_evaluator::visit_argument_list (tree_argument_list&) - { - panic_impossible (); - } - - void - tree_evaluator::visit_arguments_block (tree_arguments_block&) - { - warning ("function arguments validation blocks are not supported; INCORRECT RESULTS ARE POSSIBLE"); - } - - void - tree_evaluator::visit_args_block_attribute_list (tree_args_block_attribute_list&) - { - panic_impossible (); - } - - void - tree_evaluator::visit_args_block_validation_list (tree_args_block_validation_list&) - { - panic_impossible (); - } - - void - tree_evaluator::visit_arg_validation (tree_arg_validation&) - { - panic_impossible (); - } - - void - tree_evaluator::visit_arg_size_spec (tree_arg_size_spec&) - { - panic_impossible (); - } - - void - tree_evaluator::visit_arg_validation_fcns (tree_arg_validation_fcns&) - { - panic_impossible (); - } - - void - tree_evaluator::visit_binary_expression (tree_binary_expression&) - { - panic_impossible (); - } - - void - tree_evaluator::visit_boolean_expression (tree_boolean_expression&) - { - panic_impossible (); - } - - void - tree_evaluator::visit_compound_binary_expression (tree_compound_binary_expression&) - { - panic_impossible (); - } - - void - tree_evaluator::visit_break_command (tree_break_command& cmd) - { - if (m_echo_state) - { - int line = cmd.line (); - if (line < 0) - line = 1; - echo_code (line); - m_echo_file_pos = line + 1; - } - - if (m_debug_mode) - do_breakpoint (cmd.is_active_breakpoint (*this)); - - if (m_in_loop_command) - m_breaking = 1; - else - error ("break must appear in a loop in the same file as loop command"); - } - - void - tree_evaluator::visit_colon_expression (tree_colon_expression&) - { - panic_impossible (); - } - - void - tree_evaluator::visit_continue_command (tree_continue_command& cmd) - { - if (m_echo_state) - { - int line = cmd.line (); - if (line < 0) - line = 1; - echo_code (line); - m_echo_file_pos = line + 1; - } - - if (m_debug_mode) - do_breakpoint (cmd.is_active_breakpoint (*this)); - - if (m_in_loop_command) - m_continuing = 1; - } - - bool - tree_evaluator::statement_printing_enabled () - { - return ! (m_silent_functions && (m_statement_context == SC_FUNCTION - || m_statement_context == SC_SCRIPT)); - } - - void - tree_evaluator::reset_debug_state () - { - m_debug_mode = (m_bp_table.have_breakpoints () - || m_dbstep_flag != 0 - || m_break_on_next_stmt - || in_debug_repl ()); - } - - void - tree_evaluator::reset_debug_state (bool mode) - { - m_debug_mode = mode; - } - - void - tree_evaluator::enter_debugger (const std::string& prompt) - { - unwind_protect frame; - - frame.add (command_history::ignore_entries, - command_history::ignoring_entries ()); - - command_history::ignore_entries (false); - - frame.add (&call_stack::restore_frame, &m_call_stack, - m_call_stack.current_frame ()); - - // Don't allow errors or warnings at the debug prompt to push us - // into deeper levels of debugging. - - error_system& es = m_interpreter.get_error_system (); - - frame.add (&error_system::set_debug_on_error, &es, es.debug_on_error ()); - - frame.add (&error_system::set_debug_on_warning, &es, - es.debug_on_warning ()); - - es.debug_on_error (false); - es.debug_on_warning (false); - - // Go up to the nearest user code frame. - - m_debug_frame = m_call_stack.dbupdown (0); - - // FIXME: probably we just want to print one line, not the - // entire statement, which might span many lines... - // - // tree_print_code tpc (octave_stdout); - // stmt.accept (tpc); - - debugger *dbgr = new debugger (m_interpreter, m_debugger_stack.size ()); - - m_debugger_stack.push (dbgr); - - frame.add ([=] () - { - delete m_debugger_stack.top (); - m_debugger_stack.pop (); - reset_debug_state (); - }); - - dbgr->repl (prompt); - } - - void - tree_evaluator::keyboard (const std::string& prompt) - { - enter_debugger (prompt); - } - - void - tree_evaluator::dbupdown (int n, bool verbose) - { - m_debug_frame = m_call_stack.dbupdown (n, verbose); - } - - Matrix - tree_evaluator::ignored_fcn_outputs () const - { - Matrix retval; - - const std::list<octave_lvalue> *lvalues = m_lvalue_list; - - if (! lvalues) - return retval; - - octave_idx_type nbh = 0; - - for (const auto& lval : *lvalues) - nbh += lval.is_black_hole (); - - if (nbh > 0) - { - retval.resize (1, nbh); - - octave_idx_type k = 0; - octave_idx_type l = 0; - - for (const auto& lval : *lvalues) - { - if (lval.is_black_hole ()) - retval(l++) = k+1; - - k += lval.numel (); - } - } - - return retval; - } - - // If NAME is an operator (like "+", "-", ...), convert it to the - // corresponding function name ("plus", "minus", ...). - - static std::string - get_operator_function_name (const std::string& name) - { - // Bow to the god of compatibility. - - // FIXME: it seems ugly to put this here, but there is no single - // function in the parser that converts from the operator name to - // the corresponding function name. At least try to do it without N - // string compares. - - std::size_t len = name.length (); - - if (len == 2) - { - if (name[0] == '.') - { - switch (name[1]) - { - case '\'': - return "transpose"; - - case '*': - return "times"; - - case '/': - return "rdivide"; - - case '^': - return "power"; - - case '\\': - return "ldivide"; - - default: - break; - } - } - else if (name[1] == '=') - { - switch (name[0]) - { - case '<': - return "le"; - - case '=': - return "eq"; - - case '>': - return "ge"; - - case '~': - case '!': - return "ne"; - - default: - break; - } - } - } - else if (len == 1) - { - switch (name[0]) - { - case '~': - case '!': - return "not"; - - case '\'': - return "ctranspose"; - - case '+': - return "plus"; - - case '-': - return "minus"; - - case '*': - return "mtimes"; - - case '/': - return "mrdivide"; - - case '^': - return "mpower"; - - case '\\': - return "mldivide"; - - case '<': - return "lt"; - - case '>': - return "gt"; - - case '&': - return "and"; - - case '|': - return "or"; - - default: - break; - } - } - - return name; - } - - // Creates a function handle that takes into account the context, - // finding local, nested, private, or sub functions. - - octave_value - tree_evaluator::make_fcn_handle (const std::string& name) - { - octave_value retval; - - // The str2func function can create a function handle with the name - // of an operator (for example, "+"). If so, it is converted to the - // name of the corresponding function ("+" -> "plus") and we create - // a simple function handle using that name. - - std::string fcn_name = get_operator_function_name (name); - - // If FCN_NAME is different from NAME, then NAME is an operator. As - // of version 2020a, Matlab apparently uses the function name - // corresponding to the operator to search for private and local - // functions in the current scope but not(!) nested functions. - - bool name_is_operator = fcn_name != name; - - std::size_t pos = fcn_name.find ('.'); - - if (pos != std::string::npos) - { - // Recognize (some of? which ones?) the following cases - // and create something other than a simple function handle? - // Should we just be checking for the last two when the first - // element of the dot-separated list is an object? If so, then - // should this syntax be limited to a dot-separated list with - // exactly two elements? - // - // object . method - // object . static-method - // - // Code to do that duplicates some of simple_fcn_handle::call. - - // Only accept expressions that contain one '.' separator. - - // FIXME: The logic here is a bit complicated. Is there a good - // way to simplify it? - - std::string meth_nm = fcn_name.substr (pos+1); - - if (meth_nm.find ('.') == std::string::npos) - { - std::string obj_nm = fcn_name.substr (0, pos); - - // If obj_nm is an object in the current scope with a - // method named meth_nm, create a classsimple handle. - - octave_value object = varval (obj_nm); - - if (object.is_defined () && object.is_classdef_object ()) - { - octave_classdef *cdef = object.classdef_object_value (); - - if (cdef) - { - std::string class_nm = cdef->class_name (); - - cdef_object cdef_obj = cdef->get_object (); - - cdef_class cls = cdef_obj.get_class (); - - cdef_method meth = cls.find_method (meth_nm); - - if (meth.ok ()) - { - // If the method we found is static, create a - // new function name from the class name and - // method name and create a simple function - // handle below. Otherwise, create a class - // simple function handle. - - if (meth.is_static ()) - fcn_name = class_nm + '.' + meth_nm; - else - { - octave_value meth_fcn = meth.get_function (); - - octave_fcn_handle *fh - = new octave_fcn_handle (object, meth_fcn, - class_nm, meth_nm); - - return octave_value (fh); - } - } - } - } - } - - // We didn't match anything above, so create handle to SIMPLE - // package function or static class method. Function resolution - // is performed when the handle is used. - - return octave_value (new octave_fcn_handle (fcn_name)); - } - - // If the function name refers to a sub/local/private function or a - // class method/constructor, create scoped function handle that is - // bound to that function. Use the same precedence list as - // fcn_info::find but limit search to the following types of - // functions: - // - // nested functions (and subfunctions) - // local functions in the current file - // private function - // class method - // - // For anything else we create a simple function handle that will be - // resolved dynamically in the scope where it is evaluated. - - symbol_scope curr_scope = get_current_scope (); - - symbol_table& symtab = m_interpreter.get_symbol_table (); - - if (curr_scope) - { - octave_value ov_fcn - = symtab.find_scoped_function (fcn_name, curr_scope); - - // If name is operator, we are in Fstr2func, so skip the stack - // frame for that function. - - bool skip_first = name_is_operator; - octave_function *curr_fcn = current_function (skip_first); - - if (ov_fcn.is_defined ()) - { - octave_function *fcn = ov_fcn.function_value (); - - if (fcn->is_nested_function ()) - { - if (! name_is_operator) - { - // Get current stack frame and return handle to nested - // function. - - std::shared_ptr<stack_frame> frame - = m_call_stack.get_current_stack_frame (); - - // If we are creating a handle to the current - // function or a handle to a sibling function (i.e., - // not a child of the current function), then use - // the calling stack frame as the context instead of - // the current stack frame. - - // FIXME: Do we need both checks here or is it - // sufficient to check that the parent of curr_fcn - // is the same as the parent of fcn? Is there any - // case where curr_fcn could be nullptr, or does - // that indicate an internal error of some kind? - - if (curr_fcn - && (fcn_name == curr_fcn->name () - || fcn->parent_fcn_name () == curr_fcn->parent_fcn_name ())) - frame = frame->access_link (); - - octave_fcn_handle *fh - = new octave_fcn_handle (ov_fcn, fcn_name, frame); - - return octave_value (fh); - } - } - else if (fcn->is_subfunction () - /* || fcn->is_localfunction () */ - || fcn->is_private_function ()) - { - // Create handle to SCOPED function (sub/local function - // or private function). - - std::list<std::string> parentage = fcn->parent_fcn_names (); - - octave_fcn_handle *fh - = new octave_fcn_handle (ov_fcn, fcn_name, parentage); - - return octave_value (fh); - } - } - - if (curr_fcn && (curr_fcn->is_class_method () - || curr_fcn->is_class_constructor ())) - { - std::string dispatch_class = curr_fcn->dispatch_class (); - - octave_value ov_meth - = symtab.find_method (fcn_name, dispatch_class); - - if (ov_meth.is_defined ()) - { - octave_function *fcn = ov_meth.function_value (); - - // FIXME: do we need to check that it is a method of - // dispatch_class, or is it sufficient to just check - // that it is a method? - - if (fcn->is_class_method ()) - { - // Create CLASSSIMPLE handle to method but don't - // bind to the method. Lookup will be done later. - - octave_fcn_handle *fh - = new octave_fcn_handle (dispatch_class, fcn_name); - - return octave_value (fh); - } - } - } - } - - octave_value ov_fcn = symtab.find_user_function (fcn_name); - - // Create handle to SIMPLE function. If the function is not found - // now, then we will look for it again when the handle is used. - - return octave_value (new octave_fcn_handle (ov_fcn, fcn_name)); - } + + octave_idx_type nbh = 0; + + for (const auto& lval : *lvalues) + nbh += lval.is_black_hole (); + + if (nbh > 0) + { + retval.resize (1, nbh); + + octave_idx_type k = 0; + octave_idx_type l = 0; + + for (const auto& lval : *lvalues) + { + if (lval.is_black_hole ()) + retval(l++) = k+1; + + k += lval.numel (); + } + } + + return retval; +} + +// If NAME is an operator (like "+", "-", ...), convert it to the +// corresponding function name ("plus", "minus", ...). + +static std::string +get_operator_function_name (const std::string& name) +{ + // Bow to the god of compatibility. + + // FIXME: it seems ugly to put this here, but there is no single + // function in the parser that converts from the operator name to + // the corresponding function name. At least try to do it without N + // string compares. + + std::size_t len = name.length (); + + if (len == 2) + { + if (name[0] == '.') + { + switch (name[1]) + { + case '\'': + return "transpose"; + + case '*': + return "times"; + + case '/': + return "rdivide"; + + case '^': + return "power"; + + case '\\': + return "ldivide"; + + default: + break; + } + } + else if (name[1] == '=') + { + switch (name[0]) + { + case '<': + return "le"; + + case '=': + return "eq"; + + case '>': + return "ge"; + + case '~': + case '!': + return "ne"; + + default: + break; + } + } + } + else if (len == 1) + { + switch (name[0]) + { + case '~': + case '!': + return "not"; + + case '\'': + return "ctranspose"; + + case '+': + return "plus"; + + case '-': + return "minus"; + + case '*': + return "mtimes"; + + case '/': + return "mrdivide"; + + case '^': + return "mpower"; + + case '\\': + return "mldivide"; + + case '<': + return "lt"; + + case '>': + return "gt"; + + case '&': + return "and"; + + case '|': + return "or"; + + default: + break; + } + } + + return name; +} + +// Creates a function handle that takes into account the context, +// finding local, nested, private, or sub functions. + +octave_value +tree_evaluator::make_fcn_handle (const std::string& name) +{ + octave_value retval; + + // The str2func function can create a function handle with the name + // of an operator (for example, "+"). If so, it is converted to the + // name of the corresponding function ("+" -> "plus") and we create + // a simple function handle using that name. + + std::string fcn_name = get_operator_function_name (name); + + // If FCN_NAME is different from NAME, then NAME is an operator. As + // of version 2020a, Matlab apparently uses the function name + // corresponding to the operator to search for private and local + // functions in the current scope but not(!) nested functions. + + bool name_is_operator = fcn_name != name; + + std::size_t pos = fcn_name.find ('.'); + + if (pos != std::string::npos) + { + // Recognize (some of? which ones?) the following cases + // and create something other than a simple function handle? + // Should we just be checking for the last two when the first + // element of the dot-separated list is an object? If so, then + // should this syntax be limited to a dot-separated list with + // exactly two elements? + // + // object . method + // object . static-method + // + // Code to do that duplicates some of simple_fcn_handle::call. + + // Only accept expressions that contain one '.' separator. + + // FIXME: The logic here is a bit complicated. Is there a good + // way to simplify it? + + std::string meth_nm = fcn_name.substr (pos+1); + + if (meth_nm.find ('.') == std::string::npos) + { + std::string obj_nm = fcn_name.substr (0, pos); + + // If obj_nm is an object in the current scope with a + // method named meth_nm, create a classsimple handle. + + octave_value object = varval (obj_nm); + + if (object.is_defined () && object.is_classdef_object ()) + { + octave_classdef *cdef = object.classdef_object_value (); + + if (cdef) + { + std::string class_nm = cdef->class_name (); + + cdef_object cdef_obj = cdef->get_object (); + + cdef_class cls = cdef_obj.get_class (); + + cdef_method meth = cls.find_method (meth_nm); + + if (meth.ok ()) + { + // If the method we found is static, create a + // new function name from the class name and + // method name and create a simple function + // handle below. Otherwise, create a class + // simple function handle. + + if (meth.is_static ()) + fcn_name = class_nm + '.' + meth_nm; + else + { + octave_value meth_fcn = meth.get_function (); + + octave_fcn_handle *fh + = new octave_fcn_handle (object, meth_fcn, + class_nm, meth_nm); + + return octave_value (fh); + } + } + } + } + } + + // We didn't match anything above, so create handle to SIMPLE + // package function or static class method. Function resolution + // is performed when the handle is used. + + return octave_value (new octave_fcn_handle (fcn_name)); + } + + // If the function name refers to a sub/local/private function or a + // class method/constructor, create scoped function handle that is + // bound to that function. Use the same precedence list as + // fcn_info::find but limit search to the following types of + // functions: + // + // nested functions (and subfunctions) + // local functions in the current file + // private function + // class method + // + // For anything else we create a simple function handle that will be + // resolved dynamically in the scope where it is evaluated. + + symbol_scope curr_scope = get_current_scope (); + + symbol_table& symtab = m_interpreter.get_symbol_table (); + + if (curr_scope) + { + octave_value ov_fcn + = symtab.find_scoped_function (fcn_name, curr_scope); + + // If name is operator, we are in Fstr2func, so skip the stack + // frame for that function. + + bool skip_first = name_is_operator; + octave_function *curr_fcn = current_function (skip_first); + + if (ov_fcn.is_defined ()) + { + octave_function *fcn = ov_fcn.function_value (); + + if (fcn->is_nested_function ()) + { + if (! name_is_operator) + { + // Get current stack frame and return handle to nested + // function. + + std::shared_ptr<stack_frame> frame + = m_call_stack.get_current_stack_frame (); + + // If we are creating a handle to the current + // function or a handle to a sibling function (i.e., + // not a child of the current function), then use + // the calling stack frame as the context instead of + // the current stack frame. + + // FIXME: Do we need both checks here or is it + // sufficient to check that the parent of curr_fcn + // is the same as the parent of fcn? Is there any + // case where curr_fcn could be nullptr, or does + // that indicate an internal error of some kind? + + if (curr_fcn + && (fcn_name == curr_fcn->name () + || fcn->parent_fcn_name () == curr_fcn->parent_fcn_name ())) + frame = frame->access_link (); + + octave_fcn_handle *fh + = new octave_fcn_handle (ov_fcn, fcn_name, frame); + + return octave_value (fh); + } + } + else if (fcn->is_subfunction () + /* || fcn->is_localfunction () */ + || fcn->is_private_function ()) + { + // Create handle to SCOPED function (sub/local function + // or private function). + + std::list<std::string> parentage = fcn->parent_fcn_names (); + + octave_fcn_handle *fh + = new octave_fcn_handle (ov_fcn, fcn_name, parentage); + + return octave_value (fh); + } + } + + if (curr_fcn && (curr_fcn->is_class_method () + || curr_fcn->is_class_constructor ())) + { + std::string dispatch_class = curr_fcn->dispatch_class (); + + octave_value ov_meth + = symtab.find_method (fcn_name, dispatch_class); + + if (ov_meth.is_defined ()) + { + octave_function *fcn = ov_meth.function_value (); + + // FIXME: do we need to check that it is a method of + // dispatch_class, or is it sufficient to just check + // that it is a method? + + if (fcn->is_class_method ()) + { + // Create CLASSSIMPLE handle to method but don't + // bind to the method. Lookup will be done later. + + octave_fcn_handle *fh + = new octave_fcn_handle (dispatch_class, fcn_name); + + return octave_value (fh); + } + } + } + } + + octave_value ov_fcn = symtab.find_user_function (fcn_name); + + // Create handle to SIMPLE function. If the function is not found + // now, then we will look for it again when the handle is used. + + return octave_value (new octave_fcn_handle (ov_fcn, fcn_name)); +} /* %!test @@ -1831,405 +1831,405 @@ %! endfor */ - octave_value - tree_evaluator::evaluate (tree_decl_elt *elt) - { - // Do not allow functions to return null values. - - tree_identifier *id = elt->ident (); - - return id ? id->evaluate (*this).storable_value () : octave_value (); - } - - bool - tree_evaluator::is_variable (const std::string& name) const - { - std::shared_ptr<stack_frame> frame - = m_call_stack.get_current_stack_frame (); - - return frame->is_variable (name); - } - - bool - tree_evaluator::is_local_variable (const std::string& name) const - { - std::shared_ptr<stack_frame> frame - = m_call_stack.get_current_stack_frame (); - - return frame->is_local_variable (name); - } - - bool - tree_evaluator::is_variable (const tree_expression *expr) const - { - if (expr->is_identifier ()) - { - const tree_identifier *id - = dynamic_cast<const tree_identifier *> (expr); - - if (id->is_black_hole ()) - return false; - - return is_variable (id->symbol ()); - } - - return false; - } - - bool - tree_evaluator::is_defined (const tree_expression *expr) const - { - if (expr->is_identifier ()) - { - const tree_identifier *id - = dynamic_cast<const tree_identifier *> (expr); - - return is_defined (id->symbol ()); - } - - return false; - } - - bool - tree_evaluator::is_variable (const symbol_record& sym) const - { - std::shared_ptr<stack_frame> frame - = m_call_stack.get_current_stack_frame (); - - return frame->is_variable (sym); - } - - bool - tree_evaluator::is_defined (const symbol_record& sym) const - { - std::shared_ptr<stack_frame> frame - = m_call_stack.get_current_stack_frame (); - - return frame->is_defined (sym); - } - - bool tree_evaluator::is_global (const std::string& name) const - { - std::shared_ptr<stack_frame> frame - = m_call_stack.get_current_stack_frame (); - - return frame->is_global (name); - } - - octave_value - tree_evaluator::varval (const symbol_record& sym) const - { - std::shared_ptr<stack_frame> frame - = m_call_stack.get_current_stack_frame (); - - return frame->varval (sym); - } - - octave_value - tree_evaluator::varval (const std::string& name) const - { - std::shared_ptr<stack_frame> frame - = m_call_stack.get_current_stack_frame (); - - return frame->varval (name); - } - - void tree_evaluator::install_variable (const std::string& name, - const octave_value& value, - bool global) - { - std::shared_ptr<stack_frame> frame - = m_call_stack.get_current_stack_frame (); - - return frame->install_variable (name, value, global); - } - - octave_value - tree_evaluator::global_varval (const std::string& name) const - { - return m_call_stack.global_varval (name); - } - - octave_value& - tree_evaluator::global_varref (const std::string& name) - { - return m_call_stack.global_varref (name); - } - - void - tree_evaluator::global_assign (const std::string& name, - const octave_value& val) - { - m_call_stack.global_varref (name) = val; - } - - octave_value - tree_evaluator::top_level_varval (const std::string& name) const - { - return m_call_stack.get_top_level_value (name); - } - - void - tree_evaluator::top_level_assign (const std::string& name, - const octave_value& val) - { - m_call_stack.set_top_level_value (name, val); - } - - void - tree_evaluator::assign (const std::string& name, const octave_value& val) - { - std::shared_ptr<stack_frame> frame - = m_call_stack.get_current_stack_frame (); - - frame->assign (name, val); - } - - void - tree_evaluator::assignin (const std::string& context, - const std::string& name, const octave_value& val) - { - // FIXME: Can this be done without an unwind-protect frame, simply - // by getting a reference to the caller or base stack frame and - // calling assign on that? - - unwind_action act ([=] (std::size_t frm) - { - m_call_stack.restore_frame (frm); - }, m_call_stack.current_frame ()); - - if (context == "caller") - m_call_stack.goto_caller_frame (); - else if (context == "base") - m_call_stack.goto_base_frame (); - else - error (R"(assignin: CONTEXT must be "caller" or "base")"); - - if (valid_identifier (name)) - { - // Put the check here so that we don't slow down assignments - // generally. Any that go through Octave's parser should have - // already been checked. - - if (iskeyword (name)) - error ("assignin: invalid assignment to keyword '%s'", - name.c_str ()); - - assign (name, val); - } - else - error ("assignin: invalid variable name '%s'", name.c_str ()); - } - - void - tree_evaluator::source_file (const std::string& file_name, - const std::string& context, - bool verbose, bool require_file) - { - // Map from absolute name of script file to recursion level. We - // use a map instead of simply placing a limit on recursion in the - // source_file function so that two mutually recursive scripts - // written as - // - // foo1.m: - // ------ - // foo2 - // - // foo2.m: - // ------ - // foo1 - // - // and called with - // - // foo1 - // - // (for example) will behave the same if they are written as - // - // foo1.m: - // ------ - // source ("foo2.m") - // - // foo2.m: - // ------ - // source ("foo1.m") - // - // and called with - // - // source ("foo1.m") - // - // (for example). - - static std::map<std::string, int> source_call_depth; - - std::string file_full_name - = sys::file_ops::tilde_expand (file_name); - - std::size_t pos - = file_full_name.find_last_of (sys::file_ops::dir_sep_str ()); - - std::string dir_name = file_full_name.substr (0, pos); - - file_full_name = sys::env::make_absolute (file_full_name); - - unwind_protect frame; - - if (source_call_depth.find (file_full_name) == source_call_depth.end ()) - source_call_depth[file_full_name] = -1; - - frame.protect_var (source_call_depth[file_full_name]); - - source_call_depth[file_full_name]++; - - if (source_call_depth[file_full_name] >= max_recursion_depth ()) - error ("max_recursion_depth exceeded"); - - if (! context.empty ()) - { - frame.add (&call_stack::restore_frame, &m_call_stack, - m_call_stack.current_frame ()); - - if (context == "caller") - m_call_stack.goto_caller_frame (); - else if (context == "base") - m_call_stack.goto_base_frame (); - else - error (R"(source: CONTEXT must be "caller" or "base")"); - } - - // Find symbol name that would be in symbol_table, if it were loaded. - std::size_t dir_end - = file_name.find_last_of (sys::file_ops::dir_sep_chars ()); - dir_end = (dir_end == std::string::npos) ? 0 : dir_end + 1; - - std::size_t extension = file_name.find_last_of ('.'); - if (extension == std::string::npos) - extension = file_name.length (); - - std::string symbol = file_name.substr (dir_end, extension - dir_end); - std::string full_name = sys::canonicalize_file_name (file_name); - - // Check if this file is already loaded (or in the path) - symbol_table& symtab = m_interpreter.get_symbol_table (); - octave_value ov_code = symtab.fcn_table_find (symbol); - - // For compatibility with Matlab, accept both scripts and - // functions. - - if (ov_code.is_user_code ()) - { - octave_user_code *code = ov_code.user_code_value (); - - if (! code - || (sys::canonicalize_file_name (code->fcn_file_name ()) - != full_name)) - { - // Wrong file, so load it below. - ov_code = octave_value (); - } - } - else - { - // Not a script, so load it below. - ov_code = octave_value (); - } - - // If no symbol of this name, or the symbol is for a different - // file, load. - - if (ov_code.is_undefined ()) - { - try - { - ov_code = parse_fcn_file (m_interpreter, file_full_name, - file_name, dir_name, "", "", - require_file, true, false, false); - } - catch (execution_exception& ee) - { - error (ee, "source: error sourcing file '%s'", - file_full_name.c_str ()); - } - } - - // Return or error if we don't have a valid script or function. - - if (ov_code.is_undefined ()) - return; - - if (! ov_code.is_user_code ()) - error ("source: %s is not a script", full_name.c_str ()); - - if (verbose) - { - octave_stdout << "executing commands from " << full_name << " ... "; - octave_stdout.flush (); - } - - octave_user_code *code = ov_code.user_code_value (); - - code->call (*this, 0, octave_value_list ()); - - if (verbose) - octave_stdout << "done." << std::endl; - } - - void - tree_evaluator::set_auto_fcn_var (stack_frame::auto_var_type avt, - const octave_value& val) - { - m_call_stack.set_auto_fcn_var (avt, val); - } - - octave_value - tree_evaluator::get_auto_fcn_var (stack_frame::auto_var_type avt) const - { - return m_call_stack.get_auto_fcn_var (avt); - } - - void - tree_evaluator::define_parameter_list_from_arg_vector - (tree_parameter_list *param_list, const octave_value_list& args) - { - if (! param_list || param_list->varargs_only ()) - return; - - int i = -1; - - for (tree_decl_elt *elt : *param_list) - { - i++; - - octave_lvalue ref = elt->lvalue (*this); - - if (i < args.length ()) - { - if (args(i).is_defined () && args(i).is_magic_colon ()) - { - if (! eval_decl_elt (elt)) - error ("no default value for argument %d", i+1); - } - else - ref.define (args(i)); - } - else - eval_decl_elt (elt); - } - } - - void - tree_evaluator::undefine_parameter_list (tree_parameter_list *param_list) - { - for (tree_decl_elt *elt : *param_list) - { - octave_lvalue ref = elt->lvalue (*this); - - ref.assign (octave_value::op_asn_eq, octave_value ()); - } - } +octave_value +tree_evaluator::evaluate (tree_decl_elt *elt) +{ + // Do not allow functions to return null values. + + tree_identifier *id = elt->ident (); + + return id ? id->evaluate (*this).storable_value () : octave_value (); +} + +bool +tree_evaluator::is_variable (const std::string& name) const +{ + std::shared_ptr<stack_frame> frame + = m_call_stack.get_current_stack_frame (); + + return frame->is_variable (name); +} + +bool +tree_evaluator::is_local_variable (const std::string& name) const +{ + std::shared_ptr<stack_frame> frame + = m_call_stack.get_current_stack_frame (); + + return frame->is_local_variable (name); +} + +bool +tree_evaluator::is_variable (const tree_expression *expr) const +{ + if (expr->is_identifier ()) + { + const tree_identifier *id + = dynamic_cast<const tree_identifier *> (expr); + + if (id->is_black_hole ()) + return false; + + return is_variable (id->symbol ()); + } + + return false; +} + +bool +tree_evaluator::is_defined (const tree_expression *expr) const +{ + if (expr->is_identifier ()) + { + const tree_identifier *id + = dynamic_cast<const tree_identifier *> (expr); + + return is_defined (id->symbol ()); + } + + return false; +} + +bool +tree_evaluator::is_variable (const symbol_record& sym) const +{ + std::shared_ptr<stack_frame> frame + = m_call_stack.get_current_stack_frame (); + + return frame->is_variable (sym); +} + +bool +tree_evaluator::is_defined (const symbol_record& sym) const +{ + std::shared_ptr<stack_frame> frame + = m_call_stack.get_current_stack_frame (); + + return frame->is_defined (sym); +} + +bool tree_evaluator::is_global (const std::string& name) const +{ + std::shared_ptr<stack_frame> frame + = m_call_stack.get_current_stack_frame (); + + return frame->is_global (name); +} + +octave_value +tree_evaluator::varval (const symbol_record& sym) const +{ + std::shared_ptr<stack_frame> frame + = m_call_stack.get_current_stack_frame (); + + return frame->varval (sym); +} + +octave_value +tree_evaluator::varval (const std::string& name) const +{ + std::shared_ptr<stack_frame> frame + = m_call_stack.get_current_stack_frame (); + + return frame->varval (name); +} + +void tree_evaluator::install_variable (const std::string& name, + const octave_value& value, + bool global) +{ + std::shared_ptr<stack_frame> frame + = m_call_stack.get_current_stack_frame (); + + return frame->install_variable (name, value, global); +} + +octave_value +tree_evaluator::global_varval (const std::string& name) const +{ + return m_call_stack.global_varval (name); +} + +octave_value& +tree_evaluator::global_varref (const std::string& name) +{ + return m_call_stack.global_varref (name); +} + +void +tree_evaluator::global_assign (const std::string& name, + const octave_value& val) +{ + m_call_stack.global_varref (name) = val; +} + +octave_value +tree_evaluator::top_level_varval (const std::string& name) const +{ + return m_call_stack.get_top_level_value (name); +} + +void +tree_evaluator::top_level_assign (const std::string& name, + const octave_value& val) +{ + m_call_stack.set_top_level_value (name, val); +} + +void +tree_evaluator::assign (const std::string& name, const octave_value& val) +{ + std::shared_ptr<stack_frame> frame + = m_call_stack.get_current_stack_frame (); + + frame->assign (name, val); +} + +void +tree_evaluator::assignin (const std::string& context, + const std::string& name, const octave_value& val) +{ + // FIXME: Can this be done without an unwind-protect frame, simply + // by getting a reference to the caller or base stack frame and + // calling assign on that? + + unwind_action act ([=] (std::size_t frm) + { + m_call_stack.restore_frame (frm); + }, m_call_stack.current_frame ()); + + if (context == "caller") + m_call_stack.goto_caller_frame (); + else if (context == "base") + m_call_stack.goto_base_frame (); + else + error (R"(assignin: CONTEXT must be "caller" or "base")"); + + if (valid_identifier (name)) + { + // Put the check here so that we don't slow down assignments + // generally. Any that go through Octave's parser should have + // already been checked. + + if (iskeyword (name)) + error ("assignin: invalid assignment to keyword '%s'", + name.c_str ()); + + assign (name, val); + } + else + error ("assignin: invalid variable name '%s'", name.c_str ()); +} + +void +tree_evaluator::source_file (const std::string& file_name, + const std::string& context, + bool verbose, bool require_file) +{ + // Map from absolute name of script file to recursion level. We + // use a map instead of simply placing a limit on recursion in the + // source_file function so that two mutually recursive scripts + // written as + // + // foo1.m: + // ------ + // foo2 + // + // foo2.m: + // ------ + // foo1 + // + // and called with + // + // foo1 + // + // (for example) will behave the same if they are written as + // + // foo1.m: + // ------ + // source ("foo2.m") + // + // foo2.m: + // ------ + // source ("foo1.m") + // + // and called with + // + // source ("foo1.m") + // + // (for example). + + static std::map<std::string, int> source_call_depth; + + std::string file_full_name + = sys::file_ops::tilde_expand (file_name); + + std::size_t pos + = file_full_name.find_last_of (sys::file_ops::dir_sep_str ()); + + std::string dir_name = file_full_name.substr (0, pos); + + file_full_name = sys::env::make_absolute (file_full_name); + + unwind_protect frame; + + if (source_call_depth.find (file_full_name) == source_call_depth.end ()) + source_call_depth[file_full_name] = -1; + + frame.protect_var (source_call_depth[file_full_name]); + + source_call_depth[file_full_name]++; + + if (source_call_depth[file_full_name] >= max_recursion_depth ()) + error ("max_recursion_depth exceeded"); + + if (! context.empty ()) + { + frame.add (&call_stack::restore_frame, &m_call_stack, + m_call_stack.current_frame ()); + + if (context == "caller") + m_call_stack.goto_caller_frame (); + else if (context == "base") + m_call_stack.goto_base_frame (); + else + error (R"(source: CONTEXT must be "caller" or "base")"); + } + + // Find symbol name that would be in symbol_table, if it were loaded. + std::size_t dir_end + = file_name.find_last_of (sys::file_ops::dir_sep_chars ()); + dir_end = (dir_end == std::string::npos) ? 0 : dir_end + 1; + + std::size_t extension = file_name.find_last_of ('.'); + if (extension == std::string::npos) + extension = file_name.length (); + + std::string symbol = file_name.substr (dir_end, extension - dir_end); + std::string full_name = sys::canonicalize_file_name (file_name); + + // Check if this file is already loaded (or in the path) + symbol_table& symtab = m_interpreter.get_symbol_table (); + octave_value ov_code = symtab.fcn_table_find (symbol); + + // For compatibility with Matlab, accept both scripts and + // functions. + + if (ov_code.is_user_code ()) + { + octave_user_code *code = ov_code.user_code_value (); + + if (! code + || (sys::canonicalize_file_name (code->fcn_file_name ()) + != full_name)) + { + // Wrong file, so load it below. + ov_code = octave_value (); + } + } + else + { + // Not a script, so load it below. + ov_code = octave_value (); + } + + // If no symbol of this name, or the symbol is for a different + // file, load. + + if (ov_code.is_undefined ()) + { + try + { + ov_code = parse_fcn_file (m_interpreter, file_full_name, + file_name, dir_name, "", "", + require_file, true, false, false); + } + catch (execution_exception& ee) + { + error (ee, "source: error sourcing file '%s'", + file_full_name.c_str ()); + } + } + + // Return or error if we don't have a valid script or function. + + if (ov_code.is_undefined ()) + return; + + if (! ov_code.is_user_code ()) + error ("source: %s is not a script", full_name.c_str ()); + + if (verbose) + { + octave_stdout << "executing commands from " << full_name << " ... "; + octave_stdout.flush (); + } + + octave_user_code *code = ov_code.user_code_value (); + + code->call (*this, 0, octave_value_list ()); + + if (verbose) + octave_stdout << "done." << std::endl; +} + +void +tree_evaluator::set_auto_fcn_var (stack_frame::auto_var_type avt, + const octave_value& val) +{ + m_call_stack.set_auto_fcn_var (avt, val); +} + +octave_value +tree_evaluator::get_auto_fcn_var (stack_frame::auto_var_type avt) const +{ + return m_call_stack.get_auto_fcn_var (avt); +} + +void +tree_evaluator::define_parameter_list_from_arg_vector + (tree_parameter_list *param_list, const octave_value_list& args) +{ + if (! param_list || param_list->varargs_only ()) + return; + + int i = -1; + + for (tree_decl_elt *elt : *param_list) + { + i++; + + octave_lvalue ref = elt->lvalue (*this); + + if (i < args.length ()) + { + if (args(i).is_defined () && args(i).is_magic_colon ()) + { + if (! eval_decl_elt (elt)) + error ("no default value for argument %d", i+1); + } + else + ref.define (args(i)); + } + else + eval_decl_elt (elt); + } +} + +void +tree_evaluator::undefine_parameter_list (tree_parameter_list *param_list) +{ + for (tree_decl_elt *elt : *param_list) + { + octave_lvalue ref = elt->lvalue (*this); + + ref.assign (octave_value::op_asn_eq, octave_value ()); + } +} // END is documented in op-kw-docs. DEFMETHOD (end, interp, args, , - doc: /* -*- texinfo -*- + doc: /* -*- texinfo -*- @deftypefn {} {} end Last element of an array or the end of any @code{for}, @code{parfor}, @code{if}, @code{do}, @code{while}, @code{function}, @code{switch}, @@ -2244,19 +2244,19 @@ @group @var{x} = [ 1 2 3; 4 5 6 ]; @var{x}(1,end) - @result{} 3 + @result{} 3 @var{x}(end,1) - @result{} 4 + @result{} 4 @var{x}(end,end) - @result{} 6 + @result{} 6 @end group @end example @seealso{for, parfor, if, do, while, function, switch, try, unwind_protect} @end deftypefn */) { - tree_evaluator& tw = interp.get_evaluator (); - - return tw.evaluate_end_expression (args); +tree_evaluator& tw = interp.get_evaluator (); + +return tw.evaluate_end_expression (args); } /* @@ -2271,2991 +2271,2991 @@ %! assert (x(minus (minus (end, 1), 1)), 8); */ - octave_value_list - tree_evaluator::convert_to_const_vector (tree_argument_list *args) - { - std::list<octave_value> arg_vals; - - for (auto elt : *args) +octave_value_list +tree_evaluator::convert_to_const_vector (tree_argument_list *args) +{ + std::list<octave_value> arg_vals; + + for (auto elt : *args) + { + // FIXME: is it possible for elt to be invalid? + + if (! elt) + break; + + octave_value tmp = elt->evaluate (*this); + + if (tmp.is_cs_list ()) + { + octave_value_list tmp_ovl = tmp.list_value (); + + for (octave_idx_type i = 0; i < tmp_ovl.length (); i++) + arg_vals.push_back (tmp_ovl(i)); + } + else if (tmp.is_defined ()) + arg_vals.push_back (tmp); + } + + return octave_value_list (arg_vals); +} + +octave_value_list +tree_evaluator::convert_return_list_to_const_vector + (tree_parameter_list *ret_list, int nargout, const Matrix& ignored_outputs, + const Cell& varargout) +{ + octave_idx_type vlen = varargout.numel (); + int len = ret_list->length (); + + // Special case. Will do a shallow copy. + if (len == 0) + return varargout; + else + { + int i = 0; + int k = 0; + int num_ignored = ignored_outputs.numel (); + int ignored = num_ignored > 0 ? ignored_outputs(k) - 1 : -1; + + if (nargout <= len) + { + int nout = nargout > 0 ? nargout : 1; + octave_value_list retval (nout); + + for (tree_decl_elt *elt : *ret_list) + { + if (nargout == 0 && ! is_defined (elt->ident ())) + break; + + if (ignored >= 0 && i == ignored) + { + i++; + k++; + ignored = k < num_ignored ? ignored_outputs(k) - 1 : -1; + } + else + retval(i++) = evaluate (elt); + + if (i == nout) + break; + } + + return retval; + } + else + { + octave_value_list retval (len + vlen); + + for (tree_decl_elt *elt : *ret_list) + { + if (ignored >= 0 && i == ignored) + { + i++; + k++; + ignored = k < num_ignored ? ignored_outputs(k) - 1 : -1; + } + else + retval(i++) = evaluate (elt); + } + + for (octave_idx_type j = 0; j < vlen; j++) + retval(i++) = varargout(j); + + return retval; + } + } +} + +bool +tree_evaluator::eval_decl_elt (tree_decl_elt *elt) +{ + bool retval = false; + + tree_identifier *id = elt->ident (); + tree_expression *expr = elt->expression (); + + if (id && expr) + { + octave_lvalue ult = id->lvalue (*this); + + octave_value init_val = expr->evaluate (*this); + + ult.assign (octave_value::op_asn_eq, init_val); + + retval = true; + } + + return retval; +} + +bool +tree_evaluator::switch_case_label_matches (tree_switch_case *expr, + const octave_value& val) +{ + tree_expression *label = expr->case_label (); + + octave_value label_value = label->evaluate (*this); + + if (label_value.is_defined ()) + { + if (label_value.iscell ()) + { + Cell cell (label_value.cell_value ()); + + for (octave_idx_type i = 0; i < cell.rows (); i++) + { + for (octave_idx_type j = 0; j < cell.columns (); j++) + { + bool match = val.is_equal (cell(i,j)); + + if (match) + return true; + } + } + } + else + return val.is_equal (label_value); + } + + return false; +} + +void tree_evaluator::push_stack_frame (const symbol_scope& scope) +{ + m_call_stack.push (scope); +} + +void tree_evaluator::push_stack_frame (octave_user_function *fcn, + const std::shared_ptr<stack_frame>& closure_frames) +{ + m_call_stack.push (fcn, closure_frames); +} + +void tree_evaluator::push_stack_frame (octave_user_function *fcn, + const stack_frame::local_vars_map& local_vars, + const std::shared_ptr<stack_frame>& closure_frames) +{ + m_call_stack.push (fcn, local_vars, closure_frames); +} + +void tree_evaluator::push_stack_frame (octave_user_script *script) +{ + m_call_stack.push (script); +} + +void tree_evaluator::push_stack_frame (octave_function *fcn) +{ + m_call_stack.push (fcn); +} + +void tree_evaluator::pop_stack_frame () +{ + m_call_stack.pop (); +} + +int tree_evaluator::current_line () const +{ + return m_call_stack.current_line (); +} + +int tree_evaluator::current_column () const +{ + return m_call_stack.current_column (); +} + +int tree_evaluator::debug_user_code_line () const +{ + return m_call_stack.debug_user_code_line (); +} + +int tree_evaluator::debug_user_code_column () const +{ + return m_call_stack.debug_user_code_column (); +} + +void tree_evaluator::debug_where (std::ostream& os) const +{ + std::shared_ptr<stack_frame> frm = m_call_stack.current_user_frame (); + + frm->display_stopped_in_message (os); +} + +octave_user_code * tree_evaluator::current_user_code () const +{ + return m_call_stack.current_user_code (); +} + +unwind_protect * tree_evaluator::curr_fcn_unwind_protect_frame () +{ + return m_call_stack.curr_fcn_unwind_protect_frame (); +} + +octave_user_code * tree_evaluator::debug_user_code () const +{ + return m_call_stack.debug_user_code (); +} + +octave_function * tree_evaluator::current_function (bool skip_first) const +{ + return m_call_stack.current_function (skip_first); +} + +octave_function * tree_evaluator::caller_function () const +{ + return m_call_stack.current_function (true); +} + +bool tree_evaluator::goto_frame (std::size_t n, bool verbose) +{ + return m_call_stack.goto_frame (n, verbose); +} + +void tree_evaluator::goto_caller_frame () +{ + m_call_stack.goto_caller_frame (); +} + +void tree_evaluator::goto_base_frame () +{ + m_call_stack.goto_base_frame (); +} + +void tree_evaluator::restore_frame (std::size_t n) +{ + return m_call_stack.restore_frame (n); +} + +std::string tree_evaluator::get_dispatch_class () const +{ + return m_call_stack.get_dispatch_class (); +} + +void tree_evaluator::set_dispatch_class (const std::string& class_name) +{ + m_call_stack.set_dispatch_class (class_name); +} + +bool +tree_evaluator::is_class_method_executing (std::string& dclass) const +{ + return m_call_stack.is_class_method_executing (dclass); +} + +bool +tree_evaluator::is_class_constructor_executing (std::string& dclass) const +{ + return m_call_stack.is_class_constructor_executing (dclass); +} + +std::list<std::shared_ptr<stack_frame>> +tree_evaluator::backtrace_frames (octave_idx_type& curr_user_frame) const +{ + return m_call_stack.backtrace_frames (curr_user_frame); +} + +std::list<std::shared_ptr<stack_frame>> +tree_evaluator::backtrace_frames () const +{ + return m_call_stack.backtrace_frames (); +} + +std::list<frame_info> +tree_evaluator::backtrace_info (octave_idx_type& curr_user_frame, + bool print_subfn) const +{ + return m_call_stack.backtrace_info (curr_user_frame, print_subfn); +} + +std::list<frame_info> tree_evaluator::backtrace_info () const +{ + return m_call_stack.backtrace_info (); +} + +octave_map +tree_evaluator::backtrace (octave_idx_type& curr_user_frame, + bool print_subfn) const +{ + return m_call_stack.backtrace (curr_user_frame, print_subfn); +} + +octave_map tree_evaluator::backtrace () const +{ + return m_call_stack.backtrace (); +} + +octave_map tree_evaluator::empty_backtrace () const +{ + return m_call_stack.empty_backtrace (); +} + +std::string tree_evaluator::backtrace_message () const +{ + std::list<frame_info> frames = backtrace_info (); + + std::ostringstream buf; + + for (const auto& frm : frames) + { + buf << " " << frm.fcn_name (); + + int line = frm.line (); + + if (line > 0) + { + buf << " at line " << line; + + int column = frm.column (); + + if (column > 0) + buf << " column " << column; + + buf << "\n"; + } + } + + return buf.str (); +} + +void tree_evaluator::push_dummy_scope (const std::string& name) +{ + symbol_scope dummy_scope (name + "$dummy"); + + m_call_stack.push (dummy_scope); +} + +void tree_evaluator::pop_scope () +{ + m_call_stack.pop (); +} + +symbol_scope tree_evaluator::get_top_scope () const +{ + return m_call_stack.top_scope (); +} + +symbol_scope tree_evaluator::get_current_scope () const +{ + return m_call_stack.current_scope (); +} + +void tree_evaluator::mlock (bool skip_first) const +{ + octave_function *fcn = m_call_stack.current_function (skip_first); + + if (! fcn) + error ("mlock: invalid use outside a function"); + + if (fcn->is_builtin_function ()) + { + warning ("mlock: locking built-in function has no effect"); + return; + } + + fcn->lock (); +} + +void tree_evaluator::munlock (bool skip_first) const +{ + octave_function *fcn = m_call_stack.current_function (skip_first); + + if (! fcn) + error ("munlock: invalid use outside a function"); + + if (fcn->is_builtin_function ()) + { + warning ("munlock: unlocking built-in function has no effect"); + return; + } + + fcn->unlock (); +} + +bool tree_evaluator::mislocked (bool skip_first) const +{ + octave_function *fcn = m_call_stack.current_function (skip_first); + + if (! fcn) + error ("mislocked: invalid use outside a function"); + + return fcn->islocked (); +} + +octave_value +tree_evaluator::max_stack_depth (const octave_value_list& args, int nargout) +{ + return m_call_stack.max_stack_depth (args, nargout); +} + +void tree_evaluator::display_call_stack () const +{ + m_call_stack.display (); +} + +octave_value tree_evaluator::find (const std::string& name) +{ + std::shared_ptr<stack_frame> frame + = m_call_stack.get_current_stack_frame (); + + octave_value val = frame->varval (name); + + if (val.is_defined ()) + return val; + + // Subfunction. I think it only makes sense to check for + // subfunctions if we are currently executing a function defined + // from a .m file. + + octave_value fcn = frame->find_subfunction (name); + + if (fcn.is_defined ()) + return fcn; + + symbol_table& symtab = m_interpreter.get_symbol_table (); + + return symtab.fcn_table_find (name, ovl ()); +} + +void tree_evaluator::clear_objects () +{ + std::shared_ptr<stack_frame> frame + = m_call_stack.get_current_stack_frame (); + + frame->clear_objects (); +} + +void tree_evaluator::clear_variable (const std::string& name) +{ + std::shared_ptr<stack_frame> frame + = m_call_stack.get_current_stack_frame (); + + frame->clear_variable (name); +} + +void tree_evaluator::clear_variable_pattern (const std::string& pattern) +{ + std::shared_ptr<stack_frame> frame + = m_call_stack.get_current_stack_frame (); + + frame->clear_variable_pattern (pattern); +} + +void tree_evaluator::clear_variable_regexp (const std::string& pattern) +{ + std::shared_ptr<stack_frame> frame + = m_call_stack.get_current_stack_frame (); + + frame->clear_variable_regexp (pattern); +} + +void tree_evaluator::clear_variables () +{ + std::shared_ptr<stack_frame> frame + = m_call_stack.get_current_stack_frame (); + + frame->clear_variables (); +} + +void tree_evaluator::clear_global_variable (const std::string& name) +{ + m_call_stack.clear_global_variable (name); +} + +void +tree_evaluator::clear_global_variable_pattern (const std::string& pattern) +{ + m_call_stack.clear_global_variable_pattern (pattern); +} + +void tree_evaluator::clear_global_variable_regexp(const std::string& pattern) +{ + m_call_stack.clear_global_variable_regexp (pattern); +} + +void tree_evaluator::clear_global_variables () +{ + m_call_stack.clear_global_variables (); +} + +void tree_evaluator::clear_all (bool force) +{ + // FIXME: should this also clear objects? + + clear_variables (); + clear_global_variables (); + + symbol_table& symtab = m_interpreter.get_symbol_table (); + + symtab.clear_functions (force); +} + +void tree_evaluator::clear_symbol (const std::string& name) +{ + // FIXME: are we supposed to do both here? + + clear_variable (name); + + symbol_table& symtab = m_interpreter.get_symbol_table (); + + symtab.clear_function (name); +} + +void tree_evaluator::clear_symbol_pattern (const std::string& pattern) +{ + // FIXME: are we supposed to do both here? + + clear_variable_pattern (pattern); + + symbol_table& symtab = m_interpreter.get_symbol_table (); + + symtab.clear_function_pattern (pattern); +} + +void tree_evaluator::clear_symbol_regexp (const std::string& pattern) +{ + // FIXME: are we supposed to do both here? + + clear_variable_regexp (pattern); + + symbol_table& symtab = m_interpreter.get_symbol_table (); + + symtab.clear_function_regexp (pattern); +} + +std::list<std::string> tree_evaluator::global_variable_names () const +{ + return m_call_stack.global_variable_names (); +} + +std::list<std::string> tree_evaluator::top_level_variable_names () const +{ + return m_call_stack.top_level_variable_names (); +} + +std::list<std::string> tree_evaluator::variable_names () const +{ + return m_call_stack.variable_names (); +} + +// Return a pointer to the user-defined function FNAME. If FNAME is empty, +// search backward for the first user-defined function in the +// current call stack. + +octave_user_code * +tree_evaluator::get_user_code (const std::string& fname, + const std::string& class_name) +{ + octave_user_code *user_code = nullptr; + + if (fname.empty ()) + user_code = m_call_stack.debug_user_code (); + else + { + std::string name = fname; + + if (sys::file_ops::dir_sep_char () != '/' && name[0] == '@') + { + auto beg = name.begin () + 2; // never have @/method + auto end = name.end () - 1; // never have trailing '/' + std::replace (beg, end, '/', sys::file_ops::dir_sep_char ()); + } + + std::size_t name_len = name.length (); + + if (name_len > 2 && name.substr (name_len-2) == ".m") + name = name.substr (0, name_len-2); + + if (name.empty ()) + return nullptr; + + symbol_table& symtab = m_interpreter.get_symbol_table (); + + octave_value fcn; + std::size_t p2 = std::string::npos; + + if (name[0] == '@') + { + std::size_t p1 = name.find (sys::file_ops::dir_sep_char (), 1); + + if (p1 == std::string::npos) + return nullptr; + + std::string dispatch_type = name.substr (1, p1-1); + + p2 = name.find ('>', p1); + + std::string method = name.substr (p1+1, p2-1); + + fcn = symtab.find_method (method, dispatch_type); + } + else if (! class_name.empty ()) + { + cdef_manager& cdm = m_interpreter.get_cdef_manager (); + + fcn = cdm.find_method (class_name, name); + + // If there is no classdef method, then try legacy classes. + if (fcn.is_undefined ()) + fcn = symtab.find_method (name, class_name); + } + else + { + p2 = name.find ('>'); + + std::string main_fcn = name.substr (0, p2); + + fcn = symtab.find_function (main_fcn); + } + + // List of function names sub1>sub2>... + std::string subfuns; + + if (p2 != std::string::npos) + subfuns = name.substr (p2+1); + + if (fcn.is_defined () && fcn.is_user_code ()) + user_code = fcn.user_code_value (); + + if (! user_code || subfuns.empty ()) + return user_code; + + fcn = user_code->find_subfunction (subfuns); + + if (fcn.is_undefined ()) + return nullptr; + + user_code = fcn.user_code_value (); + } + + return user_code; +} + +std::string +tree_evaluator::current_function_name (bool skip_first) const +{ + octave_function *curfcn = m_call_stack.current_function (skip_first); + + if (curfcn) + return curfcn->name (); + + return ""; +} + +bool +tree_evaluator::in_user_code () const +{ + return m_call_stack.current_user_code () != nullptr; +} + +void +tree_evaluator::visit_decl_command (tree_decl_command& cmd) +{ + if (m_echo_state) + { + int line = cmd.line (); + if (line < 0) + line = 1; + echo_code (line); + m_echo_file_pos = line + 1; + } + + if (m_debug_mode) + do_breakpoint (cmd.is_active_breakpoint (*this)); + + // FIXME: tree_decl_init_list is not derived from tree, so should it + // really have an accept method? + + tree_decl_init_list *init_list = cmd.initializer_list (); + + if (init_list) + init_list->accept (*this); +} + +void +tree_evaluator::visit_decl_elt (tree_decl_elt& elt) +{ + tree_identifier *id = elt.ident (); + + if (id) + { + if (elt.is_global ()) + m_call_stack.make_global (id->symbol ()); + else if (elt.is_persistent ()) + m_call_stack.make_persistent (id->symbol ()); + else + error ("declaration list element not global or persistent"); + + octave_lvalue ult = id->lvalue (*this); + + if (ult.is_undefined ()) + { + tree_expression *expr = elt.expression (); + + octave_value init_val; + + if (expr) + init_val = expr->evaluate (*this); + else + init_val = Matrix (); + + ult.assign (octave_value::op_asn_eq, init_val); + } + } +} + +template <typename T> +void +tree_evaluator::execute_range_loop (const range<T>& rng, int line, + octave_lvalue& ult, + tree_statement_list *loop_body) +{ + octave_idx_type steps = rng.numel (); + + if (math::isinf (rng.limit ())) + warning_with_id ("Octave:infinite-loop", + "FOR loop limit is infinite, will stop after %" + OCTAVE_IDX_TYPE_FORMAT " steps", steps); + + for (octave_idx_type i = 0; i < steps; i++) + { + if (m_echo_state) + m_echo_file_pos = line; + + octave_value val (rng.elem (i)); + + ult.assign (octave_value::op_asn_eq, val); + + if (loop_body) + loop_body->accept (*this); + + if (quit_loop_now ()) + break; + } +} + +void +tree_evaluator::visit_simple_for_command (tree_simple_for_command& cmd) +{ + int line = cmd.line (); + if (line < 0) + line = 1; + + if (m_echo_state) + { + echo_code (line); + line++; + } + + if (m_debug_mode) + do_breakpoint (cmd.is_active_breakpoint (*this)); + + // FIXME: need to handle PARFOR loops here using cmd.in_parallel () + // and cmd.maxproc_expr (); + + unwind_protect_var<bool> upv (m_in_loop_command, true); + + tree_expression *expr = cmd.control_expr (); + + octave_value rhs = expr->evaluate (*this); + + if (rhs.is_undefined ()) + return; + + tree_expression *lhs = cmd.left_hand_side (); + + octave_lvalue ult = lhs->lvalue (*this); + + tree_statement_list *loop_body = cmd.body (); + + if (rhs.is_range ()) + { + // FIXME: is there a better way to dispatch here? + + if (rhs.is_double_type ()) + { + execute_range_loop (rhs.range_value (), line, ult, loop_body); + return; + } + + // For now, disable all but range<double>. + +#if 0 + if (rhs.is_int64_type ()) + { + execute_range_loop (rhs.int64_range_value (), line, ult, loop_body); + return; + } + + if (rhs.is_uint64_type ()) + { + execute_range_loop (rhs.uint64_range_value (), line, ult, loop_body); + return; + } + + if (rhs.is_int32_type ()) + { + execute_range_loop (rhs.int32_range_value (), line, ult, loop_body); + return; + } + + if (rhs.is_uint32_type ()) + { + execute_range_loop (rhs.uint32_range_value (), line, ult, loop_body); + return; + } + + if (rhs.is_int16_type ()) + { + execute_range_loop (rhs.int16_range_value (), line, ult, loop_body); + return; + } + + if (rhs.is_uint16_type ()) + { + execute_range_loop (rhs.uint16_range_value (), line, ult, loop_body); + return; + } + + if (rhs.is_int8_type ()) + { + execute_range_loop (rhs.int8_range_value (), line, ult, loop_body); + return; + } + + if (rhs.is_uint8_type ()) + { + execute_range_loop (rhs.uint8_range_value (), line, ult, loop_body); + return; + } + + if (rhs.is_single_type ()) + { + execute_range_loop (rhs.float_range_value (), line, ult, loop_body); + return; + } +#endif + } + + if (rhs.is_scalar_type ()) + { + if (m_echo_state) + m_echo_file_pos = line; + + ult.assign (octave_value::op_asn_eq, rhs); + + if (loop_body) + loop_body->accept (*this); + + // Maybe decrement break and continue states. + quit_loop_now (); + + return; + } + + // Also handle any range types not explicitly handled above, though + // not as efficiently as the specialized code above. + + if (rhs.is_range () || rhs.is_matrix_type () || rhs.iscell () + || rhs.is_string () || rhs.isstruct ()) + { + // A matrix or cell is reshaped to 2 dimensions and iterated by + // columns. + + dim_vector dv = rhs.dims ().redim (2); + + octave_idx_type nrows = dv(0); + octave_idx_type steps = dv(1); + + octave_value arg = rhs; + if (rhs.ndims () > 2) + arg = arg.reshape (dv); + + if (nrows > 0 && steps > 0) + { + octave_value_list idx; + octave_idx_type iidx; + + // for row vectors, use single index to speed things up. + if (nrows == 1) + { + idx.resize (1); + iidx = 0; + } + else + { + idx.resize (2); + idx(0) = octave_value::magic_colon_t; + iidx = 1; + } + + for (octave_idx_type i = 1; i <= steps; i++) + { + if (m_echo_state) + m_echo_file_pos = line; + + // index_op expects one-based indices. + idx(iidx) = i; + octave_value val = arg.index_op (idx); + + ult.assign (octave_value::op_asn_eq, val); + + if (loop_body) + loop_body->accept (*this); + + if (quit_loop_now ()) + break; + } + } + else + { + // Handle empty cases, while still assigning to loop var. + ult.assign (octave_value::op_asn_eq, arg); + } + + return; + } + + error ("invalid type in for loop expression near line %d, column %d", + cmd.line (), cmd.column ()); +} + +void +tree_evaluator::visit_complex_for_command (tree_complex_for_command& cmd) +{ + int line = cmd.line (); + if (line < 0) + line = 1; + + if (m_echo_state) + { + echo_code (line); + line++; + } + + if (m_debug_mode) + do_breakpoint (cmd.is_active_breakpoint (*this)); + + unwind_protect_var<bool> upv (m_in_loop_command, true); + + tree_expression *expr = cmd.control_expr (); + + octave_value rhs = expr->evaluate (*this); + + if (rhs.is_undefined ()) + return; + + if (! rhs.isstruct ()) + error ("in statement 'for [X, Y] = VAL', VAL must be a structure"); + + // Cycle through structure elements. First element of id_list + // is set to value and the second is set to the name of the + // structure element. + + tree_argument_list *lhs = cmd.left_hand_side (); + + auto p = lhs->begin (); + + tree_expression *elt = *p++; + + octave_lvalue val_ref = elt->lvalue (*this); + + elt = *p; + + octave_lvalue key_ref = elt->lvalue (*this); + + const octave_map tmp_val = rhs.map_value (); + + tree_statement_list *loop_body = cmd.body (); + + string_vector keys = tmp_val.keys (); + + octave_idx_type nel = keys.numel (); + + for (octave_idx_type i = 0; i < nel; i++) + { + if (m_echo_state) + m_echo_file_pos = line; + + std::string key = keys[i]; + + const Cell val_lst = tmp_val.contents (key); + + octave_idx_type n = val_lst.numel (); + + octave_value val = (n == 1) ? val_lst(0) : octave_value (val_lst); + + val_ref.assign (octave_value::op_asn_eq, val); + key_ref.assign (octave_value::op_asn_eq, key); + + if (loop_body) + loop_body->accept (*this); + + if (quit_loop_now ()) + break; + } +} + +void tree_evaluator::visit_spmd_command (tree_spmd_command& cmd) +{ + // For now, we just execute the commands serially. + + tree_statement_list *body = cmd.body (); + + if (body) + body->accept (*this); +} + +octave_value +tree_evaluator::evaluate_anon_fcn_handle (tree_anon_fcn_handle& afh) +{ + // FIXME: should CMD_LIST be limited to a single expression? + // I think that is what Matlab does. + + symbol_scope new_scope; + symbol_scope scope = afh.scope (); + if (scope) + new_scope = scope.dup (); + + tree_parameter_list *param_list = afh.parameter_list (); + tree_parameter_list *param_list_dup + = param_list ? param_list->dup (new_scope) : nullptr; + + tree_parameter_list *ret_list = nullptr; + + tree_statement_list *stmt_list = nullptr; + + symbol_scope parent_scope = get_current_scope (); + + new_scope.set_parent (parent_scope); + new_scope.set_primary_parent (parent_scope); + + tree_expression *expr = afh.expression (); + if (expr) + { + tree_expression *expr_dup = expr->dup (new_scope); + tree_statement *stmt = new tree_statement (expr_dup, nullptr); + stmt_list = new tree_statement_list (stmt); + } + + tree_anon_scopes anon_fcn_ctx (afh); + + std::set<std::string> free_vars = anon_fcn_ctx.free_variables (); + + stack_frame::local_vars_map local_vars; + + std::shared_ptr<stack_frame> frame + = m_call_stack.get_current_stack_frame (); + + for (auto& name : free_vars) + { + octave_value val = frame->varval (name); + + if (val.is_defined ()) + local_vars[name] = val; + } + + octave_user_function *af + = new octave_user_function (new_scope, param_list_dup, ret_list, + stmt_list); + + octave_function *curr_fcn = m_call_stack.current_function (); + + bool is_nested = false; + + if (curr_fcn) + { + // FIXME: maybe it would be better to just stash curr_fcn + // instead of individual bits of info about it? + + // An anonymous function defined inside another nested function + // or parent of a nested function also behaves like a nested + // function. + + if (curr_fcn->is_parent_function () || curr_fcn->is_nested_function ()) + { + is_nested = true; + af->mark_as_nested_function (); + new_scope.set_nesting_depth (parent_scope.nesting_depth () + 1); + } + + af->stash_dir_name (curr_fcn->dir_name ()); + + new_scope.cache_fcn_file_name (curr_fcn->fcn_file_name ()); + new_scope.cache_dir_name (curr_fcn->dir_name ()); + + // The following is needed so that class method dispatch works + // properly for anonymous functions that wrap class methods. + + if (curr_fcn->is_class_method () || curr_fcn->is_class_constructor ()) + af->stash_dispatch_class (curr_fcn->dispatch_class ()); + + af->stash_fcn_file_name (curr_fcn->fcn_file_name ()); + } + + af->mark_as_anonymous_function (); + + octave_value ov_fcn (af); + + return (is_nested + ? octave_value (new octave_fcn_handle (ov_fcn, local_vars, frame)) + : octave_value (new octave_fcn_handle (ov_fcn, local_vars))); +} + +octave_value_list +tree_evaluator::execute_builtin_function (octave_builtin& builtin_function, + int nargout, + const octave_value_list& args) +{ + octave_value_list retval; + + if (args.has_magic_colon ()) + error ("invalid use of colon in function argument list"); + + profiler::enter<octave_builtin> block (m_profiler, builtin_function); + + octave_builtin::fcn fcn = builtin_function.function (); + + if (fcn) + retval = (*fcn) (args, nargout); + else + { + octave_builtin::meth meth = builtin_function.method (); + + retval = (*meth) (m_interpreter, args, nargout); + } + + // Do not allow null values to be returned from functions. + // FIXME: perhaps true builtins should be allowed? + + retval.make_storable_values (); + + // Fix the case of a single undefined value. + // This happens when a compiled function uses + // + // octave_value retval; + // + // instead of + // + // octave_value_list retval; + // + // the idiom is very common, so we solve that here. + + if (retval.length () == 1 && retval.xelem (0).is_undefined ()) + retval.clear (); + + return retval; +} + +octave_value_list +tree_evaluator::execute_mex_function (octave_mex_function& mex_function, + int nargout, + const octave_value_list& args) +{ + octave_value_list retval; + + if (args.has_magic_colon ()) + error ("invalid use of colon in function argument list"); + + profiler::enter<octave_mex_function> block (m_profiler, mex_function); + + retval = call_mex (mex_function, args, nargout); + + return retval; +} + +octave_value_list +tree_evaluator::execute_user_script (octave_user_script& user_script, + int nargout, + const octave_value_list& args) +{ + octave_value_list retval; + + std::string file_name = user_script.fcn_file_name (); + + if (args.length () != 0 || nargout != 0) + error ("invalid call to script %s", file_name.c_str ()); + + tree_statement_list *cmd_list = user_script.body (); + + if (! cmd_list) + return retval; + + // FIXME: Maybe this check belongs in the places where we push a new + // stack frame? Or in the call_stack push method itself? + + if (m_call_stack.size () >= static_cast<std::size_t> (m_max_recursion_depth)) + error ("max_recursion_depth exceeded"); + + unwind_protect_var<stmt_list_type> upv (m_statement_context, SC_SCRIPT); + + profiler::enter<octave_user_script> block (m_profiler, user_script); + + if (echo ()) + push_echo_state (tree_evaluator::ECHO_SCRIPTS, file_name); + + // FIXME: Should we be using tree_evaluator::eval here? + + cmd_list->accept (*this); + + if (m_returning) + m_returning = 0; + + if (m_breaking) + m_breaking--; + + return retval; +} + +void +tree_evaluator::visit_octave_user_script (octave_user_script&) +{ + // ?? + panic_impossible (); +} + +octave_value_list +tree_evaluator::execute_user_function (octave_user_function& user_function, + int nargout, + const octave_value_list& xargs) +{ + octave_value_list retval; + + // If this function is a classdef constructor, extract the first input + // argument, which must be the partially constructed object instance. + + octave_value_list args (xargs); + octave_value_list ret_args; + + int nargin = args.length (); + + if (user_function.is_classdef_constructor ()) + { + if (nargin > 0) + { + ret_args = args.slice (0, 1, true); + --nargin; + args = args.slice (1, nargin, true); + } + else + panic_impossible (); + } + + // FIXME: this probably shouldn't be a double-precision matrix. + Matrix ignored_outputs = ignored_fcn_outputs (); + + tree_parameter_list *param_list = user_function.parameter_list (); + + bool takes_varargs = false; + int max_inputs = 0; + + if (param_list) + { + takes_varargs = param_list->takes_varargs (); + max_inputs = param_list->length (); + } + + if (! takes_varargs && nargin > max_inputs) + { + std::string name = user_function.name (); + + if (name.empty ()) + name = "@<anonymous>"; + + error_with_id ("Octave:invalid-fun-call", + "%s: function called with too many inputs", + name.c_str ()); + } + + define_parameter_list_from_arg_vector (param_list, args); + + tree_parameter_list *ret_list = user_function.return_list (); + + if (ret_list && ! ret_list->takes_varargs ()) + { + int max_outputs = ret_list->length (); + + if (nargout > max_outputs) + { + std::string name = user_function.name (); + + error_with_id ("Octave:invalid-fun-call", + "%s: function called with too many outputs", + name.c_str ()); + } + } + + bind_auto_fcn_vars (xargs.name_tags (), ignored_outputs, nargin, + nargout, user_function.takes_varargs (), + user_function.all_va_args (args)); + + // For classdef constructor, pre-populate the output arguments + // with the pre-initialized object instance, extracted above. + + if (user_function.is_classdef_constructor ()) + { + if (! ret_list) + error ("%s: invalid classdef constructor, no output argument defined", + user_function.dispatch_class ().c_str ()); + + define_parameter_list_from_arg_vector (ret_list, ret_args); + } + + // FIXME: Maybe this check belongs in the places where we push a + // new stack frame? Or in the call_stack push method itself? + + if (m_call_stack.size () >= static_cast<std::size_t> (m_max_recursion_depth)) + error ("max_recursion_depth exceeded"); + + unwind_action act2 ([&user_function] () { + user_function.restore_warning_states (); + }); + + // Evaluate the commands that make up the function. + + unwind_protect_var<stmt_list_type> upv (m_statement_context, SC_FUNCTION); + + tree_statement_list *cmd_list = user_function.body (); + + if (cmd_list) + { + profiler::enter<octave_user_function> + block (m_profiler, user_function); + + if (echo ()) + push_echo_state (tree_evaluator::ECHO_FUNCTIONS, + user_function.fcn_file_name ()); + + if (user_function.is_special_expr ()) + { + panic_if (cmd_list->length () != 1); + + tree_statement *stmt = cmd_list->front (); + + tree_expression *expr = stmt->expression (); + + if (expr) + { + m_call_stack.set_location (stmt->line (), stmt->column ()); + + retval = expr->evaluate_n (*this, nargout); + } + } + else + cmd_list->accept (*this); + + if (m_returning) + m_returning = 0; + + if (m_breaking) + m_breaking--; + } + + // Copy return values out. + + if (ret_list && ! user_function.is_special_expr ()) + { + Cell varargout; + + if (ret_list->takes_varargs ()) + { + octave_value varargout_varval = varval ("varargout"); + + if (varargout_varval.is_defined ()) + varargout = varargout_varval.xcell_value ("varargout must be a cell array object"); + } + + retval = convert_return_list_to_const_vector (ret_list, nargout, + ignored_outputs, + varargout); + } + + return retval; +} + +void +tree_evaluator::visit_octave_user_function (octave_user_function&) +{ + // ?? + panic_impossible (); +} + +void +tree_evaluator::visit_octave_user_function_header (octave_user_function&) +{ + panic_impossible (); +} + +void +tree_evaluator::visit_octave_user_function_trailer (octave_user_function&) +{ + panic_impossible (); +} + +void +tree_evaluator::visit_function_def (tree_function_def& cmd) +{ + octave_value fcn = cmd.function (); + + octave_function *f = fcn.function_value (); + + if (f) + { + std::string nm = f->name (); + + symbol_table& symtab = m_interpreter.get_symbol_table (); + + symtab.install_cmdline_function (nm, fcn); + + // Make sure that any variable with the same name as the new + // function is cleared. + + assign (nm); + } +} + +void +tree_evaluator::visit_identifier (tree_identifier&) +{ + panic_impossible (); +} + +void +tree_evaluator::visit_if_clause (tree_if_clause&) +{ + panic_impossible (); +} + +void +tree_evaluator::visit_if_command (tree_if_command& cmd) +{ + if (m_echo_state) + { + int line = cmd.line (); + if (line < 0) + line = 1; + echo_code (line); + m_echo_file_pos = line + 1; + } + + // FIXME: tree_if_command_list is not derived from tree, so should it + // really have an accept method? + + tree_if_command_list *lst = cmd.cmd_list (); + + if (lst) + lst->accept (*this); +} + +void +tree_evaluator::visit_if_command_list (tree_if_command_list& lst) +{ + for (tree_if_clause *tic : lst) + { + tree_expression *expr = tic->condition (); + + if (! (in_debug_repl () + && m_call_stack.current_frame () == m_debug_frame)) + m_call_stack.set_location (tic->line (), tic->column ()); + + if (m_debug_mode && ! tic->is_else_clause ()) + do_breakpoint (tic->is_active_breakpoint (*this)); + + if (tic->is_else_clause () || is_logically_true (expr, "if")) + { + tree_statement_list *stmt_lst = tic->commands (); + + if (stmt_lst) + stmt_lst->accept (*this); + + break; + } + } +} + +void +tree_evaluator::visit_index_expression (tree_index_expression&) +{ + panic_impossible (); +} + +void +tree_evaluator::visit_matrix (tree_matrix&) +{ + panic_impossible (); +} + +void +tree_evaluator::visit_cell (tree_cell&) +{ + panic_impossible (); +} + +void +tree_evaluator::visit_multi_assignment (tree_multi_assignment&) +{ + panic_impossible (); +} + +void +tree_evaluator::visit_no_op_command (tree_no_op_command& cmd) +{ + if (m_echo_state) + { + int line = cmd.line (); + if (line < 0) + line = 1; + echo_code (line); + m_echo_file_pos = line + 1; + } + + if (m_debug_mode && cmd.is_end_of_fcn_or_script ()) + do_breakpoint (cmd.is_active_breakpoint (*this), true); +} + +void +tree_evaluator::visit_constant (tree_constant&) +{ + panic_impossible (); +} + +void +tree_evaluator::visit_fcn_handle (tree_fcn_handle&) +{ + panic_impossible (); +} + +void +tree_evaluator::visit_parameter_list (tree_parameter_list&) +{ + panic_impossible (); +} + +void +tree_evaluator::visit_postfix_expression (tree_postfix_expression&) +{ + panic_impossible (); +} + +void +tree_evaluator::visit_prefix_expression (tree_prefix_expression&) +{ + panic_impossible (); +} + +void +tree_evaluator::visit_return_command (tree_return_command& cmd) +{ + if (m_echo_state) + { + int line = cmd.line (); + if (line < 0) + line = 1; + echo_code (line); + m_echo_file_pos = line + 1; + } + + if (m_debug_mode) + do_breakpoint (cmd.is_active_breakpoint (*this)); + + // Act like dbcont. + + if (in_debug_repl () && m_call_stack.current_frame () == m_debug_frame) + dbcont (); + else if (m_statement_context == SC_FUNCTION + || m_statement_context == SC_SCRIPT + || m_in_loop_command) + m_returning = 1; +} + +void +tree_evaluator::visit_simple_assignment (tree_simple_assignment&) +{ + panic_impossible (); +} + +void +tree_evaluator::visit_statement (tree_statement& stmt) +{ + tree_command *cmd = stmt.command (); + tree_expression *expr = stmt.expression (); + + if (cmd || expr) + { + if (! (in_debug_repl () + && m_call_stack.current_frame () == m_debug_frame)) + m_call_stack.set_location (stmt.line (), stmt.column ()); + + try + { + if (cmd) + { + unwind_protect_var<const std::list<octave_lvalue> *> + upv (m_lvalue_list, nullptr); + + cmd->accept (*this); + } + else + { + if (m_echo_state) + { + int line = stmt.line (); + if (line < 0) + line = 1; + echo_code (line); + m_echo_file_pos = line + 1; + } + + if (m_debug_mode) + do_breakpoint (expr->is_active_breakpoint (*this)); + + // FIXME: maybe all of this should be packaged in + // one virtual function that returns a flag saying whether + // or not the expression will take care of binding ans and + // printing the result. + + // FIXME: it seems that we should just have to + // evaluate the expression and that should take care of + // everything, binding ans as necessary? + + octave_value tmp_result = expr->evaluate (*this, 0); + + if (tmp_result.is_defined ()) + { + bool do_bind_ans = false; + + if (expr->is_identifier ()) + do_bind_ans = ! is_variable (expr); + else + do_bind_ans = ! expr->is_assignment_expression (); + + if (do_bind_ans) + bind_ans (tmp_result, expr->print_result () + && statement_printing_enabled ()); + } + } + } + catch (const std::bad_alloc&) + { + // FIXME: We want to use error_with_id here so that give users + // control over this error message but error_with_id will + // require some memory allocations. Is there anything we can + // do to make those more likely to succeed? + + error_with_id ("Octave:bad-alloc", + "out of memory or dimension too large for Octave's index type"); + } + catch (const interrupt_exception&) + { + // If we are debugging, then continue with next statement. + // Otherwise, jump out of here. + + if (m_debug_mode) + m_interpreter.recover_from_exception (); + else + throw; + } + catch (const execution_exception& ee) + { + error_system& es = m_interpreter.get_error_system (); + + if ((m_interpreter.interactive () + || application::forced_interactive ()) + && ((es.debug_on_error () + && m_bp_table.debug_on_err (es.last_error_id ())) + || (es.debug_on_caught () + && m_bp_table.debug_on_caught (es.last_error_id ()))) + && in_user_code ()) + { + es.save_exception (ee); + es.display_exception (ee); + + enter_debugger (); + + // It doesn't make sense to continue execution after an + // error occurs so force the debugger to quit all debug + // levels and return the the top prompt. + + throw quit_debug_exception (true); + } + else + throw; + } + } +} + +void +tree_evaluator::visit_statement_list (tree_statement_list& lst) +{ + // FIXME: commented out along with else clause below. + // static octave_value_list empty_list; + + auto p = lst.begin (); + + if (p != lst.end ()) + { + while (true) + { + tree_statement *elt = *p++; + + if (! elt) + error ("invalid statement found in statement list!"); + + octave_quit (); + + elt->accept (*this); + + if (m_breaking || m_continuing) + break; + + if (m_returning) + break; + + if (p == lst.end ()) + break; + else + { + // Clear previous values before next statement is + // evaluated so that we aren't holding an extra + // reference to a value that may be used next. For + // example, in code like this: + // + // X = rand (N); # refcount for X should be 1 + // # after this statement + // + // X(idx) = val; # no extra copy of X should be + // # needed, but we will be faked + // # out if retval is not cleared + // # between statements here + + // result_values = empty_list; + } + } + } +} + +void +tree_evaluator::visit_switch_case (tree_switch_case&) +{ + panic_impossible (); +} + +void +tree_evaluator::visit_switch_case_list (tree_switch_case_list&) +{ + panic_impossible (); +} + +void +tree_evaluator::visit_switch_command (tree_switch_command& cmd) +{ + if (m_echo_state) + { + int line = cmd.line (); + if (line < 0) + line = 1; + echo_code (line); + m_echo_file_pos = line + 1; + } + + if (m_debug_mode) + do_breakpoint (cmd.is_active_breakpoint (*this)); + + tree_expression *expr = cmd.switch_value (); + + if (! expr) + error ("missing value in switch command near line %d, column %d", + cmd.line (), cmd.column ()); + + octave_value val = expr->evaluate (*this); + + tree_switch_case_list *lst = cmd.case_list (); + + if (lst) + { + for (tree_switch_case *t : *lst) + { + if (t->is_default_case () || switch_case_label_matches (t, val)) + { + tree_statement_list *stmt_lst = t->commands (); + + if (stmt_lst) + stmt_lst->accept (*this); + + break; + } + } + } +} + +void +tree_evaluator::visit_try_catch_command (tree_try_catch_command& cmd) +{ + if (m_echo_state) + { + int line = cmd.line (); + if (line < 0) + line = 1; + echo_code (line); + m_echo_file_pos = line + 1; + } + + bool execution_error = false; + octave_scalar_map err_map; + + tree_statement_list *try_code = cmd.body (); + + if (try_code) + { + // unwind frame before catch block + + unwind_protect frame; + + interpreter_try (frame); + + // The catch code is *not* added to unwind_protect stack; it + // doesn't need to be run on interrupts. + + try + { + try_code->accept (*this); + } + catch (const execution_exception& ee) + { + execution_error = true; + + error_system& es = m_interpreter.get_error_system (); + + es.save_exception (ee); + + err_map.assign ("message", es.last_error_message ()); + err_map.assign ("identifier", es.last_error_id ()); + err_map.assign ("stack", es.last_error_stack ()); + + m_interpreter.recover_from_exception (); + } + + // Actions attached to unwind_protect frame will run here, prior + // to executing the catch block. + } + + if (execution_error) + { + tree_statement_list *catch_code = cmd.cleanup (); + + if (catch_code) + { + tree_identifier *expr_id = cmd.identifier (); + + if (expr_id) + { + octave_lvalue ult = expr_id->lvalue (*this); + + ult.assign (octave_value::op_asn_eq, err_map); + } + + // perform actual "catch" block + catch_code->accept (*this); + } + } +} + +void +tree_evaluator::do_unwind_protect_cleanup_code (tree_statement_list *list) +{ + unwind_protect frame; + + frame.protect_var (octave_interrupt_state); + octave_interrupt_state = 0; + + // We want to preserve the last location info for possible + // backtracking. + + frame.add (&call_stack::set_line, &m_call_stack, + m_call_stack.current_line ()); + + frame.add (&call_stack::set_column, &m_call_stack, + m_call_stack.current_column ()); + + // Similarly, if we have seen a return or break statement, allow all + // the cleanup code to run before returning or handling the break. + // We don't have to worry about continue statements because they can + // only occur in loops. + + frame.protect_var (m_returning); + m_returning = 0; + + frame.protect_var (m_breaking); + m_breaking = 0; + + try + { + if (list) + list->accept (*this); + } + catch (const execution_exception& ee) + { + error_system& es = m_interpreter.get_error_system (); + + es.save_exception (ee); + m_interpreter.recover_from_exception (); + + if (m_breaking || m_returning) + frame.discard (2); + else + frame.run (2); + + frame.discard (2); + + throw; + } + + // The unwind_protects are popped off the stack in the reverse of + // the order they are pushed on. + + // FIXME: these statements say that if we see a break or + // return statement in the cleanup block, that we want to use the + // new value of the breaking or returning flag instead of restoring + // the previous value. Is that the right thing to do? I think so. + // Consider the case of + // + // function foo () + // unwind_protect + // fprintf (stderr, "1: this should always be executed\n"); + // break; + // fprintf (stderr, "1: this should never be executed\n"); + // unwind_protect_cleanup + // fprintf (stderr, "2: this should always be executed\n"); + // return; + // fprintf (stderr, "2: this should never be executed\n"); + // end_unwind_protect + // endfunction + // + // If we reset the value of the breaking flag, both the returning + // flag and the breaking flag will be set, and we shouldn't have + // both. So, use the most recent one. If there is no return or + // break in the cleanup block, the values should be reset to + // whatever they were when the cleanup block was entered. + + if (m_breaking || m_returning) + frame.discard (2); + else + frame.run (2); +} + +void +tree_evaluator::visit_unwind_protect_command (tree_unwind_protect_command& cmd) +{ + if (m_echo_state) + { + int line = cmd.line (); + if (line < 0) + line = 1; + echo_code (line); + m_echo_file_pos = line + 1; + } + + tree_statement_list *cleanup_code = cmd.cleanup (); + + tree_statement_list *unwind_protect_code = cmd.body (); + + if (unwind_protect_code) + { + try + { + unwind_protect_code->accept (*this); + } + catch (const execution_exception& ee) + { + error_system& es = m_interpreter.get_error_system (); + + // FIXME: Maybe we should be able to temporarily set the + // interpreter's exception handling state to something "safe" + // while the cleanup block runs instead of just resetting it + // here? + es.save_exception (ee); + m_interpreter.recover_from_exception (); + + // Run the cleanup code on exceptions, so that it is run even + // in case of interrupt or out-of-memory. + do_unwind_protect_cleanup_code (cleanup_code); + + // If an error occurs inside the cleanup code, a new + // exception will be thrown instead of the original. + throw; + } + catch (const interrupt_exception&) + { + // The comments above apply here as well. + m_interpreter.recover_from_exception (); + do_unwind_protect_cleanup_code (cleanup_code); + throw; + } + + // Also execute the unwind_protect_cleanump code if the + // unwind_protect block runs without error. + do_unwind_protect_cleanup_code (cleanup_code); + } +} + +void +tree_evaluator::visit_while_command (tree_while_command& cmd) +{ + int line = cmd.line (); + if (line < 0) + line = 1; + + if (m_echo_state) + { + echo_code (line); + line++; + } + + unwind_protect_var<bool> upv (m_in_loop_command, true); + + tree_expression *expr = cmd.condition (); + + if (! expr) + panic_impossible (); + + for (;;) + { + if (m_echo_state) + m_echo_file_pos = line; + + if (m_debug_mode) + do_breakpoint (cmd.is_active_breakpoint (*this)); + + if (is_logically_true (expr, "while")) + { + tree_statement_list *loop_body = cmd.body (); + + if (loop_body) + loop_body->accept (*this); + + if (quit_loop_now ()) + break; + } + else + break; + } +} + +void +tree_evaluator::visit_do_until_command (tree_do_until_command& cmd) +{ + int line = cmd.line (); + if (line < 0) + line = 1; + + if (m_echo_state) + { + echo_code (line); + line++; + } + + unwind_protect_var<bool> upv (m_in_loop_command, true); + + tree_expression *expr = cmd.condition (); + + if (! expr) + panic_impossible (); + + for (;;) + { + if (m_echo_state) + m_echo_file_pos = line; + + tree_statement_list *loop_body = cmd.body (); + + if (loop_body) + loop_body->accept (*this); + + if (quit_loop_now ()) + break; + + if (m_debug_mode) + do_breakpoint (cmd.is_active_breakpoint (*this)); + + if (is_logically_true (expr, "do-until")) + break; + } +} + +void +tree_evaluator::visit_superclass_ref (tree_superclass_ref&) +{ + panic_impossible (); +} + +void +tree_evaluator::visit_metaclass_query (tree_metaclass_query&) +{ + panic_impossible (); +} + +void tree_evaluator::bind_ans (const octave_value& val, bool print) +{ + static std::string ans = "ans"; + + if (val.is_defined ()) + { + if (val.is_cs_list ()) + { + octave_value_list lst = val.list_value (); + + for (octave_idx_type i = 0; i < lst.length (); i++) + bind_ans (lst(i), print); + } + else + { + // FIXME: Maybe assign could also return the assigned value, + // just for convenience? + + assign (ans, val); + + if (print) + { + // Use varval instead of displaying VAL directly so that + // we get the right type and value for things like + // magic_int values that may mutate when stored. + + octave_value_list args = ovl (varval (ans)); + args.stash_name_tags (string_vector (ans)); + m_interpreter.feval ("display", args); + } + } + } +} + +void +tree_evaluator::do_breakpoint (tree_statement& stmt) +{ + do_breakpoint (stmt.is_active_breakpoint (*this), + stmt.is_end_of_fcn_or_script ()); +} + +void +tree_evaluator::do_breakpoint (bool is_breakpoint, + bool is_end_of_fcn_or_script) +{ + bool break_on_this_statement = false; + + if (is_breakpoint) + break_on_this_statement = true; + else if (m_dbstep_flag > 0) + { + if (m_call_stack.current_frame () == m_debug_frame) + { + if (m_dbstep_flag == 1 || is_end_of_fcn_or_script) + { + // We get here if we are doing a "dbstep" or a "dbstep N" and + // the count has reached 1 so that we must stop and return to + // debug prompt. Alternatively, "dbstep N" has been used but + // the end of the frame has been reached so we stop at the last + // line and return to prompt. + + break_on_this_statement = true; + } + else + { + // Executing "dbstep N". Decrease N by one and continue. + + m_dbstep_flag--; + } + + } + else if (m_dbstep_flag == 1 + && m_call_stack.current_frame () < m_debug_frame) + { + // We stepped out from the end of a function. + + m_debug_frame = m_call_stack.current_frame (); + + break_on_this_statement = true; + } + } + else if (m_dbstep_flag == -1) + { + // We get here if we are doing a "dbstep in". + + break_on_this_statement = true; + + m_debug_frame = m_call_stack.current_frame (); + } + else if (m_dbstep_flag == -2) + { + // We get here if we are doing a "dbstep out". Check for end of + // function and whether the current frame is the same as the + // cached value because we want to step out from the frame where + // "dbstep out" was evaluated, not from any functions called from + // that frame. + + if (is_end_of_fcn_or_script + && m_call_stack.current_frame () == m_debug_frame) + m_dbstep_flag = -1; + } + + if (! break_on_this_statement) + break_on_this_statement = m_break_on_next_stmt; + + m_break_on_next_stmt = false; + + if (break_on_this_statement) + { + m_dbstep_flag = 0; + + enter_debugger (); + } +} + +bool +tree_evaluator::is_logically_true (tree_expression *expr, + const char *warn_for) +{ + bool expr_value = false; + + m_call_stack.set_location (expr->line (), expr->column ()); + + octave_value t1 = expr->evaluate (*this); + + if (t1.is_defined ()) + return t1.is_true (); + else + error ("%s: undefined value used in conditional expression", warn_for); + + return expr_value; +} + +octave_value +tree_evaluator::max_recursion_depth (const octave_value_list& args, + int nargout) +{ + return set_internal_variable (m_max_recursion_depth, args, nargout, + "max_recursion_depth", 0); +} + +symbol_info_list +tree_evaluator::glob_symbol_info (const std::string& pattern) const +{ + return m_call_stack.glob_symbol_info (pattern); +} + +symbol_info_list +tree_evaluator::regexp_symbol_info (const std::string& pattern) const +{ + return m_call_stack.regexp_symbol_info (pattern); +} + +symbol_info_list +tree_evaluator::get_symbol_info () +{ + return m_call_stack.get_symbol_info (); +} + +symbol_info_list +tree_evaluator::top_scope_symbol_info () const +{ + return m_call_stack.top_scope_symbol_info (); +} + +octave_map tree_evaluator::get_autoload_map () const +{ + Cell fcn_names (dim_vector (m_autoload_map.size (), 1)); + Cell file_names (dim_vector (m_autoload_map.size (), 1)); + + octave_idx_type i = 0; + for (const auto& fcn_fname : m_autoload_map) + { + fcn_names(i) = fcn_fname.first; + file_names(i) = fcn_fname.second; + + i++; + } + + octave_map m; + + m.assign ("function", fcn_names); + m.assign ("file", file_names); + + return m; +} + +std::string tree_evaluator::lookup_autoload (const std::string& nm) const +{ + std::string retval; + + auto p = m_autoload_map.find (nm); + + if (p != m_autoload_map.end ()) + { + load_path& lp = m_interpreter.get_load_path (); + + retval = lp.find_file (p->second); + } + + return retval; +} + +std::list<std::string> tree_evaluator::autoloaded_functions () const +{ + std::list<std::string> names; + + for (const auto& fcn_fname : m_autoload_map) + names.push_back (fcn_fname.first); + + return names; +} + +std::list<std::string> +tree_evaluator::reverse_lookup_autoload (const std::string& nm) const +{ + std::list<std::string> names; + + for (const auto& fcn_fname : m_autoload_map) + if (nm == fcn_fname.second) + names.push_back (fcn_fname.first); + + return names; +} + +void tree_evaluator::add_autoload (const std::string& fcn, + const std::string& nm) +{ + std::string file_name = check_autoload_file (nm); + + m_autoload_map[fcn] = file_name; +} + +void tree_evaluator::remove_autoload (const std::string& fcn, + const std::string& nm) +{ + check_autoload_file (nm); + + // Remove function from symbol table and autoload map. + symbol_table& symtab = m_interpreter.get_symbol_table (); + + symtab.clear_dld_function (fcn); + + m_autoload_map.erase (fcn); +} + +octave_value +tree_evaluator::whos_line_format (const octave_value_list& args, int nargout) +{ + return set_internal_variable (m_whos_line_format, args, nargout, + "whos_line_format"); +} + +octave_value +tree_evaluator::silent_functions (const octave_value_list& args, int nargout) +{ + return set_internal_variable (m_silent_functions, args, nargout, + "silent_functions"); +} + +octave_value +tree_evaluator::string_fill_char (const octave_value_list& args, int nargout) +{ + return set_internal_variable (m_string_fill_char, args, nargout, + "string_fill_char"); +} + +// Final step of processing an indexing error. Add the name of the +// variable being indexed, if any, then issue an error. (Will this also +// be needed by pt-lvalue, which calls subsref?) + +void tree_evaluator::final_index_error (index_exception& ie, + const tree_expression *expr) +{ + std::string extra_message; + + if (is_variable (expr)) + { + std::string var = expr->name (); + + ie.set_var (var); + + symbol_table& symtab = m_interpreter.get_symbol_table (); + + octave_value fcn = symtab.find_function (var); + + if (fcn.is_function ()) + { + octave_function *fp = fcn.function_value (); + + if (fp && fp->name () == var) + extra_message + = " (note: variable '" + var + "' shadows function)"; + } + } + + std::string msg = ie.message () + extra_message; + + error_with_id (ie.err_id (), "%s", msg.c_str ()); +} + +octave_value +tree_evaluator::do_who (int argc, const string_vector& argv, + bool return_list, bool verbose) +{ + return m_call_stack.do_who (argc, argv, return_list, verbose); +} + +octave_value_list +tree_evaluator::make_value_list (tree_argument_list *args, + const string_vector& arg_nm) +{ + octave_value_list retval; + + if (args) + { + unwind_protect_var<const std::list<octave_lvalue> *> + upv (m_lvalue_list, nullptr); + + int len = args->length (); + + unwind_protect_var<int> upv2 (m_index_position); + unwind_protect_var<int> upv3 (m_num_indices); + + m_num_indices = len; + + std::list<octave_value> arg_vals; + + int k = 0; + + for (auto elt : *args) + { + // FIXME: is it possible for elt to be invalid? + + if (! elt) + break; + + m_index_position = k++; + + octave_value tmp = elt->evaluate (*this); + + if (tmp.is_cs_list ()) + { + octave_value_list tmp_ovl = tmp.list_value (); + + for (octave_idx_type i = 0; i < tmp_ovl.length (); i++) + arg_vals.push_back (tmp_ovl(i)); + } + else if (tmp.is_defined ()) + arg_vals.push_back (tmp); + } + + retval = octave_value_list (arg_vals); + } + + octave_idx_type n = retval.length (); + + if (n > 0) + retval.stash_name_tags (arg_nm); + + return retval; +} + +std::list<octave_lvalue> +tree_evaluator::make_lvalue_list (tree_argument_list *lhs) +{ + std::list<octave_lvalue> retval; + + for (tree_expression *elt : *lhs) + retval.push_back (elt->lvalue (*this)); + + return retval; +} + +void +tree_evaluator::push_echo_state (int type, const std::string& file_name, + int pos) +{ + unwind_protect *frame = m_call_stack.curr_fcn_unwind_protect_frame (); + + if (frame) + { + push_echo_state_cleanup (*frame); + + set_echo_state (type, file_name, pos); + } +} + +void +tree_evaluator::set_echo_state (int type, const std::string& file_name, + int pos) +{ + m_echo_state = echo_this_file (file_name, type); + m_echo_file_name = file_name; + m_echo_file_pos = pos; +} + +void +tree_evaluator::uwp_set_echo_state (bool state, const std::string& file_name, + int pos) +{ + m_echo_state = state; + m_echo_file_name = file_name; + m_echo_file_pos = pos; +} + +void +tree_evaluator::maybe_set_echo_state () +{ + octave_function *caller = caller_function (); + + if (caller && caller->is_user_code ()) + { + octave_user_code *fcn = dynamic_cast<octave_user_code *> (caller); + + int type = fcn->is_user_function () ? ECHO_FUNCTIONS : ECHO_SCRIPTS; + + std::string file_name = fcn->fcn_file_name (); + + // We want the line where "echo" was called, not the line number + // stored in the stack frame that was created for the echo + // function (that will always be -1). + + int pos = m_call_stack.current_user_code_line (); + + if (pos < 0) + pos = 1; + + set_echo_state (type, file_name, pos); + } +} + +void +tree_evaluator::push_echo_state_cleanup (unwind_protect& frame) +{ + frame.add (&tree_evaluator::uwp_set_echo_state, this, + m_echo_state, m_echo_file_name, m_echo_file_pos); +} + +bool tree_evaluator::maybe_push_echo_state_cleanup () +{ + // This function is expected to be called from ECHO, which would be + // the top of the call stack. If the caller of ECHO is a + // user-defined function or script, then set up unwind-protect + // elements to restore echo state. + + unwind_protect *frame = m_call_stack.curr_fcn_unwind_protect_frame (); + + if (frame) + { + push_echo_state_cleanup (*frame); + return true; + } + + return false; +} + + +octave_value +tree_evaluator::echo (const octave_value_list& args, int) +{ + bool cleanup_pushed = maybe_push_echo_state_cleanup (); + + string_vector argv = args.make_argv (); + + switch (args.length ()) + { + case 0: + if ((m_echo & ECHO_SCRIPTS) || (m_echo & ECHO_FUNCTIONS)) + { + m_echo = ECHO_OFF; + m_echo_files.clear (); + } + else + m_echo = ECHO_SCRIPTS; + break; + + case 1: { - // FIXME: is it possible for elt to be invalid? - - if (! elt) - break; - - octave_value tmp = elt->evaluate (*this); - - if (tmp.is_cs_list ()) + std::string arg0 = argv[0]; + + if (arg0 == "on") + m_echo = ECHO_SCRIPTS; + else if (arg0 == "off") + m_echo = ECHO_OFF; + else { - octave_value_list tmp_ovl = tmp.list_value (); - - for (octave_idx_type i = 0; i < tmp_ovl.length (); i++) - arg_vals.push_back (tmp_ovl(i)); - } - else if (tmp.is_defined ()) - arg_vals.push_back (tmp); - } - - return octave_value_list (arg_vals); - } - - octave_value_list - tree_evaluator::convert_return_list_to_const_vector - (tree_parameter_list *ret_list, int nargout, const Matrix& ignored_outputs, - const Cell& varargout) - { - octave_idx_type vlen = varargout.numel (); - int len = ret_list->length (); - - // Special case. Will do a shallow copy. - if (len == 0) - return varargout; - else - { - int i = 0; - int k = 0; - int num_ignored = ignored_outputs.numel (); - int ignored = num_ignored > 0 ? ignored_outputs(k) - 1 : -1; - - if (nargout <= len) - { - int nout = nargout > 0 ? nargout : 1; - octave_value_list retval (nout); - - for (tree_decl_elt *elt : *ret_list) + std::string file = fcn_file_in_path (arg0); + file = sys::env::make_absolute (file); + + if (file.empty ()) + error ("echo: no such file %s", arg0.c_str ()); + + if (m_echo & ECHO_ALL) { - if (nargout == 0 && ! is_defined (elt->ident ())) - break; - - if (ignored >= 0 && i == ignored) + // Echo is enabled for all functions, so turn it off + // for this one. + + m_echo_files[file] = false; + } + else + { + // Echo may be enabled for specific functions. + + auto p = m_echo_files.find (file); + + if (p == m_echo_files.end ()) { - i++; - k++; - ignored = k < num_ignored ? ignored_outputs(k) - 1 : -1; + // Not this one, so enable it. + + m_echo |= ECHO_FUNCTIONS; + m_echo_files[file] = true; } else - retval(i++) = evaluate (elt); - - if (i == nout) - break; - } - - return retval; - } - else - { - octave_value_list retval (len + vlen); - - for (tree_decl_elt *elt : *ret_list) - { - if (ignored >= 0 && i == ignored) { - i++; - k++; - ignored = k < num_ignored ? ignored_outputs(k) - 1 : -1; - } - else - retval(i++) = evaluate (elt); - } - - for (octave_idx_type j = 0; j < vlen; j++) - retval(i++) = varargout(j); - - return retval; - } - } - } - - bool - tree_evaluator::eval_decl_elt (tree_decl_elt *elt) - { - bool retval = false; - - tree_identifier *id = elt->ident (); - tree_expression *expr = elt->expression (); - - if (id && expr) - { - octave_lvalue ult = id->lvalue (*this); - - octave_value init_val = expr->evaluate (*this); - - ult.assign (octave_value::op_asn_eq, init_val); - - retval = true; - } - - return retval; - } - - bool - tree_evaluator::switch_case_label_matches (tree_switch_case *expr, - const octave_value& val) - { - tree_expression *label = expr->case_label (); - - octave_value label_value = label->evaluate (*this); - - if (label_value.is_defined ()) - { - if (label_value.iscell ()) - { - Cell cell (label_value.cell_value ()); - - for (octave_idx_type i = 0; i < cell.rows (); i++) - { - for (octave_idx_type j = 0; j < cell.columns (); j++) - { - bool match = val.is_equal (cell(i,j)); - - if (match) - return true; + // This one is already in the list. Flip the + // status for it. + + p->second = ! p->second; } } } - else - return val.is_equal (label_value); } - - return false; - } - - void tree_evaluator::push_stack_frame (const symbol_scope& scope) - { - m_call_stack.push (scope); - } - - void tree_evaluator::push_stack_frame (octave_user_function *fcn, - const std::shared_ptr<stack_frame>& closure_frames) - { - m_call_stack.push (fcn, closure_frames); - } - - void tree_evaluator::push_stack_frame (octave_user_function *fcn, - const stack_frame::local_vars_map& local_vars, - const std::shared_ptr<stack_frame>& closure_frames) - { - m_call_stack.push (fcn, local_vars, closure_frames); - } - - void tree_evaluator::push_stack_frame (octave_user_script *script) - { - m_call_stack.push (script); - } - - void tree_evaluator::push_stack_frame (octave_function *fcn) - { - m_call_stack.push (fcn); - } - - void tree_evaluator::pop_stack_frame () - { - m_call_stack.pop (); - } - - int tree_evaluator::current_line () const - { - return m_call_stack.current_line (); - } - - int tree_evaluator::current_column () const - { - return m_call_stack.current_column (); - } - - int tree_evaluator::debug_user_code_line () const - { - return m_call_stack.debug_user_code_line (); - } - - int tree_evaluator::debug_user_code_column () const - { - return m_call_stack.debug_user_code_column (); - } - - void tree_evaluator::debug_where (std::ostream& os) const - { - std::shared_ptr<stack_frame> frm = m_call_stack.current_user_frame (); - - frm->display_stopped_in_message (os); - } - - octave_user_code * tree_evaluator::current_user_code () const - { - return m_call_stack.current_user_code (); - } - - unwind_protect * tree_evaluator::curr_fcn_unwind_protect_frame () - { - return m_call_stack.curr_fcn_unwind_protect_frame (); - } - - octave_user_code * tree_evaluator::debug_user_code () const - { - return m_call_stack.debug_user_code (); - } - - octave_function * tree_evaluator::current_function (bool skip_first) const - { - return m_call_stack.current_function (skip_first); - } - - octave_function * tree_evaluator::caller_function () const - { - return m_call_stack.current_function (true); - } - - bool tree_evaluator::goto_frame (std::size_t n, bool verbose) - { - return m_call_stack.goto_frame (n, verbose); - } - - void tree_evaluator::goto_caller_frame () - { - m_call_stack.goto_caller_frame (); - } - - void tree_evaluator::goto_base_frame () - { - m_call_stack.goto_base_frame (); - } - - void tree_evaluator::restore_frame (std::size_t n) - { - return m_call_stack.restore_frame (n); - } - - std::string tree_evaluator::get_dispatch_class () const - { - return m_call_stack.get_dispatch_class (); - } - - void tree_evaluator::set_dispatch_class (const std::string& class_name) - { - m_call_stack.set_dispatch_class (class_name); - } - - bool - tree_evaluator::is_class_method_executing (std::string& dclass) const - { - return m_call_stack.is_class_method_executing (dclass); - } - - bool - tree_evaluator::is_class_constructor_executing (std::string& dclass) const - { - return m_call_stack.is_class_constructor_executing (dclass); - } - - std::list<std::shared_ptr<stack_frame>> - tree_evaluator::backtrace_frames (octave_idx_type& curr_user_frame) const - { - return m_call_stack.backtrace_frames (curr_user_frame); - } - - std::list<std::shared_ptr<stack_frame>> - tree_evaluator::backtrace_frames () const - { - return m_call_stack.backtrace_frames (); - } - - std::list<frame_info> - tree_evaluator::backtrace_info (octave_idx_type& curr_user_frame, - bool print_subfn) const - { - return m_call_stack.backtrace_info (curr_user_frame, print_subfn); - } - - std::list<frame_info> tree_evaluator::backtrace_info () const - { - return m_call_stack.backtrace_info (); - } - - octave_map - tree_evaluator::backtrace (octave_idx_type& curr_user_frame, - bool print_subfn) const - { - return m_call_stack.backtrace (curr_user_frame, print_subfn); - } - - octave_map tree_evaluator::backtrace () const - { - return m_call_stack.backtrace (); - } - - octave_map tree_evaluator::empty_backtrace () const - { - return m_call_stack.empty_backtrace (); - } - - std::string tree_evaluator::backtrace_message () const - { - std::list<frame_info> frames = backtrace_info (); - - std::ostringstream buf; - - for (const auto& frm : frames) - { - buf << " " << frm.fcn_name (); - - int line = frm.line (); - - if (line > 0) - { - buf << " at line " << line; - - int column = frm.column (); - - if (column > 0) - buf << " column " << column; - - buf << "\n"; - } - } - - return buf.str (); - } - - void tree_evaluator::push_dummy_scope (const std::string& name) - { - symbol_scope dummy_scope (name + "$dummy"); - - m_call_stack.push (dummy_scope); - } - - void tree_evaluator::pop_scope () - { - m_call_stack.pop (); - } - - symbol_scope tree_evaluator::get_top_scope () const - { - return m_call_stack.top_scope (); - } - - symbol_scope tree_evaluator::get_current_scope () const - { - return m_call_stack.current_scope (); - } - - void tree_evaluator::mlock (bool skip_first) const - { - octave_function *fcn = m_call_stack.current_function (skip_first); - - if (! fcn) - error ("mlock: invalid use outside a function"); - - if (fcn->is_builtin_function ()) - { - warning ("mlock: locking built-in function has no effect"); - return; - } - - fcn->lock (); - } - - void tree_evaluator::munlock (bool skip_first) const - { - octave_function *fcn = m_call_stack.current_function (skip_first); - - if (! fcn) - error ("munlock: invalid use outside a function"); - - if (fcn->is_builtin_function ()) + break; + + case 2: { - warning ("munlock: unlocking built-in function has no effect"); - return; - } - - fcn->unlock (); - } - - bool tree_evaluator::mislocked (bool skip_first) const - { - octave_function *fcn = m_call_stack.current_function (skip_first); - - if (! fcn) - error ("mislocked: invalid use outside a function"); - - return fcn->islocked (); - } - - octave_value - tree_evaluator::max_stack_depth (const octave_value_list& args, int nargout) - { - return m_call_stack.max_stack_depth (args, nargout); - } - - void tree_evaluator::display_call_stack () const - { - m_call_stack.display (); - } - - octave_value tree_evaluator::find (const std::string& name) - { - std::shared_ptr<stack_frame> frame - = m_call_stack.get_current_stack_frame (); - - octave_value val = frame->varval (name); - - if (val.is_defined ()) - return val; - - // Subfunction. I think it only makes sense to check for - // subfunctions if we are currently executing a function defined - // from a .m file. - - octave_value fcn = frame->find_subfunction (name); - - if (fcn.is_defined ()) - return fcn; - - symbol_table& symtab = m_interpreter.get_symbol_table (); - - return symtab.fcn_table_find (name, ovl ()); - } - - void tree_evaluator::clear_objects () - { - std::shared_ptr<stack_frame> frame - = m_call_stack.get_current_stack_frame (); - - frame->clear_objects (); - } - - void tree_evaluator::clear_variable (const std::string& name) - { - std::shared_ptr<stack_frame> frame - = m_call_stack.get_current_stack_frame (); - - frame->clear_variable (name); - } - - void tree_evaluator::clear_variable_pattern (const std::string& pattern) - { - std::shared_ptr<stack_frame> frame - = m_call_stack.get_current_stack_frame (); - - frame->clear_variable_pattern (pattern); - } - - void tree_evaluator::clear_variable_regexp (const std::string& pattern) - { - std::shared_ptr<stack_frame> frame - = m_call_stack.get_current_stack_frame (); - - frame->clear_variable_regexp (pattern); - } - - void tree_evaluator::clear_variables () - { - std::shared_ptr<stack_frame> frame - = m_call_stack.get_current_stack_frame (); - - frame->clear_variables (); - } - - void tree_evaluator::clear_global_variable (const std::string& name) - { - m_call_stack.clear_global_variable (name); - } - - void - tree_evaluator::clear_global_variable_pattern (const std::string& pattern) - { - m_call_stack.clear_global_variable_pattern (pattern); - } - - void tree_evaluator::clear_global_variable_regexp(const std::string& pattern) - { - m_call_stack.clear_global_variable_regexp (pattern); - } - - void tree_evaluator::clear_global_variables () - { - m_call_stack.clear_global_variables (); - } - - void tree_evaluator::clear_all (bool force) - { - // FIXME: should this also clear objects? - - clear_variables (); - clear_global_variables (); - - symbol_table& symtab = m_interpreter.get_symbol_table (); - - symtab.clear_functions (force); - } - - void tree_evaluator::clear_symbol (const std::string& name) - { - // FIXME: are we supposed to do both here? - - clear_variable (name); - - symbol_table& symtab = m_interpreter.get_symbol_table (); - - symtab.clear_function (name); - } - - void tree_evaluator::clear_symbol_pattern (const std::string& pattern) - { - // FIXME: are we supposed to do both here? - - clear_variable_pattern (pattern); - - symbol_table& symtab = m_interpreter.get_symbol_table (); - - symtab.clear_function_pattern (pattern); - } - - void tree_evaluator::clear_symbol_regexp (const std::string& pattern) - { - // FIXME: are we supposed to do both here? - - clear_variable_regexp (pattern); - - symbol_table& symtab = m_interpreter.get_symbol_table (); - - symtab.clear_function_regexp (pattern); - } - - std::list<std::string> tree_evaluator::global_variable_names () const - { - return m_call_stack.global_variable_names (); - } - - std::list<std::string> tree_evaluator::top_level_variable_names () const - { - return m_call_stack.top_level_variable_names (); - } - - std::list<std::string> tree_evaluator::variable_names () const - { - return m_call_stack.variable_names (); - } - - // Return a pointer to the user-defined function FNAME. If FNAME is empty, - // search backward for the first user-defined function in the - // current call stack. - - octave_user_code * - tree_evaluator::get_user_code (const std::string& fname, - const std::string& class_name) - { - octave_user_code *user_code = nullptr; - - if (fname.empty ()) - user_code = m_call_stack.debug_user_code (); - else - { - std::string name = fname; - - if (sys::file_ops::dir_sep_char () != '/' && name[0] == '@') - { - auto beg = name.begin () + 2; // never have @/method - auto end = name.end () - 1; // never have trailing '/' - std::replace (beg, end, '/', sys::file_ops::dir_sep_char ()); - } - - std::size_t name_len = name.length (); - - if (name_len > 2 && name.substr (name_len-2) == ".m") - name = name.substr (0, name_len-2); - - if (name.empty ()) - return nullptr; - - symbol_table& symtab = m_interpreter.get_symbol_table (); - - octave_value fcn; - std::size_t p2 = std::string::npos; - - if (name[0] == '@') - { - std::size_t p1 = name.find (sys::file_ops::dir_sep_char (), 1); - - if (p1 == std::string::npos) - return nullptr; - - std::string dispatch_type = name.substr (1, p1-1); - - p2 = name.find ('>', p1); - - std::string method = name.substr (p1+1, p2-1); - - fcn = symtab.find_method (method, dispatch_type); - } - else if (! class_name.empty ()) - { - cdef_manager& cdm = m_interpreter.get_cdef_manager (); - - fcn = cdm.find_method (class_name, name); - - // If there is no classdef method, then try legacy classes. - if (fcn.is_undefined ()) - fcn = symtab.find_method (name, class_name); - } - else + std::string arg0 = argv[0]; + std::string arg1 = argv[1]; + + if (arg1 == "on" || arg1 == "off") + std::swap (arg0, arg1); + + if (arg0 == "on") { - p2 = name.find ('>'); - - std::string main_fcn = name.substr (0, p2); - - fcn = symtab.find_function (main_fcn); - } - - // List of function names sub1>sub2>... - std::string subfuns; - - if (p2 != std::string::npos) - subfuns = name.substr (p2+1); - - if (fcn.is_defined () && fcn.is_user_code ()) - user_code = fcn.user_code_value (); - - if (! user_code || subfuns.empty ()) - return user_code; - - fcn = user_code->find_subfunction (subfuns); - - if (fcn.is_undefined ()) - return nullptr; - - user_code = fcn.user_code_value (); - } - - return user_code; - } - - std::string - tree_evaluator::current_function_name (bool skip_first) const - { - octave_function *curfcn = m_call_stack.current_function (skip_first); - - if (curfcn) - return curfcn->name (); - - return ""; - } - - bool - tree_evaluator::in_user_code () const - { - return m_call_stack.current_user_code () != nullptr; - } - - void - tree_evaluator::visit_decl_command (tree_decl_command& cmd) - { - if (m_echo_state) - { - int line = cmd.line (); - if (line < 0) - line = 1; - echo_code (line); - m_echo_file_pos = line + 1; - } - - if (m_debug_mode) - do_breakpoint (cmd.is_active_breakpoint (*this)); - - // FIXME: tree_decl_init_list is not derived from tree, so should it - // really have an accept method? - - tree_decl_init_list *init_list = cmd.initializer_list (); - - if (init_list) - init_list->accept (*this); - } - - void - tree_evaluator::visit_decl_elt (tree_decl_elt& elt) - { - tree_identifier *id = elt.ident (); - - if (id) - { - if (elt.is_global ()) - m_call_stack.make_global (id->symbol ()); - else if (elt.is_persistent ()) - m_call_stack.make_persistent (id->symbol ()); - else - error ("declaration list element not global or persistent"); - - octave_lvalue ult = id->lvalue (*this); - - if (ult.is_undefined ()) - { - tree_expression *expr = elt.expression (); - - octave_value init_val; - - if (expr) - init_val = expr->evaluate (*this); - else - init_val = Matrix (); - - ult.assign (octave_value::op_asn_eq, init_val); - } - } - } - - template <typename T> - void - tree_evaluator::execute_range_loop (const range<T>& rng, int line, - octave_lvalue& ult, - tree_statement_list *loop_body) - { - octave_idx_type steps = rng.numel (); - - if (math::isinf (rng.limit ())) - warning_with_id ("Octave:infinite-loop", - "FOR loop limit is infinite, will stop after %" - OCTAVE_IDX_TYPE_FORMAT " steps", steps); - - for (octave_idx_type i = 0; i < steps; i++) - { - if (m_echo_state) - m_echo_file_pos = line; - - octave_value val (rng.elem (i)); - - ult.assign (octave_value::op_asn_eq, val); - - if (loop_body) - loop_body->accept (*this); - - if (quit_loop_now ()) - break; - } - } - - void - tree_evaluator::visit_simple_for_command (tree_simple_for_command& cmd) - { - int line = cmd.line (); - if (line < 0) - line = 1; - - if (m_echo_state) - { - echo_code (line); - line++; - } - - if (m_debug_mode) - do_breakpoint (cmd.is_active_breakpoint (*this)); - - // FIXME: need to handle PARFOR loops here using cmd.in_parallel () - // and cmd.maxproc_expr (); - - unwind_protect_var<bool> upv (m_in_loop_command, true); - - tree_expression *expr = cmd.control_expr (); - - octave_value rhs = expr->evaluate (*this); - - if (rhs.is_undefined ()) - return; - - tree_expression *lhs = cmd.left_hand_side (); - - octave_lvalue ult = lhs->lvalue (*this); - - tree_statement_list *loop_body = cmd.body (); - - if (rhs.is_range ()) - { - // FIXME: is there a better way to dispatch here? - - if (rhs.is_double_type ()) - { - execute_range_loop (rhs.range_value (), line, ult, loop_body); - return; - } - - // For now, disable all but range<double>. - -#if 0 - if (rhs.is_int64_type ()) - { - execute_range_loop (rhs.int64_range_value (), line, ult, loop_body); - return; - } - - if (rhs.is_uint64_type ()) - { - execute_range_loop (rhs.uint64_range_value (), line, ult, loop_body); - return; - } - - if (rhs.is_int32_type ()) - { - execute_range_loop (rhs.int32_range_value (), line, ult, loop_body); - return; - } - - if (rhs.is_uint32_type ()) - { - execute_range_loop (rhs.uint32_range_value (), line, ult, loop_body); - return; - } - - if (rhs.is_int16_type ()) - { - execute_range_loop (rhs.int16_range_value (), line, ult, loop_body); - return; - } - - if (rhs.is_uint16_type ()) - { - execute_range_loop (rhs.uint16_range_value (), line, ult, loop_body); - return; - } - - if (rhs.is_int8_type ()) - { - execute_range_loop (rhs.int8_range_value (), line, ult, loop_body); - return; - } - - if (rhs.is_uint8_type ()) - { - execute_range_loop (rhs.uint8_range_value (), line, ult, loop_body); - return; - } - - if (rhs.is_single_type ()) - { - execute_range_loop (rhs.float_range_value (), line, ult, loop_body); - return; - } -#endif - } - - if (rhs.is_scalar_type ()) - { - if (m_echo_state) - m_echo_file_pos = line; - - ult.assign (octave_value::op_asn_eq, rhs); - - if (loop_body) - loop_body->accept (*this); - - // Maybe decrement break and continue states. - quit_loop_now (); - - return; - } - - // Also handle any range types not explicitly handled above, though - // not as efficiently as the specialized code above. - - if (rhs.is_range () || rhs.is_matrix_type () || rhs.iscell () - || rhs.is_string () || rhs.isstruct ()) - { - // A matrix or cell is reshaped to 2 dimensions and iterated by - // columns. - - dim_vector dv = rhs.dims ().redim (2); - - octave_idx_type nrows = dv(0); - octave_idx_type steps = dv(1); - - octave_value arg = rhs; - if (rhs.ndims () > 2) - arg = arg.reshape (dv); - - if (nrows > 0 && steps > 0) - { - octave_value_list idx; - octave_idx_type iidx; - - // for row vectors, use single index to speed things up. - if (nrows == 1) + if (arg1 == "all") { - idx.resize (1); - iidx = 0; + m_echo = (ECHO_SCRIPTS | ECHO_FUNCTIONS | ECHO_ALL); + m_echo_files.clear (); } else { - idx.resize (2); - idx(0) = octave_value::magic_colon_t; - iidx = 1; - } - - for (octave_idx_type i = 1; i <= steps; i++) - { - if (m_echo_state) - m_echo_file_pos = line; - - // index_op expects one-based indices. - idx(iidx) = i; - octave_value val = arg.index_op (idx); - - ult.assign (octave_value::op_asn_eq, val); - - if (loop_body) - loop_body->accept (*this); - - if (quit_loop_now ()) - break; + std::string file = fcn_file_in_path (arg1); + file = sys::env::make_absolute (file); + + if (file.empty ()) + error ("echo: no such file %s", arg1.c_str ()); + + m_echo |= ECHO_FUNCTIONS; + m_echo_files[file] = true; } } - else - { - // Handle empty cases, while still assigning to loop var. - ult.assign (octave_value::op_asn_eq, arg); - } - - return; - } - - error ("invalid type in for loop expression near line %d, column %d", - cmd.line (), cmd.column ()); - } - - void - tree_evaluator::visit_complex_for_command (tree_complex_for_command& cmd) - { - int line = cmd.line (); - if (line < 0) - line = 1; - - if (m_echo_state) - { - echo_code (line); - line++; - } - - if (m_debug_mode) - do_breakpoint (cmd.is_active_breakpoint (*this)); - - unwind_protect_var<bool> upv (m_in_loop_command, true); - - tree_expression *expr = cmd.control_expr (); - - octave_value rhs = expr->evaluate (*this); - - if (rhs.is_undefined ()) - return; - - if (! rhs.isstruct ()) - error ("in statement 'for [X, Y] = VAL', VAL must be a structure"); - - // Cycle through structure elements. First element of id_list - // is set to value and the second is set to the name of the - // structure element. - - tree_argument_list *lhs = cmd.left_hand_side (); - - auto p = lhs->begin (); - - tree_expression *elt = *p++; - - octave_lvalue val_ref = elt->lvalue (*this); - - elt = *p; - - octave_lvalue key_ref = elt->lvalue (*this); - - const octave_map tmp_val = rhs.map_value (); - - tree_statement_list *loop_body = cmd.body (); - - string_vector keys = tmp_val.keys (); - - octave_idx_type nel = keys.numel (); - - for (octave_idx_type i = 0; i < nel; i++) - { - if (m_echo_state) - m_echo_file_pos = line; - - std::string key = keys[i]; - - const Cell val_lst = tmp_val.contents (key); - - octave_idx_type n = val_lst.numel (); - - octave_value val = (n == 1) ? val_lst(0) : octave_value (val_lst); - - val_ref.assign (octave_value::op_asn_eq, val); - key_ref.assign (octave_value::op_asn_eq, key); - - if (loop_body) - loop_body->accept (*this); - - if (quit_loop_now ()) - break; - } - } - - void tree_evaluator::visit_spmd_command (tree_spmd_command& cmd) - { - // For now, we just execute the commands serially. - - tree_statement_list *body = cmd.body (); - - if (body) - body->accept (*this); - } - - octave_value - tree_evaluator::evaluate_anon_fcn_handle (tree_anon_fcn_handle& afh) - { - // FIXME: should CMD_LIST be limited to a single expression? - // I think that is what Matlab does. - - symbol_scope new_scope; - symbol_scope scope = afh.scope (); - if (scope) - new_scope = scope.dup (); - - tree_parameter_list *param_list = afh.parameter_list (); - tree_parameter_list *param_list_dup - = param_list ? param_list->dup (new_scope) : nullptr; - - tree_parameter_list *ret_list = nullptr; - - tree_statement_list *stmt_list = nullptr; - - symbol_scope parent_scope = get_current_scope (); - - new_scope.set_parent (parent_scope); - new_scope.set_primary_parent (parent_scope); - - tree_expression *expr = afh.expression (); - if (expr) - { - tree_expression *expr_dup = expr->dup (new_scope); - tree_statement *stmt = new tree_statement (expr_dup, nullptr); - stmt_list = new tree_statement_list (stmt); - } - - tree_anon_scopes anon_fcn_ctx (afh); - - std::set<std::string> free_vars = anon_fcn_ctx.free_variables (); - - stack_frame::local_vars_map local_vars; - - std::shared_ptr<stack_frame> frame - = m_call_stack.get_current_stack_frame (); - - for (auto& name : free_vars) - { - octave_value val = frame->varval (name); - - if (val.is_defined ()) - local_vars[name] = val; - } - - octave_user_function *af - = new octave_user_function (new_scope, param_list_dup, ret_list, - stmt_list); - - octave_function *curr_fcn = m_call_stack.current_function (); - - bool is_nested = false; - - if (curr_fcn) - { - // FIXME: maybe it would be better to just stash curr_fcn - // instead of individual bits of info about it? - - // An anonymous function defined inside another nested function - // or parent of a nested function also behaves like a nested - // function. - - if (curr_fcn->is_parent_function () || curr_fcn->is_nested_function ()) + else if (arg0 == "off") { - is_nested = true; - af->mark_as_nested_function (); - new_scope.set_nesting_depth (parent_scope.nesting_depth () + 1); - } - - af->stash_dir_name (curr_fcn->dir_name ()); - - new_scope.cache_fcn_file_name (curr_fcn->fcn_file_name ()); - new_scope.cache_dir_name (curr_fcn->dir_name ()); - - // The following is needed so that class method dispatch works - // properly for anonymous functions that wrap class methods. - - if (curr_fcn->is_class_method () || curr_fcn->is_class_constructor ()) - af->stash_dispatch_class (curr_fcn->dispatch_class ()); - - af->stash_fcn_file_name (curr_fcn->fcn_file_name ()); - } - - af->mark_as_anonymous_function (); - - octave_value ov_fcn (af); - - return (is_nested - ? octave_value (new octave_fcn_handle (ov_fcn, local_vars, frame)) - : octave_value (new octave_fcn_handle (ov_fcn, local_vars))); - } - - octave_value_list - tree_evaluator::execute_builtin_function (octave_builtin& builtin_function, - int nargout, - const octave_value_list& args) - { - octave_value_list retval; - - if (args.has_magic_colon ()) - error ("invalid use of colon in function argument list"); - - profiler::enter<octave_builtin> block (m_profiler, builtin_function); - - octave_builtin::fcn fcn = builtin_function.function (); - - if (fcn) - retval = (*fcn) (args, nargout); - else - { - octave_builtin::meth meth = builtin_function.method (); - - retval = (*meth) (m_interpreter, args, nargout); - } - - // Do not allow null values to be returned from functions. - // FIXME: perhaps true builtins should be allowed? - - retval.make_storable_values (); - - // Fix the case of a single undefined value. - // This happens when a compiled function uses - // - // octave_value retval; - // - // instead of - // - // octave_value_list retval; - // - // the idiom is very common, so we solve that here. - - if (retval.length () == 1 && retval.xelem (0).is_undefined ()) - retval.clear (); - - return retval; - } - - octave_value_list - tree_evaluator::execute_mex_function (octave_mex_function& mex_function, - int nargout, - const octave_value_list& args) - { - octave_value_list retval; - - if (args.has_magic_colon ()) - error ("invalid use of colon in function argument list"); - - profiler::enter<octave_mex_function> block (m_profiler, mex_function); - - retval = call_mex (mex_function, args, nargout); - - return retval; - } - - octave_value_list - tree_evaluator::execute_user_script (octave_user_script& user_script, - int nargout, - const octave_value_list& args) - { - octave_value_list retval; - - std::string file_name = user_script.fcn_file_name (); - - if (args.length () != 0 || nargout != 0) - error ("invalid call to script %s", file_name.c_str ()); - - tree_statement_list *cmd_list = user_script.body (); - - if (! cmd_list) - return retval; - - // FIXME: Maybe this check belongs in the places where we push a new - // stack frame? Or in the call_stack push method itself? - - if (m_call_stack.size () >= static_cast<std::size_t> (m_max_recursion_depth)) - error ("max_recursion_depth exceeded"); - - unwind_protect_var<stmt_list_type> upv (m_statement_context, SC_SCRIPT); - - profiler::enter<octave_user_script> block (m_profiler, user_script); - - if (echo ()) - push_echo_state (tree_evaluator::ECHO_SCRIPTS, file_name); - - // FIXME: Should we be using tree_evaluator::eval here? - - cmd_list->accept (*this); - - if (m_returning) - m_returning = 0; - - if (m_breaking) - m_breaking--; - - return retval; - } - - void - tree_evaluator::visit_octave_user_script (octave_user_script&) - { - // ?? - panic_impossible (); - } - - octave_value_list - tree_evaluator::execute_user_function (octave_user_function& user_function, - int nargout, - const octave_value_list& xargs) - { - octave_value_list retval; - - // If this function is a classdef constructor, extract the first input - // argument, which must be the partially constructed object instance. - - octave_value_list args (xargs); - octave_value_list ret_args; - - int nargin = args.length (); - - if (user_function.is_classdef_constructor ()) - { - if (nargin > 0) - { - ret_args = args.slice (0, 1, true); - --nargin; - args = args.slice (1, nargin, true); - } - else - panic_impossible (); - } - - // FIXME: this probably shouldn't be a double-precision matrix. - Matrix ignored_outputs = ignored_fcn_outputs (); - - tree_parameter_list *param_list = user_function.parameter_list (); - - bool takes_varargs = false; - int max_inputs = 0; - - if (param_list) - { - takes_varargs = param_list->takes_varargs (); - max_inputs = param_list->length (); - } - - if (! takes_varargs && nargin > max_inputs) - { - std::string name = user_function.name (); - - if (name.empty ()) - name = "@<anonymous>"; - - error_with_id ("Octave:invalid-fun-call", - "%s: function called with too many inputs", - name.c_str ()); - } - - define_parameter_list_from_arg_vector (param_list, args); - - tree_parameter_list *ret_list = user_function.return_list (); - - if (ret_list && ! ret_list->takes_varargs ()) - { - int max_outputs = ret_list->length (); - - if (nargout > max_outputs) - { - std::string name = user_function.name (); - - error_with_id ("Octave:invalid-fun-call", - "%s: function called with too many outputs", - name.c_str ()); - } - } - - bind_auto_fcn_vars (xargs.name_tags (), ignored_outputs, nargin, - nargout, user_function.takes_varargs (), - user_function.all_va_args (args)); - - // For classdef constructor, pre-populate the output arguments - // with the pre-initialized object instance, extracted above. - - if (user_function.is_classdef_constructor ()) - { - if (! ret_list) - error ("%s: invalid classdef constructor, no output argument defined", - user_function.dispatch_class ().c_str ()); - - define_parameter_list_from_arg_vector (ret_list, ret_args); - } - - // FIXME: Maybe this check belongs in the places where we push a - // new stack frame? Or in the call_stack push method itself? - - if (m_call_stack.size () >= static_cast<std::size_t> (m_max_recursion_depth)) - error ("max_recursion_depth exceeded"); - - unwind_action act2 ([&user_function] () { - user_function.restore_warning_states (); - }); - - // Evaluate the commands that make up the function. - - unwind_protect_var<stmt_list_type> upv (m_statement_context, SC_FUNCTION); - - tree_statement_list *cmd_list = user_function.body (); - - if (cmd_list) - { - profiler::enter<octave_user_function> - block (m_profiler, user_function); - - if (echo ()) - push_echo_state (tree_evaluator::ECHO_FUNCTIONS, - user_function.fcn_file_name ()); - - if (user_function.is_special_expr ()) - { - panic_if (cmd_list->length () != 1); - - tree_statement *stmt = cmd_list->front (); - - tree_expression *expr = stmt->expression (); - - if (expr) + if (arg1 == "all") + { + m_echo = ECHO_OFF; + m_echo_files.clear (); + } + else { - m_call_stack.set_location (stmt->line (), stmt->column ()); - - retval = expr->evaluate_n (*this, nargout); + std::string file = fcn_file_in_path (arg1); + file = sys::env::make_absolute (file); + + if (file.empty ()) + error ("echo: no such file %s", arg1.c_str ()); + + m_echo_files[file] = false; } } else - cmd_list->accept (*this); - - if (m_returning) - m_returning = 0; - - if (m_breaking) - m_breaking--; - } - - // Copy return values out. - - if (ret_list && ! user_function.is_special_expr ()) - { - Cell varargout; - - if (ret_list->takes_varargs ()) - { - octave_value varargout_varval = varval ("varargout"); - - if (varargout_varval.is_defined ()) - varargout = varargout_varval.xcell_value ("varargout must be a cell array object"); - } - - retval = convert_return_list_to_const_vector (ret_list, nargout, - ignored_outputs, - varargout); - } - - return retval; - } - - void - tree_evaluator::visit_octave_user_function (octave_user_function&) - { - // ?? - panic_impossible (); - } - - void - tree_evaluator::visit_octave_user_function_header (octave_user_function&) - { - panic_impossible (); - } - - void - tree_evaluator::visit_octave_user_function_trailer (octave_user_function&) - { - panic_impossible (); - } - - void - tree_evaluator::visit_function_def (tree_function_def& cmd) - { - octave_value fcn = cmd.function (); - - octave_function *f = fcn.function_value (); - - if (f) - { - std::string nm = f->name (); - - symbol_table& symtab = m_interpreter.get_symbol_table (); - - symtab.install_cmdline_function (nm, fcn); - - // Make sure that any variable with the same name as the new - // function is cleared. - - assign (nm); - } - } - - void - tree_evaluator::visit_identifier (tree_identifier&) - { - panic_impossible (); - } - - void - tree_evaluator::visit_if_clause (tree_if_clause&) - { - panic_impossible (); - } - - void - tree_evaluator::visit_if_command (tree_if_command& cmd) - { - if (m_echo_state) - { - int line = cmd.line (); - if (line < 0) - line = 1; - echo_code (line); - m_echo_file_pos = line + 1; - } - - // FIXME: tree_if_command_list is not derived from tree, so should it - // really have an accept method? - - tree_if_command_list *lst = cmd.cmd_list (); - - if (lst) - lst->accept (*this); - } - - void - tree_evaluator::visit_if_command_list (tree_if_command_list& lst) - { - for (tree_if_clause *tic : lst) - { - tree_expression *expr = tic->condition (); - - if (! (in_debug_repl () - && m_call_stack.current_frame () == m_debug_frame)) - m_call_stack.set_location (tic->line (), tic->column ()); - - if (m_debug_mode && ! tic->is_else_clause ()) - do_breakpoint (tic->is_active_breakpoint (*this)); - - if (tic->is_else_clause () || is_logically_true (expr, "if")) - { - tree_statement_list *stmt_lst = tic->commands (); - - if (stmt_lst) - stmt_lst->accept (*this); - - break; - } - } - } - - void - tree_evaluator::visit_index_expression (tree_index_expression&) - { - panic_impossible (); - } - - void - tree_evaluator::visit_matrix (tree_matrix&) - { - panic_impossible (); - } - - void - tree_evaluator::visit_cell (tree_cell&) - { - panic_impossible (); - } - - void - tree_evaluator::visit_multi_assignment (tree_multi_assignment&) - { - panic_impossible (); - } - - void - tree_evaluator::visit_no_op_command (tree_no_op_command& cmd) - { - if (m_echo_state) - { - int line = cmd.line (); - if (line < 0) - line = 1; - echo_code (line); - m_echo_file_pos = line + 1; - } - - if (m_debug_mode && cmd.is_end_of_fcn_or_script ()) - do_breakpoint (cmd.is_active_breakpoint (*this), true); - } - - void - tree_evaluator::visit_constant (tree_constant&) - { - panic_impossible (); - } - - void - tree_evaluator::visit_fcn_handle (tree_fcn_handle&) - { - panic_impossible (); - } - - void - tree_evaluator::visit_parameter_list (tree_parameter_list&) - { - panic_impossible (); - } - - void - tree_evaluator::visit_postfix_expression (tree_postfix_expression&) - { - panic_impossible (); - } - - void - tree_evaluator::visit_prefix_expression (tree_prefix_expression&) - { - panic_impossible (); - } - - void - tree_evaluator::visit_return_command (tree_return_command& cmd) - { - if (m_echo_state) - { - int line = cmd.line (); - if (line < 0) - line = 1; - echo_code (line); - m_echo_file_pos = line + 1; - } - - if (m_debug_mode) - do_breakpoint (cmd.is_active_breakpoint (*this)); - - // Act like dbcont. - - if (in_debug_repl () && m_call_stack.current_frame () == m_debug_frame) - dbcont (); - else if (m_statement_context == SC_FUNCTION - || m_statement_context == SC_SCRIPT - || m_in_loop_command) - m_returning = 1; - } - - void - tree_evaluator::visit_simple_assignment (tree_simple_assignment&) - { - panic_impossible (); - } - - void - tree_evaluator::visit_statement (tree_statement& stmt) - { - tree_command *cmd = stmt.command (); - tree_expression *expr = stmt.expression (); - - if (cmd || expr) - { - if (! (in_debug_repl () - && m_call_stack.current_frame () == m_debug_frame)) - m_call_stack.set_location (stmt.line (), stmt.column ()); - - try - { - if (cmd) - { - unwind_protect_var<const std::list<octave_lvalue> *> - upv (m_lvalue_list, nullptr); - - cmd->accept (*this); - } - else - { - if (m_echo_state) - { - int line = stmt.line (); - if (line < 0) - line = 1; - echo_code (line); - m_echo_file_pos = line + 1; - } - - if (m_debug_mode) - do_breakpoint (expr->is_active_breakpoint (*this)); - - // FIXME: maybe all of this should be packaged in - // one virtual function that returns a flag saying whether - // or not the expression will take care of binding ans and - // printing the result. - - // FIXME: it seems that we should just have to - // evaluate the expression and that should take care of - // everything, binding ans as necessary? - - octave_value tmp_result = expr->evaluate (*this, 0); - - if (tmp_result.is_defined ()) - { - bool do_bind_ans = false; - - if (expr->is_identifier ()) - do_bind_ans = ! is_variable (expr); - else - do_bind_ans = ! expr->is_assignment_expression (); - - if (do_bind_ans) - bind_ans (tmp_result, expr->print_result () - && statement_printing_enabled ()); - } - } - } - catch (const std::bad_alloc&) - { - // FIXME: We want to use error_with_id here so that give users - // control over this error message but error_with_id will - // require some memory allocations. Is there anything we can - // do to make those more likely to succeed? - - error_with_id ("Octave:bad-alloc", - "out of memory or dimension too large for Octave's index type"); - } - catch (const interrupt_exception&) - { - // If we are debugging, then continue with next statement. - // Otherwise, jump out of here. - - if (m_debug_mode) - m_interpreter.recover_from_exception (); - else - throw; - } - catch (const execution_exception& ee) - { - error_system& es = m_interpreter.get_error_system (); - - if ((m_interpreter.interactive () - || application::forced_interactive ()) - && ((es.debug_on_error () - && m_bp_table.debug_on_err (es.last_error_id ())) - || (es.debug_on_caught () - && m_bp_table.debug_on_caught (es.last_error_id ()))) - && in_user_code ()) - { - es.save_exception (ee); - es.display_exception (ee); - - enter_debugger (); - - // It doesn't make sense to continue execution after an - // error occurs so force the debugger to quit all debug - // levels and return the the top prompt. - - throw quit_debug_exception (true); - } - else - throw; - } - } - } - - void - tree_evaluator::visit_statement_list (tree_statement_list& lst) - { - // FIXME: commented out along with else clause below. - // static octave_value_list empty_list; - - auto p = lst.begin (); - - if (p != lst.end ()) - { - while (true) - { - tree_statement *elt = *p++; - - if (! elt) - error ("invalid statement found in statement list!"); - - octave_quit (); - - elt->accept (*this); - - if (m_breaking || m_continuing) - break; - - if (m_returning) - break; - - if (p == lst.end ()) - break; - else - { - // Clear previous values before next statement is - // evaluated so that we aren't holding an extra - // reference to a value that may be used next. For - // example, in code like this: - // - // X = rand (N); # refcount for X should be 1 - // # after this statement - // - // X(idx) = val; # no extra copy of X should be - // # needed, but we will be faked - // # out if retval is not cleared - // # between statements here - - // result_values = empty_list; - } - } - } - } - - void - tree_evaluator::visit_switch_case (tree_switch_case&) - { - panic_impossible (); - } - - void - tree_evaluator::visit_switch_case_list (tree_switch_case_list&) - { - panic_impossible (); - } - - void - tree_evaluator::visit_switch_command (tree_switch_command& cmd) - { - if (m_echo_state) - { - int line = cmd.line (); - if (line < 0) - line = 1; - echo_code (line); - m_echo_file_pos = line + 1; - } - - if (m_debug_mode) - do_breakpoint (cmd.is_active_breakpoint (*this)); - - tree_expression *expr = cmd.switch_value (); - - if (! expr) - error ("missing value in switch command near line %d, column %d", - cmd.line (), cmd.column ()); - - octave_value val = expr->evaluate (*this); - - tree_switch_case_list *lst = cmd.case_list (); - - if (lst) - { - for (tree_switch_case *t : *lst) - { - if (t->is_default_case () || switch_case_label_matches (t, val)) - { - tree_statement_list *stmt_lst = t->commands (); - - if (stmt_lst) - stmt_lst->accept (*this); - - break; - } - } - } - } - - void - tree_evaluator::visit_try_catch_command (tree_try_catch_command& cmd) - { - if (m_echo_state) - { - int line = cmd.line (); - if (line < 0) - line = 1; - echo_code (line); - m_echo_file_pos = line + 1; - } - - bool execution_error = false; - octave_scalar_map err_map; - - tree_statement_list *try_code = cmd.body (); - - if (try_code) - { - // unwind frame before catch block - - unwind_protect frame; - - interpreter_try (frame); - - // The catch code is *not* added to unwind_protect stack; it - // doesn't need to be run on interrupts. - - try - { - try_code->accept (*this); - } - catch (const execution_exception& ee) - { - execution_error = true; - - error_system& es = m_interpreter.get_error_system (); - - es.save_exception (ee); - - err_map.assign ("message", es.last_error_message ()); - err_map.assign ("identifier", es.last_error_id ()); - err_map.assign ("stack", es.last_error_stack ()); - - m_interpreter.recover_from_exception (); - } - - // Actions attached to unwind_protect frame will run here, prior - // to executing the catch block. - } - - if (execution_error) - { - tree_statement_list *catch_code = cmd.cleanup (); - - if (catch_code) - { - tree_identifier *expr_id = cmd.identifier (); - - if (expr_id) - { - octave_lvalue ult = expr_id->lvalue (*this); - - ult.assign (octave_value::op_asn_eq, err_map); - } - - // perform actual "catch" block - catch_code->accept (*this); - } - } - } - - void - tree_evaluator::do_unwind_protect_cleanup_code (tree_statement_list *list) - { - unwind_protect frame; - - frame.protect_var (octave_interrupt_state); - octave_interrupt_state = 0; - - // We want to preserve the last location info for possible - // backtracking. - - frame.add (&call_stack::set_line, &m_call_stack, - m_call_stack.current_line ()); - - frame.add (&call_stack::set_column, &m_call_stack, - m_call_stack.current_column ()); - - // Similarly, if we have seen a return or break statement, allow all - // the cleanup code to run before returning or handling the break. - // We don't have to worry about continue statements because they can - // only occur in loops. - - frame.protect_var (m_returning); - m_returning = 0; - - frame.protect_var (m_breaking); - m_breaking = 0; - - try - { - if (list) - list->accept (*this); - } - catch (const execution_exception& ee) - { - error_system& es = m_interpreter.get_error_system (); - - es.save_exception (ee); - m_interpreter.recover_from_exception (); - - if (m_breaking || m_returning) - frame.discard (2); - else - frame.run (2); - - frame.discard (2); - - throw; - } - - // The unwind_protects are popped off the stack in the reverse of - // the order they are pushed on. - - // FIXME: these statements say that if we see a break or - // return statement in the cleanup block, that we want to use the - // new value of the breaking or returning flag instead of restoring - // the previous value. Is that the right thing to do? I think so. - // Consider the case of - // - // function foo () - // unwind_protect - // fprintf (stderr, "1: this should always be executed\n"); - // break; - // fprintf (stderr, "1: this should never be executed\n"); - // unwind_protect_cleanup - // fprintf (stderr, "2: this should always be executed\n"); - // return; - // fprintf (stderr, "2: this should never be executed\n"); - // end_unwind_protect - // endfunction - // - // If we reset the value of the breaking flag, both the returning - // flag and the breaking flag will be set, and we shouldn't have - // both. So, use the most recent one. If there is no return or - // break in the cleanup block, the values should be reset to - // whatever they were when the cleanup block was entered. - - if (m_breaking || m_returning) - frame.discard (2); - else - frame.run (2); - } - - void - tree_evaluator::visit_unwind_protect_command (tree_unwind_protect_command& cmd) - { - if (m_echo_state) - { - int line = cmd.line (); - if (line < 0) - line = 1; - echo_code (line); - m_echo_file_pos = line + 1; + print_usage (); } - - tree_statement_list *cleanup_code = cmd.cleanup (); - - tree_statement_list *unwind_protect_code = cmd.body (); - - if (unwind_protect_code) - { - try - { - unwind_protect_code->accept (*this); - } - catch (const execution_exception& ee) - { - error_system& es = m_interpreter.get_error_system (); - - // FIXME: Maybe we should be able to temporarily set the - // interpreter's exception handling state to something "safe" - // while the cleanup block runs instead of just resetting it - // here? - es.save_exception (ee); - m_interpreter.recover_from_exception (); - - // Run the cleanup code on exceptions, so that it is run even - // in case of interrupt or out-of-memory. - do_unwind_protect_cleanup_code (cleanup_code); - - // If an error occurs inside the cleanup code, a new - // exception will be thrown instead of the original. - throw; - } - catch (const interrupt_exception&) - { - // The comments above apply here as well. - m_interpreter.recover_from_exception (); - do_unwind_protect_cleanup_code (cleanup_code); - throw; - } - - // Also execute the unwind_protect_cleanump code if the - // unwind_protect block runs without error. - do_unwind_protect_cleanup_code (cleanup_code); - } - } - - void - tree_evaluator::visit_while_command (tree_while_command& cmd) - { - int line = cmd.line (); - if (line < 0) - line = 1; - - if (m_echo_state) - { - echo_code (line); - line++; - } - - unwind_protect_var<bool> upv (m_in_loop_command, true); - - tree_expression *expr = cmd.condition (); - - if (! expr) - panic_impossible (); - - for (;;) - { - if (m_echo_state) - m_echo_file_pos = line; - - if (m_debug_mode) - do_breakpoint (cmd.is_active_breakpoint (*this)); - - if (is_logically_true (expr, "while")) - { - tree_statement_list *loop_body = cmd.body (); - - if (loop_body) - loop_body->accept (*this); - - if (quit_loop_now ()) - break; - } - else - break; - } - } - - void - tree_evaluator::visit_do_until_command (tree_do_until_command& cmd) - { - int line = cmd.line (); - if (line < 0) - line = 1; - - if (m_echo_state) - { - echo_code (line); - line++; - } - - unwind_protect_var<bool> upv (m_in_loop_command, true); - - tree_expression *expr = cmd.condition (); - - if (! expr) - panic_impossible (); - - for (;;) - { - if (m_echo_state) - m_echo_file_pos = line; - - tree_statement_list *loop_body = cmd.body (); - - if (loop_body) - loop_body->accept (*this); - - if (quit_loop_now ()) - break; - - if (m_debug_mode) - do_breakpoint (cmd.is_active_breakpoint (*this)); - - if (is_logically_true (expr, "do-until")) - break; - } - } - - void - tree_evaluator::visit_superclass_ref (tree_superclass_ref&) - { - panic_impossible (); - } - - void - tree_evaluator::visit_metaclass_query (tree_metaclass_query&) - { - panic_impossible (); - } - - void tree_evaluator::bind_ans (const octave_value& val, bool print) - { - static std::string ans = "ans"; - - if (val.is_defined ()) - { - if (val.is_cs_list ()) - { - octave_value_list lst = val.list_value (); - - for (octave_idx_type i = 0; i < lst.length (); i++) - bind_ans (lst(i), print); - } - else - { - // FIXME: Maybe assign could also return the assigned value, - // just for convenience? - - assign (ans, val); - - if (print) - { - // Use varval instead of displaying VAL directly so that - // we get the right type and value for things like - // magic_int values that may mutate when stored. - - octave_value_list args = ovl (varval (ans)); - args.stash_name_tags (string_vector (ans)); - m_interpreter.feval ("display", args); - } - } - } - } - - void - tree_evaluator::do_breakpoint (tree_statement& stmt) - { - do_breakpoint (stmt.is_active_breakpoint (*this), - stmt.is_end_of_fcn_or_script ()); - } - - void - tree_evaluator::do_breakpoint (bool is_breakpoint, - bool is_end_of_fcn_or_script) - { - bool break_on_this_statement = false; - - if (is_breakpoint) - break_on_this_statement = true; - else if (m_dbstep_flag > 0) - { - if (m_call_stack.current_frame () == m_debug_frame) - { - if (m_dbstep_flag == 1 || is_end_of_fcn_or_script) - { - // We get here if we are doing a "dbstep" or a "dbstep N" and - // the count has reached 1 so that we must stop and return to - // debug prompt. Alternatively, "dbstep N" has been used but - // the end of the frame has been reached so we stop at the last - // line and return to prompt. - - break_on_this_statement = true; - } - else - { - // Executing "dbstep N". Decrease N by one and continue. - - m_dbstep_flag--; - } - - } - else if (m_dbstep_flag == 1 - && m_call_stack.current_frame () < m_debug_frame) - { - // We stepped out from the end of a function. - - m_debug_frame = m_call_stack.current_frame (); - - break_on_this_statement = true; - } - } - else if (m_dbstep_flag == -1) - { - // We get here if we are doing a "dbstep in". - - break_on_this_statement = true; - - m_debug_frame = m_call_stack.current_frame (); - } - else if (m_dbstep_flag == -2) - { - // We get here if we are doing a "dbstep out". Check for end of - // function and whether the current frame is the same as the - // cached value because we want to step out from the frame where - // "dbstep out" was evaluated, not from any functions called from - // that frame. - - if (is_end_of_fcn_or_script - && m_call_stack.current_frame () == m_debug_frame) - m_dbstep_flag = -1; - } - - if (! break_on_this_statement) - break_on_this_statement = m_break_on_next_stmt; - - m_break_on_next_stmt = false; - - if (break_on_this_statement) - { - m_dbstep_flag = 0; - - enter_debugger (); - } - } - - bool - tree_evaluator::is_logically_true (tree_expression *expr, - const char *warn_for) - { - bool expr_value = false; - - m_call_stack.set_location (expr->line (), expr->column ()); - - octave_value t1 = expr->evaluate (*this); - - if (t1.is_defined ()) - return t1.is_true (); - else - error ("%s: undefined value used in conditional expression", warn_for); - - return expr_value; - } - - octave_value - tree_evaluator::max_recursion_depth (const octave_value_list& args, - int nargout) - { - return set_internal_variable (m_max_recursion_depth, args, nargout, - "max_recursion_depth", 0); - } - - symbol_info_list - tree_evaluator::glob_symbol_info (const std::string& pattern) const - { - return m_call_stack.glob_symbol_info (pattern); - } - - symbol_info_list - tree_evaluator::regexp_symbol_info (const std::string& pattern) const - { - return m_call_stack.regexp_symbol_info (pattern); - } - - symbol_info_list - tree_evaluator::get_symbol_info () - { - return m_call_stack.get_symbol_info (); - } - - symbol_info_list - tree_evaluator::top_scope_symbol_info () const - { - return m_call_stack.top_scope_symbol_info (); - } - - octave_map tree_evaluator::get_autoload_map () const - { - Cell fcn_names (dim_vector (m_autoload_map.size (), 1)); - Cell file_names (dim_vector (m_autoload_map.size (), 1)); - - octave_idx_type i = 0; - for (const auto& fcn_fname : m_autoload_map) - { - fcn_names(i) = fcn_fname.first; - file_names(i) = fcn_fname.second; - - i++; - } - - octave_map m; - - m.assign ("function", fcn_names); - m.assign ("file", file_names); - - return m; - } - - std::string tree_evaluator::lookup_autoload (const std::string& nm) const - { - std::string retval; - - auto p = m_autoload_map.find (nm); - - if (p != m_autoload_map.end ()) - { - load_path& lp = m_interpreter.get_load_path (); - - retval = lp.find_file (p->second); - } - - return retval; - } - - std::list<std::string> tree_evaluator::autoloaded_functions () const - { - std::list<std::string> names; - - for (const auto& fcn_fname : m_autoload_map) - names.push_back (fcn_fname.first); - - return names; - } - - std::list<std::string> - tree_evaluator::reverse_lookup_autoload (const std::string& nm) const - { - std::list<std::string> names; - - for (const auto& fcn_fname : m_autoload_map) - if (nm == fcn_fname.second) - names.push_back (fcn_fname.first); - - return names; - } - - void tree_evaluator::add_autoload (const std::string& fcn, - const std::string& nm) - { - std::string file_name = check_autoload_file (nm); - - m_autoload_map[fcn] = file_name; - } - - void tree_evaluator::remove_autoload (const std::string& fcn, - const std::string& nm) - { - check_autoload_file (nm); - - // Remove function from symbol table and autoload map. - symbol_table& symtab = m_interpreter.get_symbol_table (); - - symtab.clear_dld_function (fcn); - - m_autoload_map.erase (fcn); - } - - octave_value - tree_evaluator::whos_line_format (const octave_value_list& args, int nargout) - { - return set_internal_variable (m_whos_line_format, args, nargout, - "whos_line_format"); - } - - octave_value - tree_evaluator::silent_functions (const octave_value_list& args, int nargout) - { - return set_internal_variable (m_silent_functions, args, nargout, - "silent_functions"); - } - - octave_value - tree_evaluator::string_fill_char (const octave_value_list& args, int nargout) - { - return set_internal_variable (m_string_fill_char, args, nargout, - "string_fill_char"); - } - - // Final step of processing an indexing error. Add the name of the - // variable being indexed, if any, then issue an error. (Will this also - // be needed by pt-lvalue, which calls subsref?) - - void tree_evaluator::final_index_error (index_exception& ie, - const tree_expression *expr) - { - std::string extra_message; - - if (is_variable (expr)) - { - std::string var = expr->name (); - - ie.set_var (var); - - symbol_table& symtab = m_interpreter.get_symbol_table (); - - octave_value fcn = symtab.find_function (var); - - if (fcn.is_function ()) - { - octave_function *fp = fcn.function_value (); - - if (fp && fp->name () == var) - extra_message - = " (note: variable '" + var + "' shadows function)"; - } - } - - std::string msg = ie.message () + extra_message; - - error_with_id (ie.err_id (), "%s", msg.c_str ()); - } - - octave_value - tree_evaluator::do_who (int argc, const string_vector& argv, - bool return_list, bool verbose) - { - return m_call_stack.do_who (argc, argv, return_list, verbose); - } - - octave_value_list - tree_evaluator::make_value_list (tree_argument_list *args, - const string_vector& arg_nm) - { - octave_value_list retval; - - if (args) - { - unwind_protect_var<const std::list<octave_lvalue> *> - upv (m_lvalue_list, nullptr); - - int len = args->length (); - - unwind_protect_var<int> upv2 (m_index_position); - unwind_protect_var<int> upv3 (m_num_indices); - - m_num_indices = len; - - std::list<octave_value> arg_vals; - - int k = 0; - - for (auto elt : *args) - { - // FIXME: is it possible for elt to be invalid? - - if (! elt) - break; - - m_index_position = k++; - - octave_value tmp = elt->evaluate (*this); - - if (tmp.is_cs_list ()) - { - octave_value_list tmp_ovl = tmp.list_value (); - - for (octave_idx_type i = 0; i < tmp_ovl.length (); i++) - arg_vals.push_back (tmp_ovl(i)); - } - else if (tmp.is_defined ()) - arg_vals.push_back (tmp); - } - - retval = octave_value_list (arg_vals); - } - - octave_idx_type n = retval.length (); - - if (n > 0) - retval.stash_name_tags (arg_nm); - - return retval; - } - - std::list<octave_lvalue> - tree_evaluator::make_lvalue_list (tree_argument_list *lhs) - { - std::list<octave_lvalue> retval; - - for (tree_expression *elt : *lhs) - retval.push_back (elt->lvalue (*this)); - - return retval; - } - - void - tree_evaluator::push_echo_state (int type, const std::string& file_name, - int pos) - { - unwind_protect *frame = m_call_stack.curr_fcn_unwind_protect_frame (); - - if (frame) - { - push_echo_state_cleanup (*frame); - - set_echo_state (type, file_name, pos); - } - } - - void - tree_evaluator::set_echo_state (int type, const std::string& file_name, - int pos) - { - m_echo_state = echo_this_file (file_name, type); - m_echo_file_name = file_name; - m_echo_file_pos = pos; - } - - void - tree_evaluator::uwp_set_echo_state (bool state, const std::string& file_name, - int pos) - { - m_echo_state = state; - m_echo_file_name = file_name; - m_echo_file_pos = pos; - } - - void - tree_evaluator::maybe_set_echo_state () - { - octave_function *caller = caller_function (); - - if (caller && caller->is_user_code ()) - { - octave_user_code *fcn = dynamic_cast<octave_user_code *> (caller); - - int type = fcn->is_user_function () ? ECHO_FUNCTIONS : ECHO_SCRIPTS; - - std::string file_name = fcn->fcn_file_name (); - - // We want the line where "echo" was called, not the line number - // stored in the stack frame that was created for the echo - // function (that will always be -1). - - int pos = m_call_stack.current_user_code_line (); - - if (pos < 0) - pos = 1; - - set_echo_state (type, file_name, pos); - } - } - - void - tree_evaluator::push_echo_state_cleanup (unwind_protect& frame) - { - frame.add (&tree_evaluator::uwp_set_echo_state, this, - m_echo_state, m_echo_file_name, m_echo_file_pos); - } - - bool tree_evaluator::maybe_push_echo_state_cleanup () - { - // This function is expected to be called from ECHO, which would be - // the top of the call stack. If the caller of ECHO is a - // user-defined function or script, then set up unwind-protect - // elements to restore echo state. - - unwind_protect *frame = m_call_stack.curr_fcn_unwind_protect_frame (); - - if (frame) - { - push_echo_state_cleanup (*frame); - return true; - } - - return false; - } - - - octave_value - tree_evaluator::echo (const octave_value_list& args, int) - { - bool cleanup_pushed = maybe_push_echo_state_cleanup (); - - string_vector argv = args.make_argv (); - - switch (args.length ()) - { - case 0: - if ((m_echo & ECHO_SCRIPTS) || (m_echo & ECHO_FUNCTIONS)) - { - m_echo = ECHO_OFF; - m_echo_files.clear (); - } - else - m_echo = ECHO_SCRIPTS; - break; - - case 1: + break; + + default: + print_usage (); + break; + } + + if (cleanup_pushed) + maybe_set_echo_state (); + + return octave_value (); +} + +bool tree_evaluator::in_debug_repl () const +{ + return (m_debugger_stack.empty () + ? false : m_debugger_stack.top()->in_debug_repl ()); +} + +void tree_evaluator::dbcont () +{ + if (! m_debugger_stack.empty ()) + m_debugger_stack.top()->dbcont (); +} + +void tree_evaluator::dbquit (bool all) +{ + if (! m_debugger_stack.empty ()) + m_debugger_stack.top()->dbquit (all); +} + +static octave_value end_value (const octave_value& value, + octave_idx_type index_position, + octave_idx_type num_indices) +{ + dim_vector dv = value.dims (); + int ndims = dv.ndims (); + + if (num_indices < ndims) + { + for (int i = num_indices; i < ndims; i++) + dv(num_indices-1) *= dv(i); + + if (num_indices == 1) + { + ndims = 2; + dv.resize (ndims); + dv(1) = 1; + } + else + { + ndims = num_indices; + dv.resize (ndims); + } + } + + return (index_position < ndims + ? octave_value (dv(index_position)) : octave_value (1.0)); +} + +octave_value_list +tree_evaluator::evaluate_end_expression (const octave_value_list& args) +{ + int nargin = args.length (); + + if (nargin != 0 && nargin != 3) + print_usage (); + + if (nargin == 3) + { + octave_idx_type index_position + = args(1).xidx_type_value ("end: K must be integer value"); + + if (index_position < 1) + error ("end: K must be greater than zero"); + + octave_idx_type num_indices + = args(2).xidx_type_value ("end: N must be integer value"); + + if (num_indices < 1) + error ("end: N must be greater than zero"); + + return end_value (args(0), index_position-1, num_indices); + } + + // If m_indexed_object is undefined, then this use of 'end' is + // either appearing in a function call argument list or in an + // attempt to index an undefined symbol. There seems to be no + // reasonable way to provide a better error message. So just fail + // with an invalid use message. See bug #58830. + + if (m_indexed_object.is_undefined ()) + error ("invalid use of 'end': may only be used to index existing value"); + + octave_value expr_result; + + if (m_index_list.empty ()) + expr_result = m_indexed_object; + else + { + try + { + // When evaluating "end" with no arguments, we should have + // been called from the built-in Fend function that appears + // in the context of an argument list. Fend will be + // evaluated in its own stack frame. But we need to + // evaluate the partial expression that the special "end" + // token applies to in the calling stack frame. + + unwind_action act ([=] (std::size_t frm) + { + m_call_stack.restore_frame (frm); + }, m_call_stack.current_frame ()); + + std::size_t n = m_call_stack.find_current_user_frame (); + m_call_stack.goto_frame (n); + + // End is only valid inside argument lists used for + // indexing. The dispatch class is set by the function that + // evaluates the argument list. + + // Silently ignore extra output values. + + octave_value_list tmp + = m_indexed_object.subsref (m_index_type, m_index_list, 1); + + expr_result = tmp.length () ? tmp(0) : octave_value (); + + if (expr_result.is_cs_list ()) + err_indexed_cs_list (); + } + catch (const index_exception&) { - std::string arg0 = argv[0]; - - if (arg0 == "on") - m_echo = ECHO_SCRIPTS; - else if (arg0 == "off") - m_echo = ECHO_OFF; - else + error ("error evaluating partial expression for END"); + } + } + + if (expr_result.isobject ()) + { + // FIXME: is there a better way to lookup and execute a method + // that handles all the details like setting the dispatch class + // appropriately? + + std::string dispatch_class = expr_result.class_name (); + + symbol_table& symtab = m_interpreter.get_symbol_table (); + + octave_value meth = symtab.find_method ("end", dispatch_class); + + if (meth.is_defined ()) + return m_interpreter.feval + (meth, ovl (expr_result, m_index_position+1, m_num_indices), 1); + } + + return end_value (expr_result, m_index_position, m_num_indices); +} + +octave_value +tree_evaluator::PS4 (const octave_value_list& args, int nargout) +{ + return set_internal_variable (m_PS4, args, nargout, "PS4"); +} + +bool tree_evaluator::echo_this_file (const std::string& file, int type) const +{ + if ((type & m_echo) == ECHO_SCRIPTS) + { + // Asking about scripts and echo is enabled for them. + return true; + } + + if ((type & m_echo) == ECHO_FUNCTIONS) + { + // Asking about functions and echo is enabled for functions. + // Now, which ones? + + auto p = m_echo_files.find (file); + + if (m_echo & ECHO_ALL) + { + // Return true ulness echo was turned off for a specific + // file. + + return (p == m_echo_files.end () || p->second); + } + else + { + // Return true if echo is specifically enabled for this file. + + return p != m_echo_files.end () && p->second; + } + } + + return false; +} + +void tree_evaluator::echo_code (int line) +{ + std::string prefix = command_editor::decode_prompt_string (m_PS4); + + octave_function *curr_fcn = m_call_stack.current_function (); + + if (curr_fcn && curr_fcn->is_user_code ()) + { + octave_user_code *code = dynamic_cast<octave_user_code *> (curr_fcn); + + int num_lines = line - m_echo_file_pos + 1; + + std::deque<std::string> lines + = code->get_code_lines (m_echo_file_pos, num_lines); + + for (auto& elt : lines) + octave_stdout << prefix << elt << std::endl; + } +} + +// Decide if it's time to quit a for or while loop. +bool tree_evaluator::quit_loop_now () +{ + octave_quit (); + + // Maybe handle 'continue N' someday... + + if (m_continuing) + m_continuing--; + + bool quit = (m_returning || m_breaking || m_continuing); + + if (m_breaking) + m_breaking--; + + return quit; +} + +void tree_evaluator::bind_auto_fcn_vars (const string_vector& arg_names, + const Matrix& ignored_outputs, + int nargin, int nargout, + bool takes_varargs, + const octave_value_list& va_args) +{ + set_auto_fcn_var (stack_frame::ARG_NAMES, Cell (arg_names)); + set_auto_fcn_var (stack_frame::IGNORED, ignored_outputs); + set_auto_fcn_var (stack_frame::NARGIN, nargin); + set_auto_fcn_var (stack_frame::NARGOUT, nargout); + set_auto_fcn_var (stack_frame::SAVED_WARNING_STATES, octave_value ()); + + if (takes_varargs) + assign ("varargin", va_args.cell_value ()); +} + +std::string +tree_evaluator::check_autoload_file (const std::string& nm) const +{ + if (sys::env::absolute_pathname (nm)) + return nm; + + std::string full_name = nm; + + octave_user_code *fcn = m_call_stack.current_user_code (); + + bool found = false; + + if (fcn) + { + std::string fname = fcn->fcn_file_name (); + + if (! fname.empty ()) + { + fname = sys::env::make_absolute (fname); + fname = fname.substr (0, fname.find_last_of (sys::file_ops::dir_sep_str ()) + 1); + + sys::file_stat fs (fname + nm); + + if (fs.exists ()) { - std::string file = fcn_file_in_path (arg0); - file = sys::env::make_absolute (file); - - if (file.empty ()) - error ("echo: no such file %s", arg0.c_str ()); - - if (m_echo & ECHO_ALL) - { - // Echo is enabled for all functions, so turn it off - // for this one. - - m_echo_files[file] = false; - } - else - { - // Echo may be enabled for specific functions. - - auto p = m_echo_files.find (file); - - if (p == m_echo_files.end ()) - { - // Not this one, so enable it. - - m_echo |= ECHO_FUNCTIONS; - m_echo_files[file] = true; - } - else - { - // This one is already in the list. Flip the - // status for it. - - p->second = ! p->second; - } - } + full_name = fname + nm; + found = true; } } - break; - - case 2: - { - std::string arg0 = argv[0]; - std::string arg1 = argv[1]; - - if (arg1 == "on" || arg1 == "off") - std::swap (arg0, arg1); - - if (arg0 == "on") - { - if (arg1 == "all") - { - m_echo = (ECHO_SCRIPTS | ECHO_FUNCTIONS | ECHO_ALL); - m_echo_files.clear (); - } - else - { - std::string file = fcn_file_in_path (arg1); - file = sys::env::make_absolute (file); - - if (file.empty ()) - error ("echo: no such file %s", arg1.c_str ()); - - m_echo |= ECHO_FUNCTIONS; - m_echo_files[file] = true; - } - } - else if (arg0 == "off") - { - if (arg1 == "all") - { - m_echo = ECHO_OFF; - m_echo_files.clear (); - } - else - { - std::string file = fcn_file_in_path (arg1); - file = sys::env::make_absolute (file); - - if (file.empty ()) - error ("echo: no such file %s", arg1.c_str ()); - - m_echo_files[file] = false; - } - } - else - print_usage (); - } - break; - - default: - print_usage (); - break; - } - - if (cleanup_pushed) - maybe_set_echo_state (); - - return octave_value (); - } - - bool tree_evaluator::in_debug_repl () const - { - return (m_debugger_stack.empty () - ? false : m_debugger_stack.top()->in_debug_repl ()); - } - - void tree_evaluator::dbcont () - { - if (! m_debugger_stack.empty ()) - m_debugger_stack.top()->dbcont (); - } - - void tree_evaluator::dbquit (bool all) - { - if (! m_debugger_stack.empty ()) - m_debugger_stack.top()->dbquit (all); - } - - static octave_value end_value (const octave_value& value, - octave_idx_type index_position, - octave_idx_type num_indices) - { - dim_vector dv = value.dims (); - int ndims = dv.ndims (); - - if (num_indices < ndims) - { - for (int i = num_indices; i < ndims; i++) - dv(num_indices-1) *= dv(i); - - if (num_indices == 1) - { - ndims = 2; - dv.resize (ndims); - dv(1) = 1; - } - else - { - ndims = num_indices; - dv.resize (ndims); - } - } - - return (index_position < ndims - ? octave_value (dv(index_position)) : octave_value (1.0)); - } - - octave_value_list - tree_evaluator::evaluate_end_expression (const octave_value_list& args) - { - int nargin = args.length (); - - if (nargin != 0 && nargin != 3) - print_usage (); - - if (nargin == 3) - { - octave_idx_type index_position - = args(1).xidx_type_value ("end: K must be integer value"); - - if (index_position < 1) - error ("end: K must be greater than zero"); - - octave_idx_type num_indices - = args(2).xidx_type_value ("end: N must be integer value"); - - if (num_indices < 1) - error ("end: N must be greater than zero"); - - return end_value (args(0), index_position-1, num_indices); - } - - // If m_indexed_object is undefined, then this use of 'end' is - // either appearing in a function call argument list or in an - // attempt to index an undefined symbol. There seems to be no - // reasonable way to provide a better error message. So just fail - // with an invalid use message. See bug #58830. - - if (m_indexed_object.is_undefined ()) - error ("invalid use of 'end': may only be used to index existing value"); - - octave_value expr_result; - - if (m_index_list.empty ()) - expr_result = m_indexed_object; - else - { - try - { - // When evaluating "end" with no arguments, we should have - // been called from the built-in Fend function that appears - // in the context of an argument list. Fend will be - // evaluated in its own stack frame. But we need to - // evaluate the partial expression that the special "end" - // token applies to in the calling stack frame. - - unwind_action act ([=] (std::size_t frm) - { - m_call_stack.restore_frame (frm); - }, m_call_stack.current_frame ()); - - std::size_t n = m_call_stack.find_current_user_frame (); - m_call_stack.goto_frame (n); - - // End is only valid inside argument lists used for - // indexing. The dispatch class is set by the function that - // evaluates the argument list. - - // Silently ignore extra output values. - - octave_value_list tmp - = m_indexed_object.subsref (m_index_type, m_index_list, 1); - - expr_result = tmp.length () ? tmp(0) : octave_value (); - - if (expr_result.is_cs_list ()) - err_indexed_cs_list (); - } - catch (const index_exception&) - { - error ("error evaluating partial expression for END"); - } - } - - if (expr_result.isobject ()) - { - // FIXME: is there a better way to lookup and execute a method - // that handles all the details like setting the dispatch class - // appropriately? - - std::string dispatch_class = expr_result.class_name (); - - symbol_table& symtab = m_interpreter.get_symbol_table (); - - octave_value meth = symtab.find_method ("end", dispatch_class); - - if (meth.is_defined ()) - return m_interpreter.feval - (meth, ovl (expr_result, m_index_position+1, m_num_indices), 1); - } - - return end_value (expr_result, m_index_position, m_num_indices); - } - - octave_value - tree_evaluator::PS4 (const octave_value_list& args, int nargout) - { - return set_internal_variable (m_PS4, args, nargout, "PS4"); - } - - bool tree_evaluator::echo_this_file (const std::string& file, int type) const - { - if ((type & m_echo) == ECHO_SCRIPTS) - { - // Asking about scripts and echo is enabled for them. - return true; - } - - if ((type & m_echo) == ECHO_FUNCTIONS) - { - // Asking about functions and echo is enabled for functions. - // Now, which ones? - - auto p = m_echo_files.find (file); - - if (m_echo & ECHO_ALL) - { - // Return true ulness echo was turned off for a specific - // file. - - return (p == m_echo_files.end () || p->second); - } - else - { - // Return true if echo is specifically enabled for this file. - - return p != m_echo_files.end () && p->second; - } - } - - return false; - } - - void tree_evaluator::echo_code (int line) - { - std::string prefix = command_editor::decode_prompt_string (m_PS4); - - octave_function *curr_fcn = m_call_stack.current_function (); - - if (curr_fcn && curr_fcn->is_user_code ()) - { - octave_user_code *code = dynamic_cast<octave_user_code *> (curr_fcn); - - int num_lines = line - m_echo_file_pos + 1; - - std::deque<std::string> lines - = code->get_code_lines (m_echo_file_pos, num_lines); - - for (auto& elt : lines) - octave_stdout << prefix << elt << std::endl; - } - } - - // Decide if it's time to quit a for or while loop. - bool tree_evaluator::quit_loop_now () - { - octave_quit (); - - // Maybe handle 'continue N' someday... - - if (m_continuing) - m_continuing--; - - bool quit = (m_returning || m_breaking || m_continuing); - - if (m_breaking) - m_breaking--; - - return quit; - } - - void tree_evaluator::bind_auto_fcn_vars (const string_vector& arg_names, - const Matrix& ignored_outputs, - int nargin, int nargout, - bool takes_varargs, - const octave_value_list& va_args) - { - set_auto_fcn_var (stack_frame::ARG_NAMES, Cell (arg_names)); - set_auto_fcn_var (stack_frame::IGNORED, ignored_outputs); - set_auto_fcn_var (stack_frame::NARGIN, nargin); - set_auto_fcn_var (stack_frame::NARGOUT, nargout); - set_auto_fcn_var (stack_frame::SAVED_WARNING_STATES, octave_value ()); - - if (takes_varargs) - assign ("varargin", va_args.cell_value ()); - } - - std::string - tree_evaluator::check_autoload_file (const std::string& nm) const - { - if (sys::env::absolute_pathname (nm)) - return nm; - - std::string full_name = nm; - - octave_user_code *fcn = m_call_stack.current_user_code (); - - bool found = false; - - if (fcn) - { - std::string fname = fcn->fcn_file_name (); - - if (! fname.empty ()) - { - fname = sys::env::make_absolute (fname); - fname = fname.substr (0, fname.find_last_of (sys::file_ops::dir_sep_str ()) + 1); - - sys::file_stat fs (fname + nm); - - if (fs.exists ()) - { - full_name = fname + nm; - found = true; - } - } - } - - if (! found) - warning_with_id ("Octave:autoload-relative-file-name", - "autoload: '%s' is not an absolute filename", - nm.c_str ()); - - return full_name; - } + } + + if (! found) + warning_with_id ("Octave:autoload-relative-file-name", + "autoload: '%s' is not an absolute filename", + nm.c_str ()); + + return full_name; +} DEFMETHOD (max_recursion_depth, interp, args, nargout, - doc: /* -*- texinfo -*- + doc: /* -*- texinfo -*- @deftypefn {} {@var{val} =} max_recursion_depth () @deftypefnx {} {@var{old_val} =} max_recursion_depth (@var{new_val}) @deftypefnx {} {@var{old_val} =} max_recursion_depth (@var{new_val}, "local") @@ -5272,9 +5272,9 @@ @seealso{max_stack_depth} @end deftypefn */) { - tree_evaluator& tw = interp.get_evaluator (); - - return tw.max_recursion_depth (args, nargout); +tree_evaluator& tw = interp.get_evaluator (); + +return tw.max_recursion_depth (args, nargout); } /* @@ -5290,7 +5290,7 @@ */ DEFMETHOD (whos_line_format, interp, args, nargout, - doc: /* -*- texinfo -*- + doc: /* -*- texinfo -*- @deftypefn {} {@var{val} =} whos_line_format () @deftypefnx {} {@var{old_val} =} whos_line_format (@var{new_val}) @deftypefnx {} {@var{old_val} =} whos_line_format (@var{new_val}, "local") @@ -5365,13 +5365,13 @@ @seealso{whos} @end deftypefn */) { - tree_evaluator& tw = interp.get_evaluator (); - - return tw.whos_line_format (args, nargout); +tree_evaluator& tw = interp.get_evaluator (); + +return tw.whos_line_format (args, nargout); } DEFMETHOD (silent_functions, interp, args, nargout, - doc: /* -*- texinfo -*- + doc: /* -*- texinfo -*- @deftypefn {} {@var{val} =} silent_functions () @deftypefnx {} {@var{old_val} =} silent_functions (@var{new_val}) @deftypefnx {} {@var{old_val} =} silent_functions (@var{new_val}, "local") @@ -5387,9 +5387,9 @@ The original variable value is restored when exiting the function. @end deftypefn */) { - tree_evaluator& tw = interp.get_evaluator (); - - return tw.silent_functions (args, nargout); +tree_evaluator& tw = interp.get_evaluator (); + +return tw.silent_functions (args, nargout); } /* @@ -5405,7 +5405,7 @@ */ DEFMETHOD (string_fill_char, interp, args, nargout, - doc: /* -*- texinfo -*- + doc: /* -*- texinfo -*- @deftypefn {} {@var{val} =} string_fill_char () @deftypefnx {} {@var{old_val} =} string_fill_char (@var{new_val}) @deftypefnx {} {@var{old_val} =} string_fill_char (@var{new_val}, "local") @@ -5419,9 +5419,9 @@ @group string_fill_char ("X"); [ "these"; "are"; "strings" ] - @result{} "theseXX" - "areXXXX" - "strings" + @result{} "theseXX" + "areXXXX" + "strings" @end group @end example @@ -5430,9 +5430,9 @@ The original variable value is restored when exiting the function. @end deftypefn */) { - tree_evaluator& tw = interp.get_evaluator (); - - return tw.string_fill_char (args, nargout); +tree_evaluator& tw = interp.get_evaluator (); + +return tw.string_fill_char (args, nargout); } /* @@ -5454,7 +5454,7 @@ */ DEFMETHOD (PS4, interp, args, nargout, - doc: /* -*- texinfo -*- + doc: /* -*- texinfo -*- @deftypefn {} {@var{val} =} PS4 () @deftypefnx {} {@var{old_val} =} PS4 (@var{new_val}) @deftypefnx {} {@var{old_val} =} PS4 (@var{new_val}, "local") @@ -5470,11 +5470,11 @@ @seealso{echo, PS1, PS2} @end deftypefn */) { - return interp.PS4 (args, nargout); +return interp.PS4 (args, nargout); } DEFMETHOD (echo, interp, args, nargout, - doc: /* -*- texinfo -*- + doc: /* -*- texinfo -*- @deftypefn {} {} echo @deftypefnx {} {} echo on @deftypefnx {} {} echo off @@ -5514,9 +5514,9 @@ @seealso{PS4} @end deftypefn */) { - tree_evaluator& tw = interp.get_evaluator (); - - return tw.echo (args, nargout); +tree_evaluator& tw = interp.get_evaluator (); + +return tw.echo (args, nargout); } /*