view libinterp/parse-tree/pt-eval.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 d05a4194f1ad
line wrap: on
line source

////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2009-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 <cctype>

#include <iostream>
#include <list>
#include <string>

#include "cmd-edit.h"
#include "file-ops.h"
#include "file-stat.h"
#include "lo-ieee.h"
#include "oct-env.h"

#include "bp-table.h"
#include "call-stack.h"
#include "cdef-manager.h"
#include "defun.h"
#include "error.h"
#include "errwarn.h"
#include "event-manager.h"
#include "input.h"
#include "interpreter-private.h"
#include "interpreter.h"
#include "octave.h"
#include "ov-classdef.h"
#include "ov-fcn-handle.h"
#include "ov-usr-fcn.h"
#include "ov-re-sparse.h"
#include "ov-cx-sparse.h"
#include "parse.h"
#include "profiler.h"
#include "pt-all.h"
#include "pt-anon-scopes.h"
#include "pt-eval.h"
#include "pt-tm-const.h"
#include "stack-frame.h"
#include "symtab.h"
#include "unwind-prot.h"
#include "utils.h"
#include "variables.h"

//FIXME: This should be part of tree_evaluator
#include "pt-jit.h"

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 (void) = default;

    bool all (void) 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, size_t level)
      : m_interpreter (interp), m_level (level), m_debug_frame (0),
        m_execution_mode (EX_NORMAL), m_in_debug_repl (false)
    { }

    void repl (const std::string& prompt = "debug> ");

    bool in_debug_repl (void) const { return m_in_debug_repl; }

    void dbcont (void)
    {
      m_execution_mode = EX_CONTINUE;
    }

    void dbquit (bool all = false)
    {
      if (all)
        m_execution_mode = EX_QUIT_ALL;
      else
        m_execution_mode = EX_QUIT;
    }

    bool quitting_debugger (void) const;

  private:

    interpreter& m_interpreter;

    size_t m_level;
    size_t m_debug_frame;
    execution_mode m_execution_mode;
    bool m_in_debug_repl;
  };

  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_method (tw, &tree_evaluator::restore_frame,
                      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 ();

    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);
              }

            event_manager& evmgr = m_interpreter.get_event_manager ();

            evmgr.enter_debugger_event (fcn_nm, fcn_file_nm, curr_debug_line);

            evmgr.set_workspace ();

            frame.add ([&evmgr, fcn_nm, curr_debug_line] (void) {
                         evmgr.execute_in_debugger_event (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 (! 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_method (input_sys, &input_system::set_PS1, input_sys.PS1 ());
    input_sys.PS1 (tmp_prompt);

    if (! m_interpreter.interactive ())
      {

        frame.add_method (m_interpreter, &interpreter::interactive,
                          m_interpreter.interactive ());

        m_interpreter.interactive (true);

        // FIXME: should debugging be possible in an embedded
        // interpreter?

        application *app = application::app ();

        if (app)
          {
            frame.add_method (app, &application::forced_interactive,
                              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);

#else

    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 ();

#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 (input_sys.PS2 ());
              }
            while (retval < 0);

#else

            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, std::cerr);

            // 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 (void) 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.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.in_top_level_repl ())
          throw quit_debug_exception (true);
        else
          return true;
      }

    return false;
  }

  bool tree_evaluator::at_top_level (void) const
  {
    return m_call_stack.at_top_level ();
  }

  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 ();
      }
  }

  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;

    size_t dpos = fname.rfind (sys::file_ops::dir_sep_char ());
    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);

    return fname;
  }

  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,
                                          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)
  {
    unwind_action act ([this] (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 ("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 ([this] (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 ("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_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)
      {
        size_t line = cmd.line ();
        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)
      {
        size_t line = cmd.line ();
        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 (void)
  {
    return ! (m_silent_functions && (m_statement_context == SC_FUNCTION
                                     || m_statement_context == SC_SCRIPT));
  }

  void
  tree_evaluator::reset_debug_state (void)
  {
    m_debug_mode = (m_bp_table.have_breakpoints () || m_dbstep_flag != 0
                    || 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_fcn (command_history::ignore_entries,
                   command_history::ignoring_entries ());

    command_history::ignore_entries (false);

    frame.add_method (m_call_stack, &call_stack::restore_frame,
                      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_method (es, &error_system::set_debug_on_error,
                      es.debug_on_error ());

    frame.add_method (es, &error_system::set_debug_on_warning,
                      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 ([this] (void)
               {
                 delete m_debugger_stack.top ();
                 m_debugger_stack.pop ();
               });

    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 (void) 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;
  }

  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 ([this] (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 ("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);

    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_method (m_call_stack, &call_stack::restore_frame,
                          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 ("source: context must be \"caller\" or \"base\"");
      }

    // Find symbol name that would be in symbol_table, if it were loaded.
    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;

    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& e)
          {
            error (e, "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)
  {
    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.
DEFCONSTMETHOD (end, interp, , ,
                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},
@code{try}, or @code{unwind_protect} block.

As an index of an array, the magic index @qcode{"end"} refers to the
last valid entry in an indexing operation.

Example:

@example
@group
@var{x} = [ 1 2 3; 4 5 6 ];
@var{x}(1,end)
   @result{} 3
@var{x}(end,1)
   @result{} 4
@var{x}(end,end)
   @result{} 6
@end group
@end example
@seealso{for, parfor, if, do, while, function, switch, try, unwind_protect}
@end deftypefn */)
{
  octave_value retval;

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

  const octave_value *indexed_object = tw.indexed_object ();
  int index_position = tw.index_position ();
  int num_indices = tw.num_indices ();

  // Return invalid index value instead of throwing an error so that we
  // will see an error about the object that is indexed rather than
  // "end" being used incorrectly.
  if (! indexed_object)
    return ovl (octave_NaN);

  if (indexed_object->isobject ())
    {
      octave_value_list args;

      args(2) = num_indices;
      args(1) = index_position + 1;
      args(0) = *indexed_object;

      std::string class_name = indexed_object->class_name ();

      octave::symbol_table& symtab = interp.get_symbol_table ();

      octave_value meth = symtab.find_method ("end", class_name);

      if (meth.is_defined ())
        return octave::feval (meth.function_value (), args, 1);
    }

  dim_vector dv = indexed_object->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);
        }
    }

  if (index_position < ndims)
    retval = dv(index_position);
  else
    retval = 1;

  return retval;
}

/*
%!test <*33637>
%! fail ("__undef_sym__ (end)", "'__undef_sym__' undefined");
*/

namespace octave
{
  octave_value_list
  tree_evaluator::convert_to_const_vector (tree_argument_list *arg_list,
                                           const octave_value *object)
  {
    // END doesn't make sense as a direct argument for a function (i.e.,
    // "fcn (end)" is invalid but "fcn (array (end))" is OK).  Maybe we
    // need a different way of asking an octave_value object this
    // question?

    bool stash_object = (arg_list->includes_magic_end ()
                         && object
                         && ! (object->is_function ()
                               || object->is_function_handle ()));

    unwind_protect_var<const octave_value *> upv1 (m_indexed_object);
    unwind_protect_var<int> upv2 (m_index_position);
    unwind_protect_var<int> upv3 (m_num_indices);

    if (stash_object)
      m_indexed_object = object;

    int len = arg_list->length ();

    std::list<octave_value> args;

    auto p = arg_list->begin ();
    for (int k = 0; k < len; k++)
      {
        if (stash_object)
          {
            m_index_position = k;
            m_num_indices = len;
          }

        tree_expression *elt = *p++;

        if (elt)
          {
            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++)
                  args.push_back (tmp_ovl(i));
              }
            else if (tmp.is_defined ())
              args.push_back (tmp);
          }
        else
          break;
      }

    return octave_value_list (args);
  }

  octave_value_list
  tree_evaluator::convert_return_list_to_const_vector
    (tree_parameter_list *ret_list, int nargout, 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 if (nargout <= len)
      {
        octave_value_list retval (nargout);

        int i = 0;

        for (tree_decl_elt *elt : *ret_list)
          {
            if (is_defined (elt->ident ()))
              retval(i) = evaluate (elt);

            i++;
          }

        return retval;
      }
    else
      {
        octave_value_list retval (len + vlen);

        int i = 0;

        for (tree_decl_elt *elt : *ret_list)
          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)
  {
    m_call_stack.push (fcn, local_vars);
  }

  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 (void)
  {
    m_call_stack.pop ();
  }

  int tree_evaluator::current_line (void) const
  {
    return m_call_stack.current_line ();
  }

  int tree_evaluator::current_column (void) const
  {
    return m_call_stack.current_column ();
  }

  int tree_evaluator::debug_user_code_line (void) const
  {
    return m_call_stack.debug_user_code_line ();
  }

  int tree_evaluator::debug_user_code_column (void) 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 (void) const
  {
    return m_call_stack.current_user_code ();
  }

  unwind_protect * tree_evaluator::curr_fcn_unwind_protect_frame (void)
  {
    return m_call_stack.curr_fcn_unwind_protect_frame ();
  }

  octave_user_code * tree_evaluator::debug_user_code (void) 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 (void) const
  {
    return m_call_stack.current_function (true);
  }

  bool tree_evaluator::goto_frame (size_t n, bool verbose)
  {
    return m_call_stack.goto_frame (n, verbose);
  }

  void tree_evaluator::goto_caller_frame (void)
  {
    m_call_stack.goto_caller_frame ();
  }

  void tree_evaluator::goto_base_frame (void)
  {
    m_call_stack.goto_base_frame ();
  }

  void tree_evaluator::restore_frame (size_t n)
  {
    return m_call_stack.restore_frame (n);
  }

  std::string tree_evaluator::get_dispatch_class (void) 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 (void) 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 (void) 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 (void) const
  {
    return m_call_stack.backtrace ();
  }

  octave_map tree_evaluator::empty_backtrace (void) const
  {
    return m_call_stack.empty_backtrace ();
  }

  std::string tree_evaluator::backtrace_message (void) 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 (void)
  {
    m_call_stack.pop ();
  }

  symbol_scope tree_evaluator::get_top_scope (void) const
  {
    return m_call_stack.top_scope ();
  }

  symbol_scope tree_evaluator::get_current_scope (void) 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 (void) 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 (void)
  {
    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 (void)
  {
    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 (void)
  {
    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 (void) const
  {
    return m_call_stack.global_variable_names ();
  }

  std::list<std::string> tree_evaluator::top_level_variable_names (void) const
  {
    return m_call_stack.top_level_variable_names ();
  }

  std::list<std::string> tree_evaluator::variable_names (void) 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 ());
          }

        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;
        size_t p2 = std::string::npos;

        if (name[0] == '@')
          {
            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 (void) const
  {
    return m_call_stack.current_user_code () != nullptr;
  }

  void
  tree_evaluator::visit_decl_command (tree_decl_command& cmd)
  {
    if (m_echo_state)
      {
        size_t line = cmd.line ();
        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);
          }
      }
  }

  void
  tree_evaluator::visit_simple_for_command (tree_simple_for_command& cmd)
  {
    size_t line = cmd.line ();

    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 defined (HAVE_LLVM)
    if (tree_jit::execute (cmd, rhs))
      return;
#endif

    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 ())
      {
        Range rng = rhs.range_value ();

        octave_idx_type steps = rng.numel ();

        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;
          }
      }
    else 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 ();
      }
    else if (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;

                // do_index_op expects one-based indices.
                idx(iidx) = i;
                octave_value val = arg.do_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);
          }
      }
    else
      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)
  {
    size_t line = cmd.line ();

    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_octave_user_script (octave_user_script&)
  {
    // ??
    panic_impossible ();
  }

  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;

    if (m_call_stack.size () >= static_cast<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);

    cmd_list->accept (*this);

    if (m_returning)
      m_returning = 0;

    if (m_breaking)
      m_breaking--;

    return retval;
  }

  void
  tree_evaluator::visit_octave_user_function (octave_user_function&)
  {
    // ??
    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;

    tree_statement_list *cmd_list = user_function.body ();

    if (! cmd_list)
      return 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;

    if (user_function.is_classdef_constructor ())
      {
        if (args.length () > 0)
          {
            ret_args = args.slice (0, 1, true);
            args = args.slice (1, args.length () - 1, true);
          }
        else
          panic_impossible ();
      }

#if defined (HAVE_LLVM)
    if (user_function.is_special_expr ()
        && tree_jit::execute (user_function, args, retval))
      return retval;
#endif

    if (m_call_stack.size () >= static_cast<size_t> (m_max_recursion_depth))
      error ("max_recursion_depth exceeded");

    bind_auto_fcn_vars (xargs.name_tags (), args.length (),
                        nargout, user_function.takes_varargs (),
                        user_function.all_va_args (args));

    tree_parameter_list *param_list = user_function.parameter_list ();

    if (param_list && ! param_list->varargs_only ())
      define_parameter_list_from_arg_vector (param_list, args);

    // For classdef constructor, pre-populate the output arguments
    // with the pre-initialized object instance, extracted above.

    tree_parameter_list *ret_list = user_function.return_list ();

    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);
      }

    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);

    unwind_action act1 ([this] () {
                          m_call_stack.clear_current_frame_values ();
                        });

    {
      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 ())
        {
          assert (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,
                                                      varargout);
      }

    if (user_function.is_nested_function ()
        || user_function.is_parent_function ())
      {
        // Copy current stack frame to handles to nested functions.

        for (octave_idx_type i = 0; i < retval.length (); i++)
          {
            octave_value val = retval(i);

            if (val.is_function_handle ())
              {
                octave_fcn_handle *fh = val.fcn_handle_value ();

                if (fh)
                  fh->push_closure_context (*this);
              }
          }
      }

    return retval;
  }

  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)
      {
        size_t line = cmd.line ();
        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)
      {
        size_t line = cmd.line ();
        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)
      {
        size_t line = cmd.line ();
        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)
              cmd->accept (*this);
            else
              {
                if (m_echo_state)
                  {
                    size_t line = stmt.line ();
                    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;
          }
      }
  }

  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)
      {
        size_t line = cmd.line ();
        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)
      {
        size_t line = cmd.line ();
        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_method (m_call_stack, &call_stack::set_line,
                      m_call_stack.current_line ());
    frame.add_method (m_call_stack, &call_stack::set_column,
                      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)
      {
        size_t line = cmd.line ();
        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)
  {
    size_t line = cmd.line ();

    if (m_echo_state)
      {
        echo_code (line);
        line++;
      }

#if defined (HAVE_LLVM)
    if (tree_jit::execute (cmd))
      return;
#endif

    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)
  {
    size_t line = cmd.line ();

    if (m_echo_state)
      {
        echo_code (line);
        line++;
      }

#if defined (HAVE_LLVM)
    if (tree_jit::execute (cmd))
      return;
#endif

    unwind_protect_var<bool> upv (m_in_loop_command, true);

    tree_expression *expr = cmd.condition ();
    int until_line = cmd.line ();
    int until_column = cmd.column ();

    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));

        m_call_stack.set_location (until_line, until_column);

        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
          {
            assign (ans, val);

            if (print)
              {
                octave_value_list args = ovl (val);
                args.stash_name_tags (string_vector (ans));
                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)
      {
        m_dbstep_flag = 0;

        enter_debugger ();
      }
  }

  bool
  tree_evaluator::is_logically_true (tree_expression *expr,
                                     const char *warn_for)
  {
    bool expr_value = false;

    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 (void)
  {
    return m_call_stack.get_symbol_info ();
  }

  symbol_info_list
  tree_evaluator::top_scope_symbol_info (void) const
  {
    return m_call_stack.top_scope_symbol_info ();
  }

  octave_map tree_evaluator::get_autoload_map (void) const
  {
    Cell func_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)
      {
        func_names(i) = fcn_fname.first;
        file_names(i) = fcn_fname.second;

        i++;
      }

    octave_map m;

    m.assign ("function", func_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 (void) 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& e,
                                          const tree_expression *expr)
  {
    std::string extra_message;

    if (is_variable (expr))
      {
        std::string var = expr->name ();

        e.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 = e.message () + extra_message;

    error_with_id (e.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,
                                   const octave_value *object, bool rvalue)
  {
    octave_value_list retval;

    if (args)
      {
        unwind_protect_var<const std::list<octave_lvalue> *>
          upv (m_lvalue_list, nullptr);

        if (rvalue && object && args->has_magic_end ()
            && object->is_undefined ())
          err_invalid_inquiry_subscript ();

        retval = convert_to_const_vector (args, object);
      }

    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,
                                   size_t 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,
                                  size_t 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,
                                      size_t pos)
  {
    m_echo_state = state;
    m_echo_file_name = file_name;
    m_echo_file_pos = pos;
  }

  void
  tree_evaluator::maybe_set_echo_state (void)
  {
    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 ();

        size_t pos = m_call_stack.current_line ();

        set_echo_state (type, file_name, pos);
      }
  }

  void
  tree_evaluator::push_echo_state_cleanup (unwind_protect& frame)
  {
    frame.add_method (this, &tree_evaluator::uwp_set_echo_state,
                      m_echo_state, m_echo_file_name, m_echo_file_pos);
  }

  bool tree_evaluator::maybe_push_echo_state_cleanup (void)
  {
    // 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:
        {
          std::string arg0 = argv[0];

          if (arg0 == "on")
            m_echo = ECHO_SCRIPTS;
          else if (arg0 == "off")
            m_echo = ECHO_OFF;
          else
            {
              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;
                    }
                }
            }
        }
        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 (void) const
  {
    return (m_debugger_stack.empty ()
            ? false : m_debugger_stack.top()->in_debug_repl ());
  }

  void tree_evaluator::dbcont (void)
  {
    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);
  }

  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 (size_t 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);

        size_t 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 (void)
  {
    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,
                                           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_fcn_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;
  }
}

DEFMETHOD (max_recursion_depth, interp, args, nargout,
           doc: /* -*- texinfo -*-
@deftypefn  {} {@var{val} =} max_recursion_depth ()
@deftypefnx {} {@var{old_val} =} max_recursion_depth (@var{new_val})
@deftypefnx {} {} max_recursion_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_stack_depth}
@end deftypefn */)
{
  octave::tree_evaluator& tw = interp.get_evaluator ();

  return tw.max_recursion_depth (args, nargout);
}

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

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

DEFMETHOD (whos_line_format, interp, args, nargout,
           doc: /* -*- texinfo -*-
@deftypefn  {} {@var{val} =} whos_line_format ()
@deftypefnx {} {@var{old_val} =} whos_line_format (@var{new_val})
@deftypefnx {} {} whos_line_format (@var{new_val}, "local")
Query or set the format string used by the command @code{whos}.

A full format string is:
@c Set example in small font to prevent overfull line

@smallexample
%[modifier]<command>[:width[:left-min[:balance]]];
@end smallexample

The following command sequences are available:

@table @code
@item %a
Prints attributes of variables (g=global, p=persistent, f=formal parameter).

@item %b
Prints number of bytes occupied by variables.

@item %c
Prints class names of variables.

@item %e
Prints elements held by variables.

@item %n
Prints variable names.

@item %s
Prints dimensions of variables.

@item %t
Prints type names of variables.
@end table

Every command may also have an alignment modifier:

@table @code
@item l
Left alignment.

@item r
Right alignment (default).

@item c
Column-aligned (only applicable to command %s).
@end table

The @code{width} parameter is a positive integer specifying the minimum
number of columns used for printing.  No maximum is needed as the field will
auto-expand as required.

The parameters @code{left-min} and @code{balance} are only available when
the column-aligned modifier is used with the command @samp{%s}.
@code{balance} specifies the column number within the field width which
will be aligned between entries.  Numbering starts from 0 which indicates
the leftmost column.  @code{left-min} specifies the minimum field width to
the left of the specified balance column.

The default format is:

@qcode{"  %a:4; %ln:6; %cs:16:6:1;  %rb:12;  %lc:-1;@xbackslashchar{}n"}

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{whos}
@end deftypefn */)
{
  octave::tree_evaluator& tw = interp.get_evaluator ();

  return tw.whos_line_format (args, nargout);
}

DEFMETHOD (silent_functions, interp, args, nargout,
           doc: /* -*- texinfo -*-
@deftypefn  {} {@var{val} =} silent_functions ()
@deftypefnx {} {@var{old_val} =} silent_functions (@var{new_val})
@deftypefnx {} {} silent_functions (@var{new_val}, "local")
Query or set the internal variable that controls whether internal
output from a function is suppressed.

If this option is disabled, Octave will display the results produced by
evaluating expressions within a function body that are not terminated with
a semicolon.

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.
@end deftypefn */)
{
  octave::tree_evaluator& tw = interp.get_evaluator ();

  return tw.silent_functions (args, nargout);
}

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

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

DEFMETHOD (string_fill_char, interp, args, nargout,
           doc: /* -*- texinfo -*-
@deftypefn  {} {@var{val} =} string_fill_char ()
@deftypefnx {} {@var{old_val} =} string_fill_char (@var{new_val})
@deftypefnx {} {} string_fill_char (@var{new_val}, "local")
Query or set the internal variable used to pad all rows of a character
matrix to the same length.

The value must be a single character and the default is @qcode{" "} (a
single space).  For example:

@example
@group
string_fill_char ("X");
[ "these"; "are"; "strings" ]
      @result{}  "theseXX"
          "areXXXX"
          "strings"
@end group
@end example

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.
@end deftypefn */)
{
  octave::tree_evaluator& tw = interp.get_evaluator ();

  return tw.string_fill_char (args, nargout);
}

/*
## string_fill_char() function call must be outside of %!test block
## due to the way a %!test block is wrapped inside a function
%!shared orig_val, old_val
%! orig_val = string_fill_char ();
%! old_val  = string_fill_char ("X");
%!test
%! assert (orig_val, old_val);
%! assert (string_fill_char (), "X");
%! assert (["these"; "are"; "strings"], ["theseXX"; "areXXXX"; "strings"]);
%! string_fill_char (orig_val);
%! assert (string_fill_char (), orig_val);

%!assert ( [ [], {1} ], {1} )

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

DEFMETHOD (PS4, interp, args, nargout,
           doc: /* -*- texinfo -*-
@deftypefn  {} {@var{val} =} PS4 ()
@deftypefnx {} {@var{old_val} =} PS4 (@var{new_val})
@deftypefnx {} {} PS4 (@var{new_val}, "local")
Query or set the character string used to prefix output produced
when echoing commands is enabled.

The default value is @qcode{"+ "}.
@xref{Diary and Echo Commands}, for a description of echoing commands.

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{echo, PS1, PS2}
@end deftypefn */)
{
  octave::tree_evaluator& tw = interp.get_evaluator ();

  return tw.PS4 (args, nargout);
}

DEFMETHOD (echo, interp, args, nargout,
           doc: /* -*- texinfo -*-
@deftypefn  {} {} echo
@deftypefnx {} {} echo on
@deftypefnx {} {} echo off
@deftypefnx {} {} echo on all
@deftypefnx {} {} echo off all
@deftypefnx {} {} echo @var{function} on
@deftypefnx {} {} echo @var{function} off
Control whether commands are displayed as they are executed.

Valid options are:

@table @code
@item on
Enable echoing of commands as they are executed in script files.

@item off
Disable echoing of commands as they are executed in script files.

@item on all
Enable echoing of commands as they are executed in script files and
functions.

@item off all
Disable echoing of commands as they are executed in script files and
functions.

@item @var{function} on
Enable echoing of commands as they are executed in the named function.

@item @var{function} off
Disable echoing of commands as they are executed in the named function.
@end table

@noindent
With no arguments, @code{echo} toggles the current echo state.

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

  return tw.echo (args, nargout);
}

/*
%!error echo ([])
%!error echo (0)
%!error echo ("")
%!error echo ("Octave")
%!error echo ("off", "invalid")
%!error echo ("on", "invalid")
%!error echo ("on", "all", "all")
*/