view libinterp/corefcn/call-stack.cc @ 28430:5bfa8e018704 stable

store local init vars for anonymous functions in handle, not function object This change is step toward revamping function handles by storing variable init values for anonymous functions in function handle objects instead of in the corresponding functions. * call-stack.h, call-stack.cc (call_stack::push): New overload that accepts local variable map in addition to function object. * stack-frame.h (user_fcn_stack_frame::user_fcn_stack_frame): New constructor that accepts local variable map in addition to function object. (stack_frame::local_vars_map): New typedef. * ov-fcn-handle.h, ov-fcn-handle.cc (octave_fcn_handle::m_local_vars): New data member. (octave_fcn_handle::octave_fcn_handle): Update existing constructors and provide new one to construct handle from function object and local variable map. (octave_fcn_handle::call): If m_local_vars is defined, push stack frame with that info and execute function here. (octave_fcn_handle::workspace): Create workspace struct from m_local_vars instead of getting that info from the function object. (octave_fcn_handle::parse_anon_fcn_handle): Copy m_local_vars from new function handle object. (octave_fcn_handle::save_ascii, octave_fcn_handle::save_binary, octave_fcn_handle::save_hdf5): Use m_local_vars instead of getting info from function object. * ov-usr-fcn.h, ov-usr-fcn.cc (octave_user_function::local_vars_map): Delete typedef. (octave_user_function::m_local_var_init_vals): Delete data member and all uses. (octave_user_function::local_var_init_vals): Delete. * pt-eval.h, pt-eval.cc (tree_evaluator::push_stack_frame): New overload that accepts local variable map and user function. (tree_evaluator::init_local_fcn_vars): Delete function and all uses. * pt-fcn-handle.cc (tree_anon_fcn_handle::evaluate): Store local variables in function handle object instead of function object.
author John W. Eaton <jwe@octave.org>
date Mon, 30 Mar 2020 15:14:10 -0400
parents 8eb8ba8aff9a
children 2893db10b8d2
line wrap: on
line source

////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 1995-2020 The Octave Project Developers
//
// See the file COPYRIGHT.md in the top-level directory of this
// distribution or <https://octave.org/copyright/>.
//
// This file is part of Octave.
//
// Octave is free software: you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Octave is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Octave; see the file COPYING.  If not, see
// <https://www.gnu.org/licenses/>.
//
////////////////////////////////////////////////////////////////////////

#if defined (HAVE_CONFIG_H)
#  include "config.h"
#endif

#include <iostream>

#include "lo-regexp.h"
#include "str-vec.h"

#include "call-stack.h"
#include "defun.h"
#include "interpreter.h"
#include "interpreter-private.h"
#include "oct-map.h"
#include "ov.h"
#include "ov-fcn.h"
#include "ov-fcn-handle.h"
#include "ov-usr-fcn.h"
#include "pager.h"
#include "parse.h"
#include "stack-frame.h"
#include "syminfo.h"
#include "symrec.h"
#include "symscope.h"
#include "variables.h"

namespace octave
{
  // Use static fields for the best efficiency.
  // NOTE: C++0x will allow these two to be merged into one.
  static const char *bt_fieldnames[] =
    { "file", "name", "line", "column", nullptr };

  static const octave_fields bt_fields (bt_fieldnames);

  call_stack::call_stack (tree_evaluator& evaluator)
    : m_evaluator (evaluator), m_cs (), m_curr_frame (0),
      m_max_stack_depth (1024), m_global_values ()
  {
    push (symbol_scope ("top scope"));
  }

  octave_function * call_stack::current_function (bool skip_first) const
  {
    if (m_cs.empty ())
      error ("current_function: call stack is empty");

    octave_function *fcn = nullptr;

    size_t idx = size ();

    if (idx > 1 && skip_first)
      --idx;

    while (--idx)
      {
        fcn = m_cs[idx]->function ();

        if (fcn)
          break;
      }

    return fcn;
  }

  int call_stack::current_line (void) const
  {
    int retval = -1;

    if (! m_cs.empty ())
      {
        const std::shared_ptr<stack_frame> elt = m_cs[m_curr_frame];
        retval = elt->line ();
      }

    return retval;
  }

  int call_stack::current_column (void) const
  {
    int retval = -1;

    if (! m_cs.empty ())
      {
        const std::shared_ptr<stack_frame> elt = m_cs[m_curr_frame];
        retval = elt->column ();
      }

    return retval;
  }

  octave_user_code * call_stack::current_user_code (void) const
  {
    // Start at current frame.

    size_t xframe = find_current_user_frame ();

    if (xframe > 0)
      {
        const std::shared_ptr<stack_frame> elt = m_cs[xframe];

        octave_function *f = elt->function ();

        if (f && f->is_user_code ())
          return dynamic_cast<octave_user_code *> (f);
      }

    return nullptr;
  }

  int call_stack::current_user_code_line (void) const
  {
    // Start at current frame.

    size_t xframe = find_current_user_frame ();

    if (xframe > 0)
      {
        const std::shared_ptr<stack_frame> elt = m_cs[xframe];

        octave_function *f = elt->function ();

        if (f && f->is_user_code ())
          {
            int line = elt->line ();

            if (line > 0)
              return line;
          }
      }

    return -1;
  }

  int call_stack::current_user_code_column (void) const
  {
    // Start at current frame.

    size_t xframe = find_current_user_frame ();

    if (xframe > 0)
      {
        const std::shared_ptr<stack_frame> elt = m_cs[xframe];

        octave_function *f = elt->function ();

        if (f && f->is_user_code ())
          {
            int column = elt->column ();

            if (column > 0)
              return column;
          }
      }

    return -1;
  }

  unwind_protect * call_stack::curr_fcn_unwind_protect_frame (void)
  {
    // Start at current frame.

    size_t xframe = find_current_user_frame ();

    if (xframe > 0)
      {
        const std::shared_ptr<stack_frame> elt = m_cs[xframe];

        octave_function *f = elt->function ();

        if (f && f->is_user_code ())
          return elt->unwind_protect_frame ();
      }

    return nullptr;
  }

  octave_user_code * call_stack::debug_user_code (void) const
  {
    octave_user_code *retval = nullptr;

    // This should never happen...
    if (m_curr_frame == 0)
      return retval;

    size_t i = m_curr_frame;

    while (i != 0)
      {
        const std::shared_ptr<stack_frame> elt = m_cs[i--];

        octave_function *f = elt->function ();

        if (f && f->is_user_code ())
          {
            retval = dynamic_cast<octave_user_code *> (f);
            break;
          }
      }

    return retval;
  }

  int call_stack::debug_user_code_line (void) const
  {
    int retval = -1;

    // This should never happen...
    if (m_curr_frame == 0)
      return retval;

    size_t i = m_curr_frame;

    while (i != 0)
      {
        const std::shared_ptr<stack_frame> elt = m_cs[i--];

        octave_function *f = elt->function ();

        if (f && f->is_user_code ())
          {
            if (elt->line ())
              {
                retval = elt->line ();
                break;
              }
          }
      }

    return retval;
  }

  int call_stack::debug_user_code_column (void) const
  {
    int retval = -1;

    // This should never happen...
    if (m_curr_frame == 0)
      return retval;

    // Start looking with the caller of the calling debug function.
    size_t i = m_curr_frame;

    while (i != 0)
      {
        const std::shared_ptr<stack_frame> elt = m_cs[i--];

        octave_function *f = elt->function ();

        if (f && f->is_user_code ())
          {
            if (elt->column ())
              {
                retval = elt->column ();
                break;
              }
          }
      }

    return retval;
  }

  std::string call_stack::get_dispatch_class (void) const
  {
    return m_cs[m_curr_frame]->get_dispatch_class ();
  }

  void call_stack::set_dispatch_class (const std::string& class_name)
  {
    m_cs[m_curr_frame]->set_dispatch_class (class_name);
  }

  bool call_stack::is_class_method_executing (std::string& dispatch_class) const
  {
    dispatch_class = "";

    octave_function *f = current_function ();

    bool retval = (f && f->is_class_method ());

    if (retval)
      dispatch_class = f->dispatch_class ();

    return retval;
  }

  bool call_stack::is_class_constructor_executing (std::string& dispatch_class) const
  {
    dispatch_class = "";

    octave_function *f = current_function ();

    bool retval = (f && f->is_class_constructor ());

    if (retval)
      dispatch_class = f->dispatch_class ();

    return retval;
  }

  bool call_stack::all_scripts (void) const
  {
    bool retval = true;

    auto p = m_cs.cend ();

    while (p != m_cs.cbegin ())
      {
        const std::shared_ptr<stack_frame> elt = *(--p);

        octave_function *f = elt->function ();

        if (f && ! f->is_user_script ())
          {
            retval = false;
            break;
          }
      }

    return retval;
  }

  std::shared_ptr<stack_frame>
  call_stack::get_static_link (size_t prev_frame) const
  {
    // FIXME: is there a better way?

    std::shared_ptr<stack_frame> slink;

    if (m_curr_frame > 0)
      {
        octave_function *t_fcn = m_cs[prev_frame]->function ();

        slink = (t_fcn
                 ? (t_fcn->is_user_code ()
                    ? m_cs[prev_frame] : m_cs[prev_frame]->static_link ())
                 : m_cs[prev_frame]);
      }

    return slink;
  }

  void call_stack::push (const symbol_scope& scope)
  {
    size_t prev_frame = m_curr_frame;
    m_curr_frame = m_cs.size ();

    // m_max_stack_depth should never be less than zero.
    if (m_curr_frame > static_cast<size_t> (m_max_stack_depth))
      error ("max_stack_depth exceeded");

    std::shared_ptr<stack_frame> slink = get_static_link (prev_frame);

    std::shared_ptr<stack_frame>
      new_frame (stack_frame::create (m_evaluator, scope, m_curr_frame, slink));

    m_cs.push_back (new_frame);
  }

  void call_stack::push (octave_user_function *fcn,
                         const std::shared_ptr<stack_frame>& closure_frames)
  {
    size_t prev_frame = m_curr_frame;
    m_curr_frame = m_cs.size ();

    // m_max_stack_depth should never be less than zero.
    if (m_curr_frame > static_cast<size_t> (m_max_stack_depth))
      error ("max_stack_depth exceeded");

    std::shared_ptr<stack_frame> slink = get_static_link (prev_frame);

    std::shared_ptr<stack_frame>
      new_frame (stack_frame::create (m_evaluator, fcn, m_curr_frame, slink,
                                      closure_frames));

    m_cs.push_back (new_frame);
  }

  void call_stack::push (octave_user_function *fcn,
                         const stack_frame::local_vars_map& local_vars)
  {
    size_t prev_frame = m_curr_frame;
    m_curr_frame = m_cs.size ();

    // m_max_stack_depth should never be less than zero.
    if (m_curr_frame > static_cast<size_t> (m_max_stack_depth))
      error ("max_stack_depth exceeded");

    std::shared_ptr<stack_frame> slink = get_static_link (prev_frame);

    std::shared_ptr<stack_frame>
      new_frame (stack_frame::create (m_evaluator, fcn, m_curr_frame, slink,
                                      local_vars));

    m_cs.push_back (new_frame);
  }

  void call_stack::push (octave_user_script *script)
  {
    size_t prev_frame = m_curr_frame;
    m_curr_frame = m_cs.size ();

    // m_max_stack_depth should never be less than zero.
    if (m_curr_frame > static_cast<size_t> (m_max_stack_depth))
      error ("max_stack_depth exceeded");

    std::shared_ptr<stack_frame> slink = get_static_link (prev_frame);

    std::shared_ptr<stack_frame>
      new_frame (stack_frame::create (m_evaluator, script, m_curr_frame,
                                      slink));

    m_cs.push_back (new_frame);
  }

  void call_stack::push (octave_function *fcn)
  {
    size_t prev_frame = m_curr_frame;
    m_curr_frame = m_cs.size ();

    // m_max_stack_depth should never be less than zero.
    if (m_curr_frame > static_cast<size_t> (m_max_stack_depth))
      error ("max_stack_depth exceeded");

    std::shared_ptr<stack_frame> slink = get_static_link (prev_frame);

    std::shared_ptr<stack_frame>
      new_frame (stack_frame::create (m_evaluator, fcn, m_curr_frame, slink));

    m_cs.push_back (new_frame);
  }

  bool call_stack::goto_frame (size_t n, bool verbose)
  {
    bool retval = false;

    if (n < m_cs.size ())
      {
        retval = true;

        m_curr_frame = n;

        if (verbose)
          {
            const std::shared_ptr<stack_frame> elt = m_cs[n];

            elt->display_stopped_in_message (octave_stdout);
          }
      }

    return retval;
  }

  size_t call_stack::find_current_user_frame (void) const
  {
    size_t user_frame = m_curr_frame;

    std::shared_ptr<stack_frame> frm = m_cs[user_frame];

    if (! (frm->is_user_fcn_frame () || frm->is_user_script_frame ()
           || frm->is_scope_frame ()))
      {
        frm = frm->static_link ();

        user_frame = frm->index ();
      }

    return user_frame;
  }

  std::shared_ptr<stack_frame> call_stack::current_user_frame (void) const
  {
    size_t frame = find_current_user_frame ();

    return m_cs[frame];
  }

  // Go to the Nth frame (up if N is negative or down if positive) in
  // the call stack that corresponds to a script, function, or scope
  // beginning with the frame indexed by START.

  size_t call_stack::dbupdown (size_t start, int n, bool verbose)
  {
    if (start >= m_cs.size ())
      error ("invalid stack frame");

    // Can't go up from here.

    if (start == 0 && n < 0)
      {
        if (verbose)
          m_cs[start]->display_stopped_in_message (octave_stdout);

        return start;
      }

    std::shared_ptr<stack_frame> frm = m_cs[start];

    if (! (frm && (frm->is_user_fcn_frame ()
                   || frm->is_user_script_frame ()
                   || frm->is_scope_frame ())))
      error ("call_stack::dbupdown: invalid initial frame in call stack!");

    // Use index into the call stack to begin the search.  At this point
    // we iterate up or down using indexing instead of static links
    // because ... FIXME: it's a bit complicated, but deserves
    // explanation.  May be easiest with some pictures of the call stack
    // for an example or two.

    size_t xframe = frm->index ();

    if (n == 0)
      {
        if (verbose)
          frm->display_stopped_in_message (octave_stdout);

        return xframe;
      }

    int incr = 0;

    if (n < 0)
      {
        incr = -1;
        n = -n;
      }
    else if (n > 0)
      incr = 1;

    size_t last_good_frame = 0;

    while (true)
      {
        frm = m_cs[xframe];

        if (frm->is_user_fcn_frame () || frm->is_user_script_frame ()
            || frm->is_scope_frame ())
          {
            last_good_frame = xframe;

            if (n == 0)
              break;

            n--;
          }

        xframe += incr;

        if (xframe == 0)
          {
            last_good_frame = 0;
            break;
          }

        if (xframe == m_cs.size ())
          break;
      }

    if (verbose)
      m_cs[last_good_frame]->display_stopped_in_message (octave_stdout);

    return last_good_frame;
  }

  // Like dbupdown above but find the starting frame automatically from
  // the current frame.  If the current frame is already a user
  // function, script, or scope frame, use that.  Otherwise, follow
  // the static link for the current frame.  If that is not a user
  // function, script or scope frame then there is an error in the
  // implementation.

  size_t call_stack::dbupdown (int n, bool verbose)
  {
    size_t start = find_current_user_frame ();

    return dbupdown (start, n, verbose);
  }

  // May be used to temporarily change the value ov m_curr_frame inside
  // a function like evalin.  If used in a function like dbup, the new
  // value of m_curr_frame would be wiped out when dbup returns and the
  // stack frame for dbup is popped.

  void call_stack::goto_caller_frame (void)
  {
    size_t start = find_current_user_frame ();

    // FIXME: is this supposed to be an error?
    if (start == 0)
      error ("already at top level");

    m_curr_frame = dbupdown (start, -1, false);
  }

  void call_stack::goto_base_frame (void)
  {
    if (m_curr_frame > 0)
      m_curr_frame = 0;
  }

  std::list<std::shared_ptr<stack_frame>>
  call_stack::backtrace_frames (octave_idx_type& curr_user_frame) const
  {
    std::list<std::shared_ptr<stack_frame>> frames;

    // curr_frame is the index to the current frame in the overall call
    // stack, which includes any compiled function frames and scope
    // frames.  The curr_user_frame value we set is the index into the
    // subset of frames returned in the octave_map object.

    size_t curr_frame = find_current_user_frame ();

    // Don't include top-level stack frame in the list.

    for (size_t n = m_cs.size () - 1; n > 0; n--)
      {
        std::shared_ptr<stack_frame> frm = m_cs[n];

        if (frm->is_user_script_frame () || frm->is_user_fcn_frame ()
            || frm->is_scope_frame ())
          {
            if (frm->index () == curr_frame)
              curr_user_frame = frames.size ();

            frames.push_back (frm);
          }

        if (n == 0)
          break;
      }

    return frames;
  }

  std::list<std::shared_ptr<stack_frame>>
  call_stack::backtrace_frames (void) const
  {
    octave_idx_type curr_user_frame = -1;

    return backtrace_frames (curr_user_frame);
  }

  std::list<frame_info>
  call_stack::backtrace_info (octave_idx_type& curr_user_frame,
                              bool print_subfn) const
  {
    std::list<std::shared_ptr<stack_frame>> frames
      = backtrace_frames (curr_user_frame);

    std::list<frame_info> retval;

    for (const auto& frm : frames)
      {
        if (frm->is_user_script_frame () || frm->is_user_fcn_frame ()
            || frm->is_scope_frame ())
          {
            retval.push_back (frame_info (frm->fcn_file_name (),
                                          frm->fcn_name (print_subfn),
                                          frm->line (), frm->column ()));
          }
      }

    return retval;
  }

  std::list<frame_info> call_stack::backtrace_info (void) const
  {
    octave_idx_type curr_user_frame = -1;

    return backtrace_info (curr_user_frame, true);
  }

  octave_map call_stack::backtrace (octave_idx_type& curr_user_frame,
                                    bool print_subfn) const
  {
    std::list<std::shared_ptr<stack_frame>> frames
      = backtrace_frames (curr_user_frame);

    size_t nframes = frames.size ();

    octave_map retval (dim_vector (nframes, 1), bt_fields);

    Cell& file = retval.contents (0);
    Cell& name = retval.contents (1);
    Cell& line = retval.contents (2);
    Cell& column = retval.contents (3);

    octave_idx_type k = 0;

    for (const auto& frm : frames)
      {
        if (frm->is_user_script_frame () || frm->is_user_fcn_frame ()
            || frm->is_scope_frame ())
          {
            file(k) = frm->fcn_file_name ();
            name(k) = frm->fcn_name (print_subfn);
            line(k) = frm->line ();
            column(k) = frm->column ();

            k++;
          }
      }

    return retval;
  }

  octave_map call_stack::backtrace (void) const
  {
    octave_idx_type curr_user_frame = -1;

    return backtrace (curr_user_frame, true);
  }

  octave_map call_stack::empty_backtrace (void) const
  {
    return octave_map (dim_vector (0, 1), bt_fields);
  }

  void call_stack::pop (void)
  {
    // Never pop top scope.
    // FIXME: is it possible for this case to happen?

    if (m_cs.size () > 1)
      {
        std::shared_ptr<stack_frame> elt = m_cs.back ();

        std::shared_ptr<stack_frame> caller = elt->static_link ();

        m_curr_frame = caller->index ();

        m_cs.pop_back ();
      }
  }

  void call_stack::clear (void)
  {
    while (! m_cs.empty ())
      pop ();
  }

  symbol_info_list call_stack::all_variables (void)
  {
    return m_cs[m_curr_frame]->all_variables ();
  }

  std::list<std::string> call_stack::global_variable_names (void) const
  {
    std::list<std::string> retval;

    for (const auto& nm_ov : m_global_values)
      {
        if (nm_ov.second.is_defined ())
          retval.push_back (nm_ov.first);
      }

    retval.sort ();

    return retval;
  }

  std::list<std::string> call_stack::top_level_variable_names (void) const
  {
    return m_cs[0]->variable_names ();
  }

  std::list<std::string> call_stack::variable_names (void) const
  {
    return m_cs[m_curr_frame]->variable_names ();
  }

  void call_stack::clear_global_variable (const std::string& name)
  {
    auto p = m_global_values.find (name);

    if (p != m_global_values.end ())
      p->second = octave_value ();
  }

  void call_stack::clear_global_variable_pattern (const std::string& pattern)
  {
    glob_match pat (pattern);

    for (auto& nm_ov : m_global_values)
      {
        if (pat.match (nm_ov.first))
          nm_ov.second = octave_value ();
      }
  }

  void call_stack::clear_global_variable_regexp (const std::string& pattern)
  {
    octave::regexp pat (pattern);

    for (auto& nm_ov : m_global_values)
      {
        if (pat.is_match (nm_ov.first))
          nm_ov.second = octave_value ();
      }
  }

  void call_stack::clear_global_variables (void)
  {
    for (auto& nm_ov : m_global_values)
      nm_ov.second = octave_value ();
  }

  symbol_info_list
  call_stack::glob_symbol_info (const std::string& pattern) const
  {
    return m_cs[m_curr_frame]->glob_symbol_info (pattern);
  }

  symbol_info_list
  call_stack::regexp_symbol_info (const std::string& pattern) const
  {
    return m_cs[m_curr_frame]->regexp_symbol_info (pattern);
  }

  symbol_info_list call_stack::get_symbol_info (void)
  {
    return m_cs[m_curr_frame]->get_symbol_info ();
  }

  symbol_info_list call_stack::top_scope_symbol_info (void) const
  {
    return m_cs[0]->get_symbol_info ();
  }

  octave_value call_stack::max_stack_depth (const octave_value_list& args,
                                            int nargout)
  {
    return set_internal_variable (m_max_stack_depth, args, nargout,
                                  "max_stack_depth", 0);
  }

  void call_stack::make_persistent (const symbol_record& sym)
  {
    m_cs[m_curr_frame]->make_persistent (sym);
  }

  void call_stack::make_global (const symbol_record& sym)
  {
    m_cs[m_curr_frame]->make_global (sym);
  }

  octave_value call_stack::global_varval (const std::string& name) const
  {
    auto p = m_global_values.find (name);

    return p == m_global_values.end () ? octave_value () : p->second;
  }

  octave_value& call_stack::global_varref (const std::string& name)
  {
    return m_global_values[name];
  }

  octave_value call_stack::get_top_level_value (const std::string& name) const
  {
    return m_cs[0]->varval (name);
  }

  void call_stack::set_top_level_value (const std::string& name,
                                        const octave_value& value)
  {
    m_cs[0]->assign (name, value);
  }

  octave_value call_stack::do_who (int argc, const string_vector& argv,
                                   bool return_list, bool verbose)
  {
    octave_value retval;

    std::string my_name = argv[0];

    std::string file_name;

    bool from_file = false;
    bool global_only = false;
    bool have_regexp = false;

    int i = 1;
    while (i < argc)
      {
        if (argv[i] == "-file")
          {
            if (from_file)
              error ("%s: -file option may only be specified once",
                     my_name.c_str ());

            from_file = true;

            if (i == argc - 1)
              error ("%s: -file argument must be followed by a filename",
                     my_name.c_str ());

            file_name = argv[++i];
          }
        else if (argv[i] == "-regexp")
          {
            have_regexp = true;
          }
        else if (argv[i] == "global")
          global_only = true;
        else if (argv[i][0] == '-')
          warning ("%s: unrecognized option '%s'", my_name.c_str (),
                   argv[i].c_str ());
        else
          break;

        i++;
      }

    int npatterns = argc - i;
    string_vector patterns;
    if (npatterns > 0)
      {
        patterns.resize (npatterns);
        for (int j = 0; j < npatterns; j++)
          patterns[j] = argv[i+j];
      }
    else
      {
        patterns.resize (1);
        patterns[0] = "*";
      }

    if (from_file)
      {
        // FIXME: This is an inefficient manner to implement this as the
        // variables are loaded in to a temporary context and then treated.
        // It would be better to refactor symbol_info_list to not store the
        // symbol records and then use it in load-save.cc (do_load) to
        // implement this option there so that the variables are never
        // stored at all.

        unwind_protect frame;

        // Set up temporary scope.

        symbol_scope tmp_scope ("$dummy_scope$");

        push (tmp_scope);
        frame.add_method (*this, &call_stack::pop);

        feval ("load", octave_value (file_name), 0);

        std::string newmsg = "Variables in the file " + file_name + ":\n\n";

        if (global_only)
          return do_global_who_two (patterns, have_regexp, return_list,
                                    verbose, newmsg);
        else
          return do_who_two (patterns, have_regexp, return_list, verbose,
                             newmsg);
      }
    else
      {
        if (global_only)
          return do_global_who_two (patterns, have_regexp, return_list,
                                    verbose);
        else
          return do_who_two (patterns, have_regexp, return_list, verbose);
      }
  }

  octave_value call_stack::do_who_two (const string_vector& patterns,
                                       bool have_regexp, bool return_list,
                                       bool verbose, const std::string& msg)
  {
    return m_cs[m_curr_frame]->who (patterns, have_regexp, return_list,
                                    verbose, m_evaluator.whos_line_format (),
                                    msg);
  }

  octave_value call_stack::do_global_who_two (const string_vector& patterns,
                                              bool have_regexp,
                                              bool return_list, bool verbose,
                                              const std::string& msg)
  {
    symbol_info_list symbol_stats;
    std::list<std::string> symbol_names;

    octave_idx_type npatterns = patterns.numel ();

    for (octave_idx_type j = 0; j < npatterns; j++)
      {
        std::string pattern = patterns[j];

        std::list<std::string> tmp;

        if (have_regexp)
          {
            octave::regexp pat (pattern);

            for (auto& nm_ov : m_global_values)
              {
                if (pat.is_match (nm_ov.first))
                  tmp.push_back (nm_ov.first);
              }
          }
        else
          {
            glob_match pat (pattern);

            for (auto& nm_ov : m_global_values)
              {
                if (pat.match (nm_ov.first))
                  tmp.push_back (nm_ov.first);
              }
          }

        for (const auto& nm : tmp)
          {
            octave_value value = m_global_values[nm];

            if (value.is_defined ())
              {
                if (verbose)
                  {
                    bool is_formal = false;
                    bool is_global = true;
                    bool is_persistent = false;

                    symbol_info syminf (nm, value, is_formal, is_global,
                                        is_persistent);

                    symbol_stats.append (syminf);
                  }
                else
                  symbol_names.push_back (nm);
              }
          }
      }

    if (return_list)
      {
        if (verbose)
          {
            std::string caller_fcn_name;
            octave_function *caller_fcn = caller_function ();
            if (caller_fcn)
              caller_fcn_name = caller_fcn->name ();

            return symbol_stats.map_value (caller_fcn_name, 1);
          }
        else
          return Cell (string_vector (symbol_names));
      }
    else if (! (symbol_stats.empty () && symbol_names.empty ()))
      {
        if (msg.empty ())
          octave_stdout << "Global variables:\n\n";
        else
          octave_stdout << msg;

        if (verbose)
          symbol_stats.display (octave_stdout,
                                m_evaluator.whos_line_format ());
        else
          {
            string_vector names (symbol_names);

            names.list_in_columns (octave_stdout);
          }

        octave_stdout << "\n";
      }

    return octave_value ();
  }

  void call_stack::clear_current_frame_values (void)
  {
    m_cs[m_curr_frame]->clear_values ();
  }

  void call_stack::display (void) const
  {
    std::ostream& os = octave_stdout;

    size_t nframes = size ();

    for (size_t i = 0; i < nframes; i++)
      {
        m_cs[i]->display (false);
        if (i < nframes - 1)
          os << std::endl;
      }
  }

  void call_stack::set_auto_fcn_var (stack_frame::auto_var_type avt,
                                     const octave_value& val)
  {
    m_cs[m_curr_frame]->set_auto_fcn_var (avt, val);
  }

  octave_value call_stack::get_auto_fcn_var (stack_frame::auto_var_type avt) const
  {
    return m_cs[m_curr_frame]->get_auto_fcn_var (avt);
  }
}

DEFMETHOD (max_stack_depth, interp, args, nargout,
           doc: /* -*- texinfo -*-
@deftypefn  {} {@var{val} =} max_stack_depth ()
@deftypefnx {} {@var{old_val} =} max_stack_depth (@var{new_val})
@deftypefnx {} {} max_stack_depth (@var{new_val}, "local")
Query or set the internal limit on the number of times a function may
be called recursively.

If the limit is exceeded, an error message is printed and control returns to
the top level.

When called from inside a function with the @qcode{"local"} option, the
variable is changed locally for the function and any subroutines it calls.
The original variable value is restored when exiting the function.

@seealso{max_recursion_depth}
@end deftypefn */)
{
  octave::tree_evaluator& tw = interp.get_evaluator ();

  return tw.max_stack_depth (args, nargout);
}

/*
%!test
%! orig_val = max_stack_depth ();
%! old_val = max_stack_depth (2*orig_val);
%! assert (orig_val, old_val);
%! assert (max_stack_depth (), 2*orig_val);
%! max_stack_depth (orig_val);
%! assert (max_stack_depth (), orig_val);

%!error (max_stack_depth (1, 2))
*/

DEFMETHOD (who, interp, args, nargout,
           doc: /* -*- texinfo -*-
@deftypefn  {} {} who
@deftypefnx {} {} who pattern @dots{}
@deftypefnx {} {} who option pattern @dots{}
@deftypefnx {} {C =} who ("pattern", @dots{})
List currently defined variables matching the given patterns.

Valid pattern syntax is the same as described for the @code{clear} command.
If no patterns are supplied, all variables are listed.

By default, only variables visible in the local scope are displayed.

The following are valid options, but may not be combined.

@table @code
@item global
List variables in the global scope rather than the current scope.

@item -regexp
The patterns are considered to be regular expressions when matching the
variables to display.  The same pattern syntax accepted by the @code{regexp}
function is used.

@item -file
The next argument is treated as a filename.  All variables found within the
specified file are listed.  No patterns are accepted when reading variables
from a file.
@end table

If called as a function, return a cell array of defined variable names
matching the given patterns.
@seealso{whos, isglobal, isvarname, exist, regexp}
@end deftypefn */)
{
  int argc = args.length () + 1;

  string_vector argv = args.make_argv ("who");

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

  return tw.do_who (argc, argv, nargout == 1);
}

/*
%!test
%! avar = magic (4);
%! ftmp = [tempname() ".mat"];
%! save_default_options ("-binary", "local");
%! unwind_protect
%!   save (ftmp, "avar");
%!   vars = whos ("-file", ftmp);
%!   assert (numel (vars), 1);
%!   assert (isstruct (vars));
%!   assert (vars.name, "avar");
%!   assert (vars.size, [4, 4]);
%!   assert (vars.class, "double");
%!   assert (vars.bytes, 128);
%! unwind_protect_cleanup
%!   unlink (ftmp);
%! end_unwind_protect
*/

DEFMETHOD (whos, interp, args, nargout,
           doc: /* -*- texinfo -*-
@deftypefn  {} {} whos
@deftypefnx {} {} whos pattern @dots{}
@deftypefnx {} {} whos option pattern @dots{}
@deftypefnx {} {S =} whos ("pattern", @dots{})
Provide detailed information on currently defined variables matching the
given patterns.

Options and pattern syntax are the same as for the @code{who} command.

Extended information about each variable is summarized in a table with the
following default entries.

@table @asis
@item Attr
Attributes of the listed variable.  Possible attributes are:

@table @asis
@item blank
Variable in local scope

@item @code{c}
Variable of complex type.

@item @code{f}
Formal parameter (function argument).

@item @code{g}
Variable with global scope.

@item @code{p}
Persistent variable.
@end table

@item Name
The name of the variable.

@item Size
The logical size of the variable.  A scalar is 1x1, a vector is
@nospell{1xN} or @nospell{Nx1}, a 2-D matrix is @nospell{MxN}.

@item Bytes
The amount of memory currently used to store the variable.

@item Class
The class of the variable.  Examples include double, single, char, uint16,
cell, and struct.
@end table

The table can be customized to display more or less information through
the function @code{whos_line_format}.

If @code{whos} is called as a function, return a struct array of defined
variable names matching the given patterns.  Fields in the structure
describing each variable are: name, size, bytes, class, global, sparse,
complex, nesting, persistent.
@seealso{who, whos_line_format}
@end deftypefn */)
{
  int argc = args.length () + 1;

  string_vector argv = args.make_argv ("whos");

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

  return tw.do_who (argc, argv, nargout == 1, true);
}