/*

Copyright (C) 2009-2016 John W. Eaton

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
<http://www.gnu.org/licenses/>.

*/

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

#include <cctype>

#include <iostream>

#include <fstream>
#include <typeinfo>

#include "call-stack.h"
#include "debug.h"
#include "defun.h"
#include "error.h"
#include "errwarn.h"
#include "input.h"
#include "interpreter.h"
#include "ov-fcn-handle.h"
#include "ov-usr-fcn.h"
#include "variables.h"
#include "pt-all.h"
#include "pt-eval.h"
#include "symtab.h"
#include "unwind-prot.h"

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

// Maximum nesting level for functions, scripts, or sourced files called
// recursively.
int Vmax_recursion_depth = 256;

// If TRUE, turn off printing of results in functions (as if a
// semicolon has been appended to each statement).
static bool Vsilent_functions = false;

namespace octave
{
  int tree_evaluator::dbstep_flag = 0;

  size_t tree_evaluator::current_frame = 0;

  bool tree_evaluator::debug_mode = false;

  bool tree_evaluator::quiet_breakpoint_flag = false;

  tree_evaluator::stmt_list_type tree_evaluator::statement_context
    = tree_evaluator::other;

  bool tree_evaluator::in_loop_command = false;

  // Normal evaluator.

  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_break_command (tree_break_command& cmd)
  {
    if (debug_mode)
      do_breakpoint (cmd.is_breakpoint (true));

    if (statement_context == function || statement_context == script
        || in_loop_command)
      tree_break_command::breaking = 1;
  }

  void
  tree_evaluator::visit_colon_expression (tree_colon_expression&)
  {
    panic_impossible ();
  }

  void
  tree_evaluator::visit_continue_command (tree_continue_command& cmd)
  {
    if (debug_mode)
      do_breakpoint (cmd.is_breakpoint (true));

    if (in_loop_command)
      tree_continue_command::continuing = 1;
  }

  void
  tree_evaluator::reset_debug_state (void)
  {
    debug_mode = bp_table::have_breakpoints () || Vdebugging;

    dbstep_flag = 0;
  }

  bool
  tree_evaluator::statement_printing_enabled (void)
  {
    return ! (Vsilent_functions && (statement_context == function
                                    || statement_context == script));
  }
}

static inline void
do_global_init (tree_decl_elt& elt)
{
  tree_identifier *id = elt.ident ();

  if (id)
    {
      id->mark_global ();

      octave_lvalue ult = id->lvalue ();

      if (ult.is_undefined ())
        {
          tree_expression *expr = elt.expression ();

          octave_value init_val;

          if (expr)
            init_val = expr->rvalue1 ();
          else
            init_val = Matrix ();

          ult.assign (octave_value::op_asn_eq, init_val);
        }
    }
}

static inline void
do_static_init (tree_decl_elt& elt)
{
  tree_identifier *id = elt.ident ();

  if (id)
    {
      id->mark_as_static ();

      octave_lvalue ult = id->lvalue ();

      if (ult.is_undefined ())
        {
          tree_expression *expr = elt.expression ();

          octave_value init_val;

          if (expr)
            init_val = expr->rvalue1 ();
          else
            init_val = Matrix ();

          ult.assign (octave_value::op_asn_eq, init_val);
        }
    }
}

namespace octave
{
  void
  tree_evaluator::do_decl_init_list (decl_elt_init_fcn fcn,
                                     tree_decl_init_list *init_list)
  {
    if (init_list)
      {
        for (tree_decl_init_list::iterator p = init_list->begin ();
             p != init_list->end (); p++)
          {
            tree_decl_elt *elt = *p;

            fcn (*elt);
          }
      }
  }

  void
  tree_evaluator::visit_global_command (tree_global_command& cmd)
  {
    if (debug_mode)
      do_breakpoint (cmd.is_breakpoint (true));

    do_decl_init_list (do_global_init, cmd.initializer_list ());
  }

  void
  tree_evaluator::visit_persistent_command (tree_persistent_command& cmd)
  {
    if (debug_mode)
      do_breakpoint (cmd.is_breakpoint (true));

    do_decl_init_list (do_static_init, cmd.initializer_list ());
  }

  void
  tree_evaluator::visit_decl_elt (tree_decl_elt&)
  {
    panic_impossible ();
  }

  void
  tree_evaluator::visit_decl_init_list (tree_decl_init_list&)
  {
    panic_impossible ();
  }
}

// Decide if it's time to quit a for or while loop.
static inline bool
quit_loop_now (void)
{
  octave_quit ();

  // Maybe handle 'continue N' someday...

  if (tree_continue_command::continuing)
    tree_continue_command::continuing--;

  bool quit = (tree_return_command::returning
               || tree_break_command::breaking
               || tree_continue_command::continuing);

  if (tree_break_command::breaking)
    tree_break_command::breaking--;

  return quit;
}

namespace octave
{
  void
  tree_evaluator::visit_simple_for_command (tree_simple_for_command& cmd)
  {
    if (debug_mode)
      do_breakpoint (cmd.is_breakpoint (true));

    // FIXME: need to handle PARFOR loops here using cmd.in_parallel ()
    // and cmd.maxproc_expr ();

    octave::unwind_protect frame;

    frame.protect_var (in_loop_command);

    in_loop_command = true;

    tree_expression *expr = cmd.control_expr ();

    octave_value rhs = expr->rvalue1 ();

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

    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++)
          {
            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 ())
      {
        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.is_cell () || rhs.is_string ()
             || rhs.is_map ())
      {
        // 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);

        if (steps > 0)
          {
            octave_value arg = rhs;
            if (rhs.ndims () > 2)
              arg = arg.reshape (dv);

            // for row vectors, use single index to speed things up.
            octave_value_list idx;
            octave_idx_type iidx;
            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++)
              {
                // 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
      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)
  {
    if (debug_mode)
      do_breakpoint (cmd.is_breakpoint (true));

    octave::unwind_protect frame;

    frame.protect_var (in_loop_command);

    in_loop_command = true;

    tree_expression *expr = cmd.control_expr ();

    octave_value rhs = expr->rvalue1 ();

    if (rhs.is_undefined ())
      return;

    if (! rhs.is_map ())
      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 ();

    tree_argument_list::iterator p = lhs->begin ();

    tree_expression *elt = *p++;

    octave_lvalue val_ref = elt->lvalue ();

    elt = *p;

    octave_lvalue key_ref = elt->lvalue ();

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

  void
  tree_evaluator::visit_octave_user_function (octave_user_function&)
  {
    panic_impossible ();
  }

  void
  tree_evaluator::visit_octave_user_function_header (octave_user_function&)
  {
    panic_impossible ();
  }

  void
  tree_evaluator::visit_octave_user_function_trailer (octave_user_function&)
  {
    panic_impossible ();
  }

  void
  tree_evaluator::visit_function_def (tree_function_def& cmd)
  {
    octave_value fcn = cmd.function ();

    octave_function *f = fcn.function_value ();

    if (f)
      {
        std::string nm = f->name ();

        symbol_table::install_cmdline_function (nm, fcn);

        // Make sure that any variable with the same name as the new
        // function is cleared.

        symbol_table::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)
  {
    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_command_list::iterator p = lst.begin (); p != lst.end (); p++)
      {
        tree_if_clause *tic = *p;

        tree_expression *expr = tic->condition ();

        if (statement_context == function || statement_context == script)
          octave_call_stack::set_location (tic->line (), tic->column ());

        if (debug_mode && ! tic->is_else_clause ())
          do_breakpoint (tic->is_breakpoint (true));

        if (tic->is_else_clause () || expr->is_logically_true ("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 (debug_mode && cmd.is_end_of_fcn_or_script ())
      do_breakpoint (cmd.is_breakpoint (true), 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_funcall (tree_funcall&)
  {
    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 (debug_mode)
      do_breakpoint (cmd.is_breakpoint (true));

    // Act like dbcont.

    if (Vdebugging
        && octave_call_stack::current_frame () == current_frame)
      {
        Vdebugging = false;

        reset_debug_state ();
      }
    else if (statement_context == function || statement_context == script
             || in_loop_command)
      tree_return_command::returning = 1;
  }

  void
  tree_evaluator::visit_return_list (tree_return_list&)
  {
    panic_impossible ();
  }

  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 (statement_context == function || statement_context == script)
          {
            // Skip commands issued at a debug> prompt to avoid disturbing
            // the state of the program we are debugging.

            if (Vtrack_line_num)
              octave_call_stack::set_location (stmt.line (), stmt.column ());

            if ((statement_context == script
                 && ((Vecho_executing_commands & ECHO_SCRIPTS
                      && octave_call_stack::all_scripts ())
                     || Vecho_executing_commands & ECHO_FUNCTIONS))
                || (statement_context == function
                    && Vecho_executing_commands & ECHO_FUNCTIONS))
              stmt.echo_code ();
          }

        try
          {
            if (cmd)
              cmd->accept (*this);
            else
              {
                if (debug_mode)
                  do_breakpoint (expr->is_breakpoint (true));

                // 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
                // call expr->rvalue1 () and that should take care of
                // everything, binding ans as necessary?

                bool do_bind_ans = false;

                if (expr->is_identifier ())
                  {
                    tree_identifier *id = dynamic_cast<tree_identifier *> (expr);

                    do_bind_ans = (! id->is_variable ());
                  }
                else
                  do_bind_ans = (! expr->is_assignment_expression ());

                octave_value tmp_result = expr->rvalue1 (0);

                if (do_bind_ans && tmp_result.is_defined ())
                  bind_ans (tmp_result, expr->print_result ()
                            && statement_printing_enabled ());

                //              if (tmp_result.is_defined ())
                //                result_values(0) = tmp_result;
              }
          }
        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");
          }
      }
  }

  void
  tree_evaluator::visit_statement_list (tree_statement_list& lst)
  {
    // FIXME: commented out along with else clause below.
    // static octave_value_list empty_list;

    tree_statement_list::iterator 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 (tree_break_command::breaking
                || tree_continue_command::continuing)
              break;

            if (tree_return_command::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 (debug_mode)
      do_breakpoint (cmd.is_breakpoint (true));

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

    tree_switch_case_list *lst = cmd.case_list ();

    if (lst)
      {
        for (tree_switch_case_list::iterator p = lst->begin ();
             p != lst->end (); p++)
          {
            tree_switch_case *t = *p;

            if (t->is_default_case () || t->label_matches (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)
  {
    bool execution_error = false;

    {
      // unwind frame before catch block
      octave::unwind_protect frame;

      frame.protect_var (buffer_error_messages);
      frame.protect_var (Vdebug_on_error);
      frame.protect_var (Vdebug_on_warning);

      buffer_error_messages++;
      Vdebug_on_error = false;
      Vdebug_on_warning = false;

      // The catch code is *not* added to unwind_protect stack;
      // it doesn't need to be run on interrupts.

      tree_statement_list *try_code = cmd.body ();

      if (try_code)
        {
          try
            {
              in_try_catch++;
              try_code->accept (*this);
              in_try_catch--;
            }
          catch (const octave::execution_exception&)
            {
              recover_from_exception ();

              in_try_catch--;          // must be restored before "catch" block
              execution_error = true;
            }
        }
      // Unwind to let the user print any messages from
      // errors that occurred in the body of the try_catch statement,
      // or throw further errors.
    }

    if (execution_error)
      {
        tree_statement_list *catch_code = cmd.cleanup ();
        if (catch_code)
          {
            tree_identifier *expr_id = cmd.identifier ();
            octave_lvalue ult;

            if (expr_id)
              {
                ult = expr_id->lvalue ();

                octave_scalar_map err;

                err.assign ("message", last_error_message ());
                err.assign ("identifier", last_error_id ());
                err.assign ("stack", last_error_stack ());

                ult.assign (octave_value::op_asn_eq, err);
              }

            // perform actual "catch" block
            if (catch_code)
              catch_code->accept (*this);
          }
      }
  }

  void
  tree_evaluator::do_unwind_protect_cleanup_code (tree_statement_list *list)
  {
    octave::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_fcn (octave_call_stack::set_line,
                   octave_call_stack::current_line ());
    frame.add_fcn (octave_call_stack::set_column,
                   octave_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 (tree_return_command::returning);
    tree_return_command::returning = 0;

    frame.protect_var (tree_break_command::breaking);
    tree_break_command::breaking = 0;

    try
      {
        if (list)
          list->accept (*this);
      }
    catch (const octave::execution_exception&)
      {
        recover_from_exception ();

        if (tree_break_command::breaking || tree_return_command::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 (tree_break_command::breaking || tree_return_command::returning)
      frame.discard (2);
    else
      frame.run (2);
  }

  void
  tree_evaluator::visit_unwind_protect_command (tree_unwind_protect_command& cmd)
  {
    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 octave::execution_exception&)
          {
            // 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?
            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;
          }

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

    octave::unwind_protect frame;

    frame.protect_var (in_loop_command);

    in_loop_command = true;

    tree_expression *expr = cmd.condition ();

    if (! expr)
      panic_impossible ();

    for (;;)
      {
        if (debug_mode)
          do_breakpoint (cmd.is_breakpoint (true));

        if (expr->is_logically_true ("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)
  {
#if defined (HAVE_LLVM)
    if (tree_jit::execute (cmd))
      return;
#endif

    octave::unwind_protect frame;

    frame.protect_var (in_loop_command);

    in_loop_command = true;

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

    if (! expr)
      panic_impossible ();

    for (;;)
      {
        tree_statement_list *loop_body = cmd.body ();

        if (loop_body)
          loop_body->accept (*this);

        if (quit_loop_now ())
          break;

        if (debug_mode)
          do_breakpoint (cmd.is_breakpoint (true));

        octave_call_stack::set_location (until_line, until_column);

        if (expr->is_logically_true ("do-until"))
          break;
      }
  }

  void
  tree_evaluator::do_breakpoint (tree_statement& stmt) const
  {
    do_breakpoint (stmt.is_breakpoint (true), stmt.is_end_of_fcn_or_script ());
  }

  void
  tree_evaluator::do_breakpoint (bool is_breakpoint,
                                 bool is_end_of_fcn_or_script) const
  {
    bool break_on_this_statement = false;

    if (octave_debug_on_interrupt_state)
      {
        break_on_this_statement = true;

        octave_debug_on_interrupt_state = false;

        current_frame = octave_call_stack::current_frame ();
      }
    else if (is_breakpoint)
      {
        break_on_this_statement = true;

        dbstep_flag = 0;

        current_frame = octave_call_stack::current_frame ();
      }
    else if (dbstep_flag > 0)
      {
        if (octave_call_stack::current_frame () == current_frame)
          {
            if (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;

                dbstep_flag = 0;
              }
            else
              {
                // Executing "dbstep N".  Decrease N by one and continue.

                dbstep_flag--;
              }

          }
        else if (dbstep_flag == 1
                 && octave_call_stack::current_frame () < current_frame)
          {
            // We stepped out from the end of a function.

            current_frame = octave_call_stack::current_frame ();

            break_on_this_statement = true;

            dbstep_flag = 0;
          }
      }
    else if (dbstep_flag == -1)
      {
        // We get here if we are doing a "dbstep in".

        break_on_this_statement = true;

        dbstep_flag = 0;

        current_frame = octave_call_stack::current_frame ();
      }
    else if (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
            && octave_call_stack::current_frame () == current_frame)
          dbstep_flag = -1;
      }

    if (break_on_this_statement)
      do_keyboard ();

  }

  // ARGS is currently unused, but since the do_keyboard function in
  // input.cc accepts an argument list, we preserve it here so that the
  // interface won't have to change if we decide to use it in the future.

  octave_value
  tree_evaluator::do_keyboard (const octave_value_list& args) const
  {
    return ::do_keyboard (args);
  }
}

DEFUN (max_recursion_depth, 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.
@end deftypefn */)
{
  return SET_INTERNAL_VARIABLE (max_recursion_depth);
}

/*
%!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))
*/

DEFUN (silent_functions, 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 */)
{
  return SET_INTERNAL_VARIABLE (silent_functions);
}

/*
%!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))
*/

